moneylib
A library to deal with money safely using floating-point fixed-precision decimal.
Overview
moneylib provides a safe, robust, and ergonomic way to work with monetary value in Rust.
It handles currency and amount with operations and arithmetics avoiding floating, rounding, and precision issue exist in typical binary floating-point type.
It also make sure the money always in valid state on every operations and arithmetics done on it avoiding overflow/truncation/wrap and without fractions.
This crate uses Decimal type underneath for the amount of money.
Features
Here are some features supported:
- Type-safe:
- Compile-time check for arithmetics and operations.
- Runtime check for overflowed/wrapped/truncated amount.
- Prevents currencies mixing at compile-time.
- Value type to represent money.
Money: represents money in amount rounded to the currency's minor unit.RawMoney: represents money in raw amount keeping the precisions and choose when to round.
- Helper macros:
dec!(...): re-export from Decimal crate to instantiate hardcoded decimals.money!(...,...): instantiateMoneywith currency code and amount.raw!(...,...): instantiateRawMoneywith currency code and amount.
- Access to its amount and currency's metadata.
- Arithmetics: (*,/,+,-), operator overloading supported.
- Comparisons: (>,<,>=,<=,==,!=), operator overloading supported.
- Negative money.
- Formatting and custom formatting.
- Rounding with multiple strategies: Bankers rounding, half-up, half-down, ceil, and floor.
- Money in form of its smallest amount (minor amount).
- Some basic operations like absolute value, min, max, and clamp.
- Support for all ISO 4217 currencies.
- New/custom currency by implementing
Currencytrait. - Serde.
- Supports locale formatting.
- Exchange rates for conversions.
- Some accounting operations:
- Percentage calculations.
- Interest calculations.
- etc.
Example
use ;
use FromStr;
// Creating money from string (supports thousand separators)
let usd_money = from_str.unwrap;
println!; // USD 1,234.56
// Creating money from minor amount (cents for USD)
let from_cents = from_minor.unwrap;
println!; // USD 123.45
// Arithmetic operations with automatic rounding
let money_a = new.unwrap;
let money_b = new.unwrap;
println!; // USD 150.00
println!; // USD 150.00
println!; // USD 33.33 (rounded)
// Comparisons
println!; // true
println!; // true
// Working with different currencies
// JPY has 0 decimal places
let jpy_money = new.unwrap;
println!; // JPY 1,000
// BHD has 3 decimal places
let bhd_money = new.unwrap;
println!; // BHD 12.345
// Custom formatting
let money = new.unwrap;
println!; // $1,234.56
println!; // USD 1,234.56
// Rounding with round_with method
let rounded = new.unwrap;
let half_up_rounded = rounded.round_with;
println!; // 123.46
// Negative amounts
let negative = new.unwrap;
println!; // USD -50.00
println!; // USD 50.00
// Error handling with Result types
match money_a.checked_add
// Safe operations with different currencies (won't compile due to type safety)
let eur_money = new.unwrap;
// This won't compile because USD and EUR are different types:
// let result = money_a + eur_money; // Compile error!
Components
This library provides these main components to work with money:
Money<C>: represents the money itself and all operations on it. Generic over currency typeC.Currency: trait that defines currency behavior and metadata. Implemented by currency marker types (e.g.,USD,EUR,JPY).Decimal: 128 bit floating-point with fixed-precision decimal number. Re-export from rust_decimal represents main type for money's amount.BaseMoney: trait of money providing core operations and accessors.BaseOps: trait for arithmetic and comparison operations on money.IterOps: trait with blanket implementations for checked_sum, mean, median, and mode.CustomMoney: trait for custom formatting and rounding operations on money.RoundingStrategy: enum defining rounding strategies (BankersRounding, HalfUp, HalfDown, Ceil, Floor).MoneyError: enum of possible errors that can occur in money operations.
Money<C> and Decimal are Copy types so they can be passed around freely without having to worry about borrow checker.
Currency marker types are zero-sized types (ZST) for compile-time type safety.
Invariants
Monetary values are sensitive matter and their invariants must always hold true.
Decimal
- Significand(m): -296 < m < 296
- Decimal points(s): 0 <= s <= 28
Money
- Always rounded to its currency's minor unit using bankers rounding after each creation and operation done on it.
- Creating money from string only accepts currencies already defined in ISO 4217.
- Comparisons: Currency type-safety is enforced at compile time. Operations between different currencies won't compile.
- Arithmetics:
- *,+,-: will PANIC if overflowed. Currency mismatches are prevented at compile time.
- /: will PANIC if overflowed or division by zero. Currency mismatches are prevented at compile time.
- Use methods in
BaseOpsfor non-panic arithmetics.
Currency
- Currency types are defined at compile time using marker types (e.g.,
USD,EUR,JPY). - All ISO 4217 currencies are supported via the
Currencytrait. - Currency information is available through trait methods:
code(),symbol(),name(),minor_unit(). - New/custom currency is supported by implementing
Currencytrait.
This library maintains type-safety by preventing invalid state either by returning Result or going PANIC.
Feature Flags
raw_money
Enables the RawMoney<C> type which doesn't do automatic rounding like Money<C> does.
It keeps full decimal precision and lets callers decide when to round.
[]
= { = "...", = ["raw_money"] }
use ;
// RawMoney preserves all decimal precision
let raw = new.unwrap;
assert_eq!; // Not rounded!
// Convert from Money using into_raw()
let money = new.unwrap;
let raw = money.into_raw;
// Perform precise calculations
let result = raw * dec!; // Apply tax
// Convert back to Money with rounding using finish()
let final_money = result.finish;
Where rounding happens:
.round(): rounds to currency's minor unit using bankers rounding. ReturnsRawMoney..round_with(...): rounds using custom decimal points and strategy. ReturnsRawMoney..finish(): rounds to currency's minor unit using bankers rounding back toMoney.
serde
Enables serialization and deserialization for Money/RawMoney(raw_money) types.
By default it will serialize/deserialize as numbers from numbers or from string numbers.
If you want to serialize/deserialize as string money format with code or symbol, you can use provided serde interface inside serde module:
moneylib::serde::money::comma_str_code: Serialize into code format(e.g. "USD 1,234.56") with separators from currency's setting. Deserialize with code formatted with comma separated thousands.moneylib::serde::money::option_comma_str_code: Same as above, with nullability.moneylib::serde::money::comma_str_symbol: Serialize into symbol format(e.g. "$1,234.56") with separators from currency's setting. Deserialize with symbol formatted with comma separated thousands.moneylib::serde::money::option_comma_str_symbol: Same as above, with nullability.moneylib::serde::money::dot_str_code: Serialize into code format(e.g. "EUR 1.234,56") with separators from currency's setting. Deserialize with code formatted with dot separated thousands.moneylib::serde::money::option_dot_str_code: Same as above, with nullability.moneylib::serde::money::dot_str_symbol: Serialize into symbol format(e.g. "€1,234.56") with separators from currency's setting. Deserialize with symbol formatted with dot separated thousands.moneylib::serde::money::option_dot_str_symbol: Same as above, with nullability.
[]
= { = "...", = ["serde"] }
or serde for RawMoney:
[]
= { = "...", = ["serde", "raw_money"] }
use ;
use ;
let json_str = r#"
{
"amount_from_f64": 1234.56988,
"amount_from_i64": -1234,
"amount_from_u64": 18446744073709551615,
"amount_from_i128": -1844674407370955161588,
"amount_from_u128": 34028236692093846346337,
"amount_from_str": "1234.56",
"raw_amount_from_f64": -1004.1234,
"raw_amount_from_str": "1230.4993",
"amount_from_str_comma_code": "USD 1,234.56",
"amount_from_str_comma_code_some": "USD 2,000.00",
"amount_from_str_comma_code_none": null,
"amount_from_str_comma_symbol": "$1,234.56",
"amount_from_str_comma_symbol_some": "$2,345.6799",
"amount_from_str_comma_symbol_none": null,
"raw_amount_from_str_comma_code": "USD -42.42424242",
"amount_from_str_dot_code": "EUR 1.234,5634",
"amount_from_str_dot_code_some": "EUR 2.000,00",
"amount_from_str_dot_code_none": null,
"amount_from_str_dot_symbol": "€1.234,56",
"amount_from_str_dot_symbol_some": "€2.345,67",
"amount_from_str_dot_symbol_none": null,
"raw_amount_from_str_dot_symbol": "-€69,69696969"
}
"#;
let all = ;
dbg!;
assert!;
let ret = all.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
// comma + code
assert_eq!;
assert!;
assert_eq!;
assert!;
assert!;
// comma + symbol
assert_eq!;
assert!;
// "$2,345.6799" -> rounded to 2 decimal places -> 2345.68
assert_eq!;
assert!;
assert_eq!;
assert!;
// dot + code (European formatting)
// "EUR 1.234,5634" -> 1234.5634 -> rounded to 1234.56 (third decimal is 3 -> round down)
assert_eq!;
assert!;
assert_eq!;
assert!;
assert!;
// dot + symbol
assert_eq!;
assert!;
assert_eq!;
assert!;
assert!;
assert_eq!;
locale
Enable locale formatting using ISO 639 lowercase language code, ISO 639 with ISO 3166-1 alpha‑2 uppercase region code, and also supports BCP 47 locale extensions.
[]
= { = "...", = ["locale"] }
or locale for RawMoney:
[]
= { = "...", = ["locale", "raw_money"] }
use ;
use dec;
use CustomMoney;
// English (US) locale: comma thousands separator, dot decimal separator
let money = new.unwrap;
assert_eq!;
// Arabic (Saudi Arabia) locale: Arabic-Indic numerals
let money = new.unwrap;
assert_eq!;
// Negative amount: include `n` in format_str to show the negative sign
let money = new.unwrap;
assert_eq!;
// Indian numbers and group formatting.
let money = -new.unwrap;
let result = money.format_locale_amount;
assert_eq!;
// Invalid locale returns an error
let money = new.unwrap;
assert!;
exchange
Enable currency conversion feature with exchange rates.
Main Components:
Exchange: Trait with blanket implementation for convert method for types implementingBaseMoney<C>.ExchangeRates: Struct containing list of exchange rates with base currency.
[]
= { = "...", = ["exchange"] }
or exchange for RawMoney:
[]
= { = "...", = ["exchange", "raw_money"] }
use ;
let money = new.unwrap;
let ret = money.;
assert_eq!;
let money = new.unwrap;
let ret = money.;
assert_eq!;
let money = from_decimal;
let ret = money.;
assert_eq!;
let raw_money = from_decimal;
let ret = raw_money.;
assert_eq!;
let money = new.unwrap;
let mut rates = default;
assert_eq!;
assert_eq!;
rates.set;
rates.set;
assert_eq!;
let ret = money.;
assert_eq!;
let ret = money.;
assert_eq!;
let rates = from;
assert_eq!;
assert_eq!;
assert_eq!;
let money = from_decimal;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
accounting
Contains several features:
- Interest calculations(FV, PV, PMT).
[]
= { = "...", = ["accounting"] }