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.

  • InvalidExponent

    exponent passed to new_currency was out of the supported range (0–8).

  • InvalidCurrencyCode

    code passed to new_currency was empty.

  • EmptyRatios

    allocate was called with an empty ratio list.

  • NonPositiveRatio

    allocate received a ratio that was zero or negative.

  • EmptyList

    sum was called with an empty list. The total has no well-defined currency in that case. Use sum_with_zero when the empty input should fold to zero in a known currency.

  • ArithmeticError(error: decimal.ArithmeticError)

    A decimal operation overflowed the supported precision window. Inspect error for the underlying decimal error.

Options passed to format. Build from default_format and the with_* setters.

pub opaque type FormatOptions

An amount denominated in a particular Currency.

pub opaque type Money

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 amount(m m: Money) -> decimal.Decimal

The amount component of a Money.

pub fn code(c c: Currency) -> String

ISO 4217 alpha-3 code (e.g. "USD").

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 name(c c: Currency) -> String

English-language name of the currency.

pub fn negate(m m: Money) -> Money

Negate a money value.

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 symbol(c c: Currency) -> String

Display symbol (e.g. "$", "¥").

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.

Search Document