#currency #money #amount #macro #decimal #conversion #arithmetic

oxydized-money-macros

Companion library to oxydized-gains providing convenience macros

2 unstable releases

0.2.0 Mar 3, 2024
0.1.0 Mar 3, 2024

#180 in Finance

MIT license

10KB

Oxydized Money 💵 ↔ 💶

Build Test Clippy Doc

Crates.io Docs.rs Crates.io Crates.io

This crate aims at providing data types to manipulate amounts of money in specific currencies, convert amounts between currencies and make sure that any computation is performed on amounts of the same currency.

Motivations

This crate was introduced because all the existing known alternatives have some signigicant drawbacks:

  • using native floating point type like f32 or f64 suffer from their lack of precision.
  • using rust_decimal::Decimal solves this issue but does not prevent from adding amounts in different currency.
  • using rusty_money::Money, although slightly better, does not really solve the conversion issue because performing arithmetic operations on amounts of different currencies panics.

Rust being dedicated to proper error handling, all these options feel like compromises. This crate aims to improve this by by providing three distinct data types:

  • Amount for storing amounts in a given currency.
  • CurrencyError for representing any errors (currency mismatch, ...) during arithmetic operations on Amounts.
  • AmountResult for storing the result of arithmetic operations (either an Amount or CurrencyError).

Arithmetic operations are defined in such a way that these three types inte-roperate almost seemlessly. However, when performing an operation, the type of output always reflect whether an error could have occured. Operation that cannot fail will output Amounts and operations that can fail will outout AmountResults. Before getting at the underlying Amount, AmountResults need to be properly checked for errors.

No more 🦶-guns

Examples

use oxydized_money_macros::{eur, usd, dec};
use oxydized_money::{
    Currency::{EUR,USD},
    CurrencyError,
    Decimal,
};

// Amount(USD)
let capital = usd!(10_000);

// Decinal
let exchange_rate = dec!(0.928);

// Amount(EUR)
let converted = capital.converted_to(EUR, exchange_rate);

// Amount(EUR)
let fees = eur!(15.2);

// Amount(EUR) + Amount(EUR) => AmountResult(EUR)
let subtotal = converted + fees;

// Amount(EUR) * Decimal => Amount(EUR)
let extras = eur!(50) * dec!(2);

// AmountResult(EUR) + Amount(EUR) => AmountResult(EUR)
let total = subtotal + extras;

// Comparing AmountResult with Amounts
assert_eq!(total, eur!(9_395.200));
#
// AmountResult(EUR) + Amount(USD) => AmountResult(Mismatch(EUR,USD))
let oops = total + usd!(20);

// Comparing AmountResult with CurrencyError
assert_eq!(oops, CurrencyError::Mismatch(EUR,USD));

// AmountResult(Mismatch(EUR,USD)) + Amount(USD) => AmountResult(Mismatch(EUR,USD))
let oh_my = oops + usd!(200);
assert_eq!(oh_my, CurrencyError::Mismatch(EUR,USD));

// "Everything, everywhere, all at once."
assert_eq!(
    usd!(10_000).converted_to(EUR, dec!(0.928)) + eur!(15.2) + eur!(50)*dec!(2),
    eur!(9_395.200)
);

Supported Operations

Binary Operations

Amount

Left Operand Operator Right Operand Output
Amount * Decimal Amount
Amount / Decimal AmountResult
Amount {+,-} Amount AmountResult
Amount {+,-} AmountResult AmountResult
Amount {==,!=} Amount bool
Amount {==,!=} AmountResult bool
Amount {<,>,>=,<= } Amount bool

AmountResult

Left Operand Operator Right Operand Output
AmountResult * Decimal AmountResult
AmountResult / Decimal AmountResult
AmountResult {+,-} Amount AmountResult
AmountResult {+,-} AmountResult AmountResult
AmountResult {==,!=} Amount bool
AmountResult {==,!=} AmountResult bool
AmountResult {==,!=} CurrencyError bool

CurrencyError

Left Operand Operator Right Operand Output
CurrencyError {==,!=} AmountResult bool
CurrencyError {==,!=} CurrencyError bool

Unary Operations

Amount

Operator Operand Output
- Amount Amount

AmountResult

Operator Operand Output
- AmountResult AmountResult

Dependencies

~550–750KB
~15K SLoC