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

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

  • Down

    Truncate toward zero

  • Up

    Round away from zero

  • Ceiling

    Round toward positive infinity()

  • Floor

    Round toward negative infinity()

  • HalfUp

    Round half away from zero (standard rounding)

  • HalfEven

    Round half to nearest even (banker’s rounding)

  • HalfDown

    Round half toward zero

Represents the sign of a decimal number. Zero can have either sign (signed zero per IEEE).

pub type Sign {
  Positive
  Negative
}

Constructors

  • Positive
  • Negative

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 infinity() -> Decimal

Positive infinity().

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 nan() -> Decimal

NaN (Not a Number).

pub fn neg_infinity() -> Decimal

Negative infinity().

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 one() -> Decimal

Decimal one (1).

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"
pub fn zero() -> Decimal

Decimal zero (0).

Search Document