finanza/currency
Currency and money types built on top of
finanza/decimal.
A Currency is a small record describing an ISO 4217
alpha-3 code together with display metadata. A
Money pairs a Decimal amount with a Currency so
arithmetic that crosses currencies is rejected with a typed error.
Both types are pub opaque. Build them through the smart
constructors new_currency and
new (for Money), or pick a catalogue value from
finanza/currency/catalog.
Types
A monetary unit identified by an ISO 4217 alpha-3 code.
pub opaque type Currency
Errors raised by currency and money operations.
pub type CurrencyError {
CurrencyMismatch(left: String, right: String)
InvalidExponent
InvalidCurrencyCode
EmptyRatios
NonPositiveRatio
EmptyList
ArithmeticError(error: decimal.ArithmeticError)
}
Constructors
-
CurrencyMismatch(left: String, right: String)Two operands had different currencies.
-
InvalidExponentexponentpassed tonew_currencywas out of the supported range (0–8). -
InvalidCurrencyCodecodepassed tonew_currencywas empty. -
EmptyRatiosallocatewas called with an empty ratio list. -
NonPositiveRatioallocatereceived a ratio that was zero or negative. -
EmptyListsumwas called with an empty list. The total has no well-defined currency in that case. Usesum_with_zerowhen the empty input should fold to zero in a known currency. -
ArithmeticError(error: decimal.ArithmeticError)A decimal operation overflowed the supported precision window. Inspect
errorfor the underlying decimal error.
Options passed to format. Build from
default_format and the with_* setters.
pub opaque type FormatOptions
How negative amounts are rendered in formatted output.
pub type NegativeStyle {
MinusSign
Parentheses
}
Constructors
-
MinusSign -
Parentheses
Where the currency symbol appears in formatted output.
pub type SymbolPosition {
Prefix
Suffix
NoSymbol
}
Constructors
-
Prefix -
Suffix -
NoSymbol
Values
pub fn add(
a a: Money,
b b: Money,
) -> Result(Money, CurrencyError)
Add two Money values. Both operands must share the same
currency.
pub fn allocate(
m m: Money,
ratios ratios: List(Int),
) -> Result(List(Money), CurrencyError)
Split a Money proportionally to ratios, distributing rounding
remainders to the first slots so the slices sum back to the
original amount exactly.
Zero ratios are accepted in a mixed list and mean “skip this
recipient” — allocate(bill, [1, 0, 1]) distributes the bill
across the first and third slots and assigns zero to the middle
slot, preserving every slot’s position in the result. Negative
ratios remain rejected with NonPositiveRatio, as does the
all-zero list (which has no positive share to distribute).
let bill = from_minor(units: 1000, currency: catalog.usd())
allocate(bill, [1, 1, 1])
// Ok([$3.34, $3.33, $3.33])
allocate(bill, [1, 0, 1])
// Ok([$5.00, $0.00, $5.00])
pub fn compare(
a a: Money,
b b: Money,
) -> Result(order.Order, CurrencyError)
Compare two money values. Both operands must share the same currency.
pub fn currency_of(m m: Money) -> Currency
The currency component of a Money. Named currency_of to avoid
a currency.currency(m) call site, which reads awkwardly given
the module name.
pub fn default_format() -> FormatOptions
Default FormatOptions: symbol prefix, ,
thousands separator, . decimal separator, leading minus sign,
no currency code suffix, and the amount is rescaled to the
currency’s minor-unit exponent (so USD always renders with two
cents, JPY with none, etc.).
pub fn divide(
m m: Money,
divisor divisor: decimal.Decimal,
mode mode: rounding.Mode,
) -> Result(Money, CurrencyError)
Divide a money value by a scalar, rounding the result to the
currency’s minor-unit exponent using mode.
pub fn equal(a a: Money, b b: Money) -> Bool
Equality test for two money values. Returns False rather than an
error on currency mismatch, mirroring ==.
pub fn exponent(c c: Currency) -> Int
Minor-unit exponent (USD = 2, JPY = 0, BHD = 3, etc.).
pub fn format(
m m: Money,
options options: FormatOptions,
) -> String
Render a money value with the given FormatOptions.
By default the amount is rescaled to the currency’s minor-unit
exponent (so 12 renders as `12.00and ¥1234 as¥1,234). Call [with_minor_units](#with_minor_units) with False` to preserve
the amount’s original precision.
pub fn from_major(
amount amount: Int,
currency currency: Currency,
) -> Money
Build a Money from an integer number of major units — the
whole-currency amount a human would read off a price tag.
from_major(amount: 35, currency: catalog.usd()) represents $35,
and from_major(amount: 3500, currency: catalog.jpy()) represents
¥3,500. Both calls produce the same kind of value regardless of
the currency’s exponent, so callers do not have to pre-multiply
by 10 ^ exponent to compensate (the silent off-by-100× footgun
from_minor has on two-exponent currencies like USD or EUR when
the input is actually a major-unit integer).
Use from_minor instead when you already hold a minor-unit
integer — for example, a value loaded from a cents column in a
database.
pub fn from_minor(
units units: Int,
currency currency: Currency,
) -> Money
Build a Money from an integer number of minor units. The
resulting amount has exponent -currency.exponent.
from_minor(units: 1234, currency: catalog.usd()) represents
$12.34.
pub fn multiply(
m m: Money,
factor factor: decimal.Decimal,
) -> Result(Money, CurrencyError)
Multiply a money value by a scalar.
The product is rescaled to the currency’s minor-unit exponent
using HalfEven so the resulting Money renders with exactly
the digits the currency supports — JPY 100 × 0.5 → JPY 50,
USD 100.00 × 0.5 → USD 50.00, JPY 100 × 0.001 → JPY 0.
Use decimal.multiply directly when you need the unrescaled
product (e.g. interest-rate calculations that downstream of
the multiply will rescale themselves).
pub fn new_currency(
code code: String,
exponent exponent: Int,
symbol symbol: String,
name name: String,
) -> Result(Currency, CurrencyError)
Build a custom Currency. Use this when the desired
currency is outside the static catalogue.
code must be a non-empty string. exponent is the number of
minor-unit digits and must be in the range 0–8.
pub fn new_money(
amount amount: decimal.Decimal,
currency currency: Currency,
) -> Money
Build a Money from a Decimal and a
Currency. Named new_money rather than new to
keep symmetry with new_currency and to avoid
the surprise of currency.new returning the other type from
the module’s name.
pub fn subtract(
a a: Money,
b b: Money,
) -> Result(Money, CurrencyError)
Subtract b from a.
pub fn sum(
items items: List(Money),
) -> Result(Money, CurrencyError)
Sum a non-empty list of Money values, all of which must share
the same currency.
Removes the per-call-site list.fold + nested case + propagate CurrencyError boilerplate that totalling line items, projecting
event-sourced balances, and computing batch subtotals all require
today.
Empty input has no well-defined currency, so returns
Error(EmptyList). Use sum_with_zero when an
empty list should fold to zero in a caller-supplied currency.
Mixed-currency input returns Error(CurrencyMismatch(..)) at the
first divergence.
pub fn sum_with_zero(
items items: List(Money),
fallback fallback: Money,
) -> Result(Money, CurrencyError)
Sum a list of Money values, falling back to fallback when the
list is empty. fallback’s currency is also the expected currency
for every element; the first divergence returns
Error(CurrencyMismatch(..)).
Use this entry point when the empty case is a meaningful “no
activity” outcome in a known currency — e.g. summing a customer’s
daily transactions, where an empty day means from_minor(0, account_currency) rather than an error.
pub fn to_minor(
m m: Money,
mode mode: rounding.Mode,
) -> Result(Int, CurrencyError)
Convert a Money back to its minor-unit integer count, rounding
to the currency’s exponent using mode.
pub fn to_string(m m: Money) -> String
Render a money value using the default ISO-style format:
"USD 1234.56".
pub fn with_currency_code(
options options: FormatOptions,
enabled enabled: Bool,
) -> FormatOptions
Toggle whether the ISO code is appended to the rendered string.
pub fn with_decimal_separator(
options options: FormatOptions,
separator separator: String,
) -> FormatOptions
Override the decimal separator.
pub fn with_minor_units(
options options: FormatOptions,
enabled enabled: Bool,
) -> FormatOptions
Toggle whether the amount is rescaled to the currency’s
minor-unit exponent before rendering. Defaults to True; pass
False to preserve the amount’s original precision (useful when
the value carries finer-than-minor digits, e.g. for an FX rate
or a unit price).
pub fn with_negative_style(
options options: FormatOptions,
style style: NegativeStyle,
) -> FormatOptions
Override the negative-amount style.
pub fn with_symbol_position(
options options: FormatOptions,
position position: SymbolPosition,
) -> FormatOptions
Override the symbol position.
pub fn with_thousands_separator(
options options: FormatOptions,
separator separator: String,
) -> FormatOptions
Override the thousands separator.