finanza/decimal

Fixed-point decimal arithmetic with explicit rounding.

A Decimal is represented internally as a signed integer coefficient and an exponent:

value = coefficient × 10^exponent

Decimal is pub opaque; construct values through from_int, from_string, or new, and inspect them through coefficient and exponent.

Precision boundary

On the JavaScript target, Gleam’s Int is a 64-bit IEEE 754 number, so coefficients are limited to ±(2^53 − 1) = 9_007_199_254_740_991. Operations that would produce a larger coefficient return PrecisionExceeded. On the Erlang target, integers are arbitrary precision; the same bound is enforced anyway so behaviour is consistent across targets.

Types

Errors returned by arithmetic operations.

pub type ArithmeticError {
  DivisionByZero
  PrecisionExceeded
}

Constructors

  • DivisionByZero

    The right-hand operand of a divide was zero.

  • PrecisionExceeded

    The result would not fit in the supported precision window (±9_007_199_254_740_991). Reduce intermediate precision with round and retry.

Fixed-point decimal value. Construct via from_int, from_string, or new.

pub opaque type Decimal

Errors returned by from_string.

pub type ParseError {
  EmptyInput
  InvalidCharacter(char: String, position: Int)
  MultipleDecimalPoints
  MultipleSigns
  NoDigits
  ParsedValueTooLarge
}

Constructors

  • EmptyInput

    The input was the empty string or contained only whitespace.

  • InvalidCharacter(char: String, position: Int)

    The input contained a character that is not a digit, sign, or decimal point.

  • MultipleDecimalPoints

    The input contained more than one decimal point.

  • MultipleSigns

    The input contained more than one sign character.

  • NoDigits

    The input contained no digits (e.g. "+", ".", "-.").

  • ParsedValueTooLarge

    The parsed coefficient would exceed ±9_007_199_254_740_991 (the JavaScript-safe integer ceiling). Such a value cannot be represented faithfully on the JavaScript target; rather than silently corrupt it (and emit unparseable strings from to_string), parsing fails fast.

Values

pub fn absolute(d d: Decimal) -> Decimal

Absolute value. Always safe.

pub fn add(
  a a: Decimal,
  b b: Decimal,
) -> Result(Decimal, ArithmeticError)

Add two decimals.

When one operand is zero the result is just the other operand — we short-circuit without going through align. Without the short-circuit, align would try to scale the smaller-exponent operand up to the larger’s exponent (e.g. 1 × 10^20 for new(1, 20) + zero()), which can exceed max_safe_coefficient and surface a spurious PrecisionExceeded despite the mathematical result fitting trivially. When both operands are zero we still return zero, but with the smaller of the two exponents so that add(a, b) == add(b, a) holds at the level of structural equality (the same invariant align provided before).

pub fn coefficient(d d: Decimal) -> Int

The signed coefficient component.

pub fn compare(a a: Decimal, b b: Decimal) -> order.Order

Total ordering. Two values with the same numeric value compare as equal even when their exponents differ.

pub fn divide(
  a a: Decimal,
  b b: Decimal,
  digits digits: Int,
  mode mode: rounding.Mode,
) -> Result(Decimal, ArithmeticError)

Divide a by b, returning a result rounded to digits decimal places using mode.

Returns DivisionByZero when b is zero, or PrecisionExceeded when the intermediate representation would exceed ±9_007_199_254_740_991.

pub fn equal(a a: Decimal, b b: Decimal) -> Bool

Equality test by numeric value, not by representation. equal(new(coefficient: 100, exponent: -2), one()) is True.

pub fn exponent(d d: Decimal) -> Int

The exponent component (base 10).

pub fn format(
  d d: Decimal,
  thousands thousands: String,
  decimal_separator decimal_separator: String,
) -> String

Render a Decimal with custom thousands and decimal separators.

format(d, thousands: ",", decimal_separator: ".")  // "1,234.56"
format(d, thousands: ".", decimal_separator: ",")  // "1.234,56" (German)
format(d, thousands: "",  decimal_separator: ".")  // "1234.56"

Equivalent to to_string when thousands is empty and decimal_separator is ".".

pub fn from_int(n n: Int) -> Decimal

Build a Decimal from an integer.

pub fn from_string(
  input input: String,
) -> Result(Decimal, ParseError)

Parse a decimal from a string. Accepts an optional leading + or -, decimal digits, and at most one . separator. Scientific notation is not supported.

from_string("3.14")    // Ok(Decimal with coefficient=314, exponent=-2)
from_string("-0.5")    // Ok(Decimal with coefficient=-5, exponent=-1)
from_string("")        // Error(EmptyInput)
from_string("1.2.3")   // Error(MultipleDecimalPoints)
pub fn is_negative(d d: Decimal) -> Bool

Test for a strictly negative decimal.

pub fn is_positive(d d: Decimal) -> Bool

Test for a strictly positive decimal.

pub fn is_zero(d d: Decimal) -> Bool

Test for the zero decimal.

pub fn multiply(
  a a: Decimal,
  b b: Decimal,
) -> Result(Decimal, ArithmeticError)

Multiply two decimals.

pub fn negate(d d: Decimal) -> Decimal

Negate the value. Always safe (the coefficient sign flips but magnitude does not change).

pub fn new(
  coefficient coefficient: Int,
  exponent exponent: Int,
) -> Decimal

Build a Decimal directly from a coefficient and exponent.

new(coefficient: 1234, exponent: -2) represents 12.34.

pub fn one() -> Decimal

The decimal value 1.

pub fn rescale(
  d d: Decimal,
  target_exponent target_exponent: Int,
  mode mode: rounding.Mode,
) -> Result(Decimal, ArithmeticError)

Force the decimal to a specific exponent. When the new exponent is finer (smaller), the coefficient grows by zero-padding (may overflow). When the new exponent is coarser (larger), digits are dropped using mode.

pub fn round(
  d d: Decimal,
  digits digits: Int,
  mode mode: rounding.Mode,
) -> Decimal

Round to digits decimal places. When the input is already at equal or coarser precision, the original Decimal is returned unchanged (no zero-padding); call rescale to force a target exponent.

pub fn subtract(
  a a: Decimal,
  b b: Decimal,
) -> Result(Decimal, ArithmeticError)

Subtract b from a.

pub fn to_int(d d: Decimal) -> Result(Int, ArithmeticError)

Convert to a plain integer.

Succeeds only when d is exactly integer-valued — no fractional part and the resulting integer fits within ±max_safe_coefficient. Both a non-zero fractional remainder and a coefficient overflow produce PrecisionExceeded. Use to_int_truncated when the fractional part should be dropped toward zero, or to_int_rounded when it should be rounded using a specific rounding.Mode.

to_int(from_int(7))                 // Ok(7)
let assert Ok(d) = from_string("12.34")
to_int(d)                           // Error(PrecisionExceeded)
let assert Ok(d) = from_string("12.00")
to_int(d)                           // Ok(12)
pub fn to_int_rounded(
  d d: Decimal,
  mode mode: rounding.Mode,
) -> Result(Int, ArithmeticError)

Round d to an integer using mode and return that integer.

mode is applied as if rounding to zero decimal places — so to_int_rounded(d, mode: rounding.HalfEven) is the natural fit for the “I rounded to N decimals, now give me the integer” workflow. Returns PrecisionExceeded only when the rounded integer cannot fit within ±max_safe_coefficient.

pub fn to_int_truncated(
  d d: Decimal,
) -> Result(Int, ArithmeticError)

Truncate d toward zero (rounding.Down) and return the integer.

Drops the fractional part regardless of its size, so -1.9 becomes -1 and 1.9 becomes 1. Use to_int_rounded when a different rounding mode is needed. Returns PrecisionExceeded only when the truncated integer cannot fit within ±max_safe_coefficient (a fractional part on its own never causes a failure here, unlike to_int).

pub fn to_string(d d: Decimal) -> String

Render a Decimal as a plain string. Preserves the encoded exponent (new(coefficient: 100, exponent: -2) renders as "1.00", not "1").

pub fn truncate(d d: Decimal, digits digits: Int) -> Decimal

Truncate to digits decimal places (rounding toward zero). When the input is already at equal or coarser precision, the original Decimal is returned unchanged.

pub fn zero() -> Decimal

The decimal value 0.

Search Document