dee/decimal
Arbitrary precision decimal arithmetic for Gleam.
A pure Gleam implementation of
Elixir’s Decimal library, providing the
Decimal opaque type with arithmetic, comparison, conversion, and
rounding operations. Works identically on both Erlang and JavaScript
targets using a single codebase.
A decimal number is represented as sign * coefficient * 10 ^ exponent
where the coefficient is an arbitrary precision integer. This avoids the
rounding errors inherent to floating-point arithmetic.
Examples
// Floating point: 0.1 + 0.2 = 0.30000000000000004
// Decimal: 0.1 + 0.2 = 0.3
let assert Ok(a) = from_string("0.1")
let assert Ok(b) = from_string("0.2")
to_string(add(a, b))
// -> "0.3"
let assert Ok(price) = from_string("19.99")
let assert Ok(tax_rate) = from_string("0.08")
let tax = multiply(price, tax_rate)
to_string(tax)
// -> "1.5992"
Elixir Decimal Compatibility
This library follows Elixir’s Decimal as its reference implementation. The API, arithmetic behavior, rounding modes, and special value handling are designed to match. Code ported between the two libraries should produce identical results.
Both libraries use the same mathematical representation internally
(sign * coefficient * 10^exponent) with the same semantics for
NaN, Infinity, and signed zero. The default context matches
Elixir’s: 28 digits of precision with HalfUp rounding.
While the runtime representations differ (Gleam opaque type vs Elixir
struct), converting between them on the BEAM is straightforward via
to_string/from_string or by mapping the component fields through
from_parts, sign, and exponent.
Types
Configuration for context-dependent operations (primarily division).
pub type Context {
Context(precision: Int, rounding: RoundingMode)
}
Constructors
-
Context(precision: Int, rounding: RoundingMode)
The core decimal type representing an arbitrary-precision decimal number. Mathematical value = sign * coefficient * 10^exponent
Examples:
- 123.45 → Decimal(Positive, Finite(12345), -2)
- -0.001 → Decimal(Negative, Finite(1), -3)
- 42 → Decimal(Positive, Finite(42), 0)
- +Inf → Decimal(Positive, Infinity, 0)
- NaN → Decimal(Positive, NaN, 0)
pub opaque type Decimal
Enumeration of supported rounding strategies.
pub type RoundingMode {
Down
Up
Ceiling
Floor
HalfUp
HalfEven
HalfDown
}
Constructors
-
DownTruncate toward zero
-
UpRound away from zero
-
CeilingRound toward positive infinity()
-
FloorRound toward negative infinity()
-
HalfUpRound half away from zero (standard rounding)
-
HalfEvenRound half to nearest even (banker’s rounding)
-
HalfDownRound half toward zero
Values
pub fn absolute_value(a: Decimal) -> Decimal
Returns the absolute value of the given number. Sets the number’s sign to positive.
Examples
absolute_value(from_int(1)) |> to_string
// -> "1"
absolute_value(from_int(-1)) |> to_string
// -> "1"
pub fn add(a: Decimal, b: Decimal) -> Decimal
Adds two numbers together.
Examples
let assert Ok(a) = from_string("0.1")
let assert Ok(b) = from_string("0.2")
add(a, b) |> to_string
// -> "0.3"
pub fn apply_context(d: Decimal, ctx: Context) -> Decimal
Applies the given context to a decimal, rounding to the specified
precision (significant digits) using the specified rounding mode.
Special values (NaN, Infinity) pass through unchanged.
Use default_context() for standard precision (28 digits, HalfUp).
Examples
apply_context(from_int(123), context(2, HalfUp)) |> to_string
// -> "1.2E+2"
apply_context(from_int(5), default_context()) |> to_string
// -> "5"
pub fn compare(
a: Decimal,
b: Decimal,
) -> Result(order.Order, Nil)
Compares two numbers numerically. Returns Ok(order.Lt), Ok(order.Eq),
or Ok(order.Gt). Returns Error(Nil) if either operand is NaN since
NaN is not ordered.
Examples
compare(from_int(1), from_int(2))
// -> Ok(order.Lt)
compare(nan(), from_int(1))
// -> Error(Nil)
pub fn compare_with_threshold(
a: Decimal,
b: Decimal,
threshold: Decimal,
) -> Result(order.Order, Nil)
Compares two numbers numerically with a threshold tolerance. If the
second number is within the range of a - threshold to a + threshold,
the numbers are considered equal. Returns Error(Nil) for negative
thresholds or NaN operands.
Examples
let assert Ok(a) = from_string("1.1")
let assert Ok(t) = from_string("0.2")
compare_with_threshold(a, from_int(1), t)
// -> Ok(order.Eq)
pub fn context(precision: Int, rounding: RoundingMode) -> Context
Creates a new context with the given precision and rounding mode.
Examples
context(10, HalfEven)
// -> Context(precision: 10, rounding: HalfEven)
pub fn default_context() -> Context
The default context with precision 28 and HalfUp rounding. Matches Elixir Decimal’s default and IEEE decimal128.
pub fn divide(a: Decimal, b: Decimal) -> Decimal
Divides two numbers using the default context (precision 28, HalfUp
rounding). Division by zero returns NaN.
Examples
divide(from_int(3), from_int(4)) |> to_string
// -> "0.75"
pub fn divide_integer(a: Decimal, b: Decimal) -> Decimal
Divides two numbers and returns the integer part (truncated toward zero).
Division by zero returns NaN.
Examples
divide_integer(from_int(5), from_int(2)) |> to_string
// -> "2"
pub fn divide_remainder(
a: Decimal,
b: Decimal,
) -> #(Decimal, Decimal)
Integer division of two numbers and the remainder. Returns both the
quotient and remainder as a tuple. Should be used when both
divide_integer and remainder are needed. Division by zero returns
#(nan(), nan()).
Examples
let #(q, r) = divide_remainder(from_int(5), from_int(2))
to_string(q)
// -> "2"
to_string(r)
// -> "1"
pub fn divide_with_context(
a: Decimal,
b: Decimal,
ctx: Context,
) -> Decimal
Divides two numbers using the given context for precision and rounding.
Division by zero returns NaN.
Examples
let ctx = context(10, HalfEven)
divide_with_context(from_int(1), from_int(3), ctx) |> to_string
// -> "0.3333333333"
pub fn eq(a: Decimal, b: Decimal) -> Bool
Compares two numbers numerically and returns True if they are equal,
otherwise False. Numerical equality means 1.0 equals 1.00.
NaN is not equal to itself.
Examples
let assert Ok(a) = from_string("1.0")
eq(a, from_int(1))
// -> True
pub fn eq_with_threshold(
a: Decimal,
b: Decimal,
threshold: Decimal,
) -> Bool
Tests if two numbers are equal within a threshold tolerance. If the
second number is within the range of a - threshold to a + threshold,
returns True. Returns False for negative thresholds or NaN operands.
Examples
let assert Ok(a) = from_string("1.2")
let assert Ok(t) = from_string("0.2")
eq_with_threshold(a, from_int(1), t)
// -> True
pub fn exponent(d: Decimal) -> Int
Returns the exponent of the decimal number.
Examples
let assert Ok(d) = from_string("1.23")
exponent(d)
// -> -2
pub fn from_float(value: Float) -> Decimal
Creates a new decimal number from a floating point number.
Note that due to float representation, the result may not be exactly
the value you expect. Use from_string for exact decimal values.
Examples
from_float(3.14) |> to_string
// -> "3.14"
pub fn from_int(value: Int) -> Decimal
Creates a new decimal number from an integer. The decimal number will be created exactly as specified with all digits kept.
Examples
from_int(42) |> to_string
// -> "42"
from_int(-7) |> to_string
// -> "-7"
pub fn from_parts(s: Sign, coef: Int, exp: Int) -> Decimal
Creates a new decimal number from the sign, coefficient, and exponent
such that the number will be: sign * coefficient * 10 ^ exponent.
The coefficient is stored as its absolute value regardless of input.
Examples
from_parts(Positive, 42, 0) |> to_string
// -> "42"
from_parts(Negative, 12345, -2) |> to_string
// -> "-123.45"
pub fn from_string(input: String) -> Result(Decimal, String)
Parses a string into a decimal number. A decimal number will always be created exactly as specified with all digits kept.
Accepts integers, decimals, scientific notation, "Infinity", "-Infinity",
and "NaN". Returns Error with the invalid input string on failure.
Examples
from_string("3.14")
// -> Ok(...)
from_string("-Infinity")
// -> Ok(...)
from_string("bad")
// -> Error("bad")
pub fn gt(a: Decimal, b: Decimal) -> Bool
Compares two numbers numerically and returns True if the first
argument is greater than the second, otherwise False.
pub fn gte(a: Decimal, b: Decimal) -> Bool
Compares two numbers numerically and returns True if the first
argument is greater than or equal to the second, otherwise False.
pub fn is_inf(d: Decimal) -> Bool
Returns True if the number is positive or negative infinity(),
otherwise False.
Examples
is_inf(infinity())
// -> True
is_inf(from_int(1))
// -> False
pub fn is_integer(d: Decimal) -> Bool
Returns True when the given decimal has no significant digits after
the decimal point. Returns False for NaN and Infinity.
Examples
let assert Ok(d) = from_string("1.00")
is_integer(d)
// -> True
let assert Ok(d) = from_string("1.10")
is_integer(d)
// -> False
pub fn is_nan(d: Decimal) -> Bool
Returns True if the number is NaN, otherwise False.
Examples
is_nan(nan())
// -> True
is_nan(from_int(42))
// -> False
pub fn is_negative(d: Decimal) -> Bool
Returns True if the given number is negative, otherwise False.
Returns False for zero, NaN, and Infinity.
Examples
is_negative(from_int(-42))
// -> True
is_negative(from_int(0))
// -> False
pub fn is_positive(d: Decimal) -> Bool
Returns True if the given number is positive, otherwise False.
Returns False for zero, NaN, and Infinity.
Examples
is_positive(from_int(42))
// -> True
is_positive(from_int(0))
// -> False
pub fn is_zero(d: Decimal) -> Bool
Returns True if the given number is zero, otherwise False.
Examples
is_zero(zero())
// -> True
is_zero(from_int(1))
// -> False
pub fn lt(a: Decimal, b: Decimal) -> Bool
Compares two numbers numerically and returns True if the first
argument is less than the second, otherwise False.
pub fn lte(a: Decimal, b: Decimal) -> Bool
Compares two numbers numerically and returns True if the first
argument is less than or equal to the second, otherwise False.
pub fn max(a: Decimal, b: Decimal) -> Decimal
Compares two values numerically and returns the maximum. Unlike most
other functions, if a number is NaN the result will be the other number.
If both are NaN, returns NaN.
Examples
max(from_int(1), from_int(2)) |> to_string
// -> "2"
max(from_int(1), nan()) |> to_string
// -> "1"
pub fn min(a: Decimal, b: Decimal) -> Decimal
Compares two values numerically and returns the minimum. Unlike most
other functions, if a number is NaN the result will be the other number.
If both are NaN, returns NaN.
Examples
min(from_int(1), from_int(2)) |> to_string
// -> "1"
min(from_int(1), nan()) |> to_string
// -> "1"
pub fn multiply(a: Decimal, b: Decimal) -> Decimal
Multiplies two numbers.
Examples
let assert Ok(a) = from_string("0.5")
multiply(a, from_int(3)) |> to_string
// -> "1.5"
pub fn negate(a: Decimal) -> Decimal
Negates the given number.
Examples
negate(from_int(1)) |> to_string
// -> "-1"
negate(from_int(-1)) |> to_string
// -> "1"
pub fn normalize(d: Decimal) -> Decimal
Normalizes the given decimal by removing trailing zeros from the coefficient while keeping the number numerically equivalent by increasing the exponent.
Examples
let assert Ok(d) = from_string("1.00")
normalize(d) |> to_string
// -> "1"
let assert Ok(d) = from_string("1.01")
normalize(d) |> to_string
// -> "1.01"
pub fn parse(input: String) -> Result(#(Decimal, String), String)
Parses a string into a decimal, returning the decimal and the remaining
unparsed portion of the string. If parsing fails, returns Error with
the original input string.
Examples
parse("3.14rest")
// -> Ok(#(..., "rest"))
parse("bad")
// -> Error("bad")
pub fn remainder(a: Decimal, b: Decimal) -> Decimal
Returns the remainder of integer division of two numbers. The result
will have the sign of the first number. Division by zero returns NaN.
Examples
remainder(from_int(5), from_int(2)) |> to_string
// -> "1"
pub fn round(
d: Decimal,
places: Int,
mode: RoundingMode,
) -> Decimal
Rounds the given number to the specified number of decimal places with the given rounding mode.
Examples
let assert Ok(d) = from_string("1.234")
round(d, 1, HalfUp) |> to_string
// -> "1.2"
round(d, 0, HalfUp) |> to_string
// -> "1"
pub fn scale(d: Decimal) -> Int
Returns the scale of the decimal. A decimal’s scale is the number of digits after the decimal point.
Examples
scale(from_int(42))
// -> 0
let assert Ok(d) = from_string("99.12345")
scale(d)
// -> 5
pub fn sign(d: Decimal) -> Sign
Returns the sign of the decimal number.
Examples
sign(from_int(42))
// -> Positive
sign(from_int(-7))
// -> Negative
pub fn sqrt_with_context(d: Decimal, ctx: Context) -> Decimal
Square root with explicit context. Uses Newton’s (Babylonian) method with integer arithmetic. Returns NaN for negative inputs, zero for zero, infinity() for infinity().
pub fn square_root(d: Decimal) -> Decimal
Finds the square root of the given number using the default context (precision 28, HalfUp rounding).
Returns NaN for negative inputs, zero for zero, and infinity() for infinity().
Examples
let assert Ok(d) = from_string("100")
square_root(d) |> to_string
// -> "10"
pub fn subtract(a: Decimal, b: Decimal) -> Decimal
Subtracts the second number from the first. Equivalent to add when the
second number’s sign is negated.
Examples
let assert Ok(a) = from_string("1")
let assert Ok(b) = from_string("0.1")
subtract(a, b) |> to_string
// -> "0.9"
pub fn to_float(d: Decimal) -> Result(Float, Nil)
Returns the decimal converted to a float. The returned float may have
lower precision than the decimal. Returns Error(Nil) for NaN and
Infinity since these are not representable as floats on all targets.
Examples
let assert Ok(d) = from_string("1.5")
to_float(d)
// -> Ok(1.5)
pub fn to_int(d: Decimal) -> Result(Int, Nil)
Returns the decimal converted to an integer. Fails when the decimal has
a fractional part, is NaN, or is Infinity.
Examples
to_int(from_int(42))
// -> Ok(42)
let assert Ok(d) = from_string("1.00")
to_int(d)
// -> Ok(1)
let assert Ok(d) = from_string("1.5")
to_int(d)
// -> Error(Nil)
pub fn to_string(d: Decimal) -> String
Converts the given number to its string representation. Uses decimal notation when possible, scientific notation for very large or very small exponents.
Examples
let assert Ok(d) = from_string("1.00")
to_string(d)
// -> "1.00"
to_string(from_int(42))
// -> "42"
pub fn to_string_raw(d: Decimal) -> String
Converts the given number to a string in raw format showing the
coefficient and exponent directly: "12345E-2".
Examples
let assert Ok(d) = from_string("123.45")
to_string_raw(d)
// -> "12345E-2"
pub fn to_string_scientific(d: Decimal) -> String
Converts the given number to a string in scientific notation.
Examples
let assert Ok(d) = from_string("123.45")
to_string_scientific(d)
// -> "1.2345E+2"
pub fn to_string_xsd(d: Decimal) -> String
Converts the given number to a string in XSD canonical format.
Integers always include ".0", trailing zeros are removed (keeping at
least one decimal digit), and scientific notation is never used.
Examples
to_string_xsd(from_int(42))
// -> "42.0"
let assert Ok(d) = from_string("1.00")
to_string_xsd(d)
// -> "1.0"