glecimal/money

An amount of money: a decimal held at exactly its currency’s minor units, so BRL 10.50 always has two decimals and JPY 1050 none.

Money never mixes currencies (adding BRL to USD is CurrencyMismatch), its own arithmetic is exact, and a value with nonzero digits beyond the currency’s minor units comes in only through new_rounded, multiply or convert, each with a named rounding. Allocation works in minor units, so the parts always add up to the whole.

import glecimal.{HalfEven}
import glecimal/currency
import glecimal/money

pub fn invoice() -> Result(List(money.Money), money.MoneyError) {
  use price <- result.try(money.from_minor_units(19_990, in: currency.brl))
  use rate <- result.try(
    glecimal.parse("0.0725") |> result.map_error(money.InvalidAmount),
  )
  use tax <- result.try(money.multiply(price, by: rate, rounding: HalfEven))
  use total <- result.try(money.add(price, tax))
  money.split(total, into: 3)
}

Types

An amount at its currency’s scale.

pub opaque type Money

Why a money operation has no result.

pub type MoneyError {
  CurrencyMismatch(
    expected: currency.Currency,
    found: currency.Currency,
  )
  Arithmetic(glecimal.ArithmeticError)
  InvalidAmount(glecimal.ParseError)
  UnknownCurrencyCode(code: String)
  MalformedText
}

Constructors

  • CurrencyMismatch(
      expected: currency.Currency,
      found: currency.Currency,
    )

    The operation combined two currencies.

  • The arithmetic or allocation failed, for example WouldRound when an amount has nonzero digits beyond its currency’s minor units.

  • InvalidAmount(glecimal.ParseError)

    parse read an amount it could not accept; positions count from the start of the whole text.

  • UnknownCurrencyCode(code: String)

    parse or decoder read a code that is not in ISO 4217.

  • MalformedText

    parse read a text that is not a code, one space and an amount.

Values

pub fn absolute_value(money: Money) -> Money
pub fn add(a: Money, b: Money) -> Result(Money, MoneyError)
pub fn allocate(
  money: Money,
  weights weights: List(glecimal.Decimal),
) -> Result(List(Money), MoneyError)

Splits the amount in proportion to weights, in minor units, so the parts add up to it exactly; see glecimal.allocate.

pub fn amount(money: Money) -> glecimal.Decimal

The amount, at the currency’s scale.

pub fn compare(
  a: Money,
  b: Money,
) -> Result(order.Order, MoneyError)

Compares two amounts in the same currency.

pub fn convert(
  money: Money,
  to target: currency.Currency,
  at rate: glecimal.Decimal,
  rounding rounding: glecimal.Rounding,
) -> Result(Money, MoneyError)

money in another currency at rate, the units of the target currency per unit of money’s, rounded once to the target’s minor units: BRL 199.90 converted to currency.usd at 0.1834 is USD 36.66.

pub fn currency(money: Money) -> currency.Currency
pub fn decoder() -> decode.Decoder(Money)

Reads {"amount": "1234.50", "currency": "BRL"}: the amount as a string (numbers are refused) and the code in ISO 4217. A refusal names its reason, at the path of the field that caused it.

pub fn describe_error(error: MoneyError) -> String

A sentence for logs and messages.

pub fn format(
  money: Money,
  notation: notation.Notation,
) -> String

The amount for display, in notation and without a currency symbol: format(price, notation.pt_br) is "1.234,50" for BRL 1234.50.

pub fn from_minor_units(
  units: Int,
  in currency: currency.Currency,
) -> Result(Money, MoneyError)

An amount counted in minor units: from_minor_units(1050, in: currency.brl) is BRL 10.50.

pub fn is_negative(money: Money) -> Bool
pub fn is_positive(money: Money) -> Bool
pub fn is_zero(money: Money) -> Bool
pub fn multiply(
  money: Money,
  by factor: glecimal.Decimal,
  rounding rounding: glecimal.Rounding,
) -> Result(Money, MoneyError)

money × factor (a quantity, a rate, an exchange rate into the same currency), rounded once to the currency’s minor units, whatever the factor’s scale.

pub fn negate(money: Money) -> Money
pub fn new(
  amount: glecimal.Decimal,
  in currency: currency.Currency,
) -> Result(Money, MoneyError)

amount in currency, exactly. Zeros are added up to the currency’s minor units; a nonzero digit beyond them is Arithmetic(WouldRound).

pub fn new_rounded(
  amount: glecimal.Decimal,
  in currency: currency.Currency,
  rounding rounding: glecimal.Rounding,
) -> Result(Money, MoneyError)

amount in currency, rounded to the currency’s minor units.

pub fn parse(text: String) -> Result(Money, MoneyError)

Reads the form to_string writes. Zeros are added up to the currency’s minor units, so BRL 5 is BRL 5.00; a nonzero digit beyond them is Arithmetic(WouldRound).

pub fn split(
  money: Money,
  into parts: Int,
) -> Result(List(Money), MoneyError)

Splits the amount into parts equal shares, in minor units.

pub fn subtract(a: Money, b: Money) -> Result(Money, MoneyError)
pub fn sum(
  amounts: List(Money),
  in currency: currency.Currency,
) -> Result(Money, MoneyError)

The total of amounts in currency; sum([], in: currency) is zero.

pub fn to_minor_units(money: Money) -> Result(Int, MoneyError)

The amount counted in minor units: BRL 10.50 is 1050.

pub fn to_string(money: Money) -> String

The canonical form for logs and audit trails: the code, one space and the amount, as in BRL 1234.50. For display, format amount with glecimal/notation.

pub fn zero(in currency: currency.Currency) -> Money

Zero in currency, at its scale: zero(in: currency.brl) is BRL 0.00.

✨ Search Document