ESC
Type to search...
S
Soli Docs

Money Class

Currency-aware amounts over decimals, stored as a plain hash so they round-trip through JSON and templates like any other data. All operations are immutable — they return new money hashes.

Money.new(amount, currency)

Build from Int, Float, Decimal, or String. Strings use . for decimals and _ for grouping; a comma is rejected as ambiguous. Currency is a 3-letter ISO code; minor-unit exponents follow ISO-4217 (JPY 0, KWD 3, most others 2).

let price = Money.new("49.90", "EUR");
price["amount"];    # Decimal 49.90
price["currency"];  # "EUR"

Arithmetic and comparison

add / sub combine two money values (currency mismatch is an error); mul scales by a plain scalar; compare returns -1, 0, or 1. Every result is quantized to the currency’s minor units (half-away-from-zero), so m["amount"] is always exactly what Money.format(m) shows and repeated mul/add cannot drift away from it.

let total = Money.add(price, Money.mul(price, 2));   # 149.70 EUR
Money.compare(total, Money.new("149.70", "EUR"));    # 0

Money.allocate(m, ratios)

Split an amount losslessly by ratios using largest-remainder allocation — no lost cents.

let shares = Money.allocate(Money.new(100, "EUR"), [1, 1, 1]);
shares.map(fn(m) Money.format(m, { "symbol": false }));
# ["33.34 EUR", "33.33 EUR", "33.33 EUR"]

Money.format(m, opts?)

Localized rendering. symbol omits the currency glyph; locale switches decimal separator and grouping — de, fr and nl group with . and use , as the decimal separator, en the reverse. Currencies without a known glyph render as a code suffix. Currency codes are case-insensitive.

Money.format(Money.new(1234.5, "EUR"));
# "1,234.50 €"

Money.format(Money.new(1234.5, "EUR"), { "locale": "de" });
# "1.234,50 €"

Money.format(Money.new(-9.99, "USD"), { "symbol": false });
# "-9.99 USD"