Exact money and currency arithmetic for MoonBit: ISO 4217 minor units, allocation without losing a cent, tax, cash rounding, parsing and formatting
$ moon run cmd/main -- tax 10000 USD 19%
net: $100.00
rate: 19%
tax: $19.00
gross: $119.00
$ moon run cmd/main -- split 10000 USD 3
1: $33.34
2: $33.33
3: $33.33
total: $100.00
$ moon run cmd/main -- parse "CA$12.34"
CAD 12.34
minor units: 1234
currency: CAD (Canadian Dollar)# use it from another project
moon add haol-05/moonmoney
# work on this repository
moon check --deny-warn --target all
moon build --target all
moon test --deny-warn --target all
moon run examples/demo # a self-contained tour, no arguments
moon run cmd/main -- parse "USD 1,234.56"| Area | Details |
|---|---|
| Registry | 165 ISO 4217 currencies with a defined minor unit: alphabetic and numeric code, minor units, CLDR display digits, cash digits and cash step, English name, symbol. Currency::custom covers everything else. |
| Amounts | Money is { minor: Int64, currency: Currency }. Build from minor units, whole units, or units plus a fraction; read back whole units and the fraction; compare, negate, take the absolute value. |
| Arithmetic | Add, subtract, multiply by a whole number, divide with a rounding mode. Combining two currencies is an error, not a conversion. Overflow is reported, never wrapped. |
| Rounding | HalfUp, HalfDown, HalfEven, Up, Down, Ceiling, Floor, plus rounding to an arbitrary step and to a currency's own cash step (CHF to 0.05, DKK to 0.50). |
| Percentages | Percent is an exact number of basis points. Apply a percentage, add it (net to gross) or subtract it (a discount). |
| Allocation | Split an amount into equal, weighted or percentage shares with the largest-remainder method: the shares always sum to the original and no two differ by more than one minor unit. |
| Tax | Tax on a net amount, the net recovered from a gross amount, and one line per rate. |
| Exchange | A rate is an exact scaled integer; conversion multiplies in arbitrary precision and rounds once. |
| Parsing | Codes and disambiguated symbols, both separator conventions, signs, trailing signs, accounting parentheses. |
| Formatting | Symbol, code or bare; grouping by threes or the Indian 3-2 pattern; twenty locale presets; minus, parentheses or trailing minus. |
// A net invoice line and its tax. Every call that can fail says so in its type.
fn invoice_line(net_minor : Int64, rate_text : String) -> String raise @moonmoney.MoneyError {
let usd = @moonmoney.Currency::find("USD").unwrap()
let net = @moonmoney.Money::of_minor(net_minor, usd)
let rate = @moonmoney.Percent::parse(rate_text)
let gross = net.with_tax(rate)
"\{@format.format(net)} + \{rate.text()} = \{@format.format(gross)}"
}// Splitting a bill without losing a cent.
let total = @moonmoney.Money::of_minor(10000L, usd) // USD 100.00
let shares = total.allocate(3) // [33.34, 33.33, 33.33]
let weighted = total.allocate_ratios([3L, 2L, 1L]) // [50.00, 33.33, 16.67]// Reading amounts that were written the way people write them.
let a = @parse.amount("USD 1,234.56") // USD 1234.56
let b = @parse.amount("1.234,56 €") // EUR 1234.56, the continental reading
let c = @parse.amount("CA$12.34") // CAD 12.34
let d = @parse.amount_in("(5.00)", usd) // -USD 5.00, accounting negative@format.format(money) // $1,234.56
@format.format(money, style=@format.MoneyStyle::code_first()) // USD 1,234.56
@format.format(money, style=@format.MoneyStyle::plain()) // 1234.56
@format.format(money, style=@format.MoneyStyle::accounting()) // ($1,234.56) when negative
@format.MoneyStyle::for_locale("de-DE") // 1.234,56 $
@format.MoneyStyle::for_locale("en-IN") // ₹1,23,456.78node tools/generate_currency_table.mjsmoonmoney/ the core: Currency and its table, Money, rounding,
percentages, allocation, tax, exchange
parse/ text to Money
format/ Money to text, with the locale presets
cmd/main/ the command line
examples/demo/ a runnable tour
tools/ the table generatorpub(all) suberror MoneyError {
DivisionByZero
Overflow(String)
CurrencyMismatch(String, String)
UnknownCurrency(String)
AmbiguousCurrency(String, String)
InvalidAmount(String, String)
EmptyAllocation(String)
EmptyRatio
} derive(Debug)fn Money::add_percent(self : Money, percent : Percent, mode? : RoundingMode) -> Money raise MoneyErrorfn Money::convert(self : Money, target : Currency, rate : Rate, mode? : RoundingMode) -> Money raise MoneyErrorfn Money::net_from_gross(gross : Money, rate : Percent, mode? : RoundingMode) -> (Money, Money) raise MoneyErrorfn Money::sub_percent(self : Money, percent : Percent, mode? : RoundingMode) -> Money raise MoneyErrorfn Money::tax_lines(self : Money, rates : Array[Percent], mode? : RoundingMode) -> Array[TaxLine] raise MoneyErrorfn Percent::of_ratio(numerator : Int64, denominator : Int64, mode? : RoundingMode) -> Percent raise MoneyErrorfn mul_div_round(left : Int64, right : Int64, divisor : Int64, mode : RoundingMode) -> Int64 raise MoneyErrorfn pow10(exponent : Int) -> Int64?Install
Download zipExact money and currency arithmetic for MoonBit: ISO 4217 minor units, allocation without losing a cent, tax, cash rounding, parsing and formatting