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
Errors returned by validated constructors
(try_new, try_from_int).
pub type ConstructError {
CoefficientTooLarge
}
Constructors
-
CoefficientTooLargeThe supplied coefficient — or the value implied once the exponent is applied (
|coefficient| × 10^exponentforexponent ≥ 0) — would exceed±9_007_199_254_740_991(the JavaScript-safe integer ceiling). Such a value cannot round-trip throughto_stringandfrom_string, so construction fails fast rather than producing aDecimalthe library cannot read back.
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
-
EmptyInputThe 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.
-
MultipleDecimalPointsThe input contained more than one decimal point.
-
MultipleSignsThe input contained more than one sign character.
-
NoDigitsThe input contained no digits (e.g.
"+",".","-."). -
ParsedValueTooLargeThe 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 fromto_string), parsing fails fast.
Values
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 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 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.
Panics if |n| > 9_007_199_254_740_991 (such a coefficient
cannot round-trip through to_string and
from_string). Use try_from_int
when the input is supplied by a caller and might exceed the
safe range — that variant returns a Result instead of
panicking.
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 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.
Panics if the implied rendered value exceeds the safe range
(|coefficient| > 9_007_199_254_740_991, or — for non-negative
exponent — |coefficient| × 10^exponent > 9_007_199_254_740_991).
Use try_new when the inputs are supplied by a
caller and might exceed the safe range — that variant returns
a Result instead of panicking.
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, trim only. When the input
is already at equal or coarser precision than -digits (e.g.
Decimal(coefficient: 2000, exponent: 0) against digits: 2),
the original Decimal is returned unchanged — round never
pads with zeros, so to_string(round(from_int(2000), 2, _)) is
"2000", not "2000.00".
Use rescale when the result must always have
exponent -digits (i.e. the rendered form must always have
exactly digits decimal places, including trailing zeros) —
rescale returns Result because the padding direction can
overflow ±9_007_199_254_740_991.
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), trim
only. Like round, the input is returned unchanged
when it is already at equal or coarser precision than -digits.
pub fn try_from_int(n n: Int) -> Result(Decimal, ConstructError)
Build a Decimal from an integer, returning a Result.
Returns Error(CoefficientTooLarge) when
|n| > 9_007_199_254_740_991, which is the threshold above
which the resulting Decimal cannot round-trip through
to_string and from_string.
pub fn try_new(
coefficient coefficient: Int,
exponent exponent: Int,
) -> Result(Decimal, ConstructError)
Build a Decimal from a coefficient and exponent, returning a
Result.
Returns Error(CoefficientTooLarge) when the rendered value
would overflow the safe range — either because
|coefficient| > 9_007_199_254_740_991, or because a positive
exponent would push the rendered integer
(|coefficient| × 10^exponent) past that bound. Such a value
cannot round-trip through to_string and
from_string.