Skip to content

std/decimal

Operations on the arbitrary-precision, exact base-10 Decimal type: conversions to/from Int/Float/String, explicit-rounding division and quantization (every rounding op names a RoundingMode), sign/scale/abs helpers, and the value-based (scale-insensitive) arithmetic/Equatable/Comparable/ Hashable impls. + - * are exact; only Decimal.divide and Decimal.round round.

Import with import std/decimal.

type Decimal

Arbitrary-precision exact base-10 number. The third blessed numeric type alongside Int and Float: + - * are always exact, / is exact-or-trap, and there is no implicit conversion to/from Int or Float. Literals carry a d suffix (1.50d); scale is significant for display (1.50d prints “1.50”) but equality is scale-insensitive (1.50d == 1.5d).

fn from_int(n: Int): Decimal

type function on Decimal

Exact conversion of an Int to a Decimal (scale 0). Total.

Interactive Tests

assert Decimal.from_int(42) == 42d
assert Decimal.from_int(-7) == -7d
fn from_string(s: String): Maybe<Decimal>

type function on Decimal

Parses a plain base-10 numeric string into a Decimal. Accepts an optional sign, _ digit separators, and a single fractional part; the number of digits after the . becomes the scale ("1.50" → scale 2). Exponent notation is NOT accepted. Returns None for empty or malformed input.

Interactive Tests

assert Decimal.from_string("1.50") == Some(1.50d)
assert Decimal.from_string("1_000") == Some(1000d)
assert Decimal.from_string("oops") == None
fn to_int(d: Decimal): Maybe<Int>

type function on Decimal

Converts a Decimal to an Int by truncating toward zero. Returns None when the truncated value falls outside Int64’s range (~±9.22e18). Use Decimal.round first if a different rounding rule is needed.

Interactive Tests

assert Decimal.to_int(3.7d) == Some(3)
assert Decimal.to_int(-3.7d) == Some(-3)
fn to_float(d: Decimal): Float

type function on Decimal

Converts a Decimal to a Float. Total and lossy-by-nature (Float is binary64, so most base-10 fractions are not exactly representable). Magnitudes beyond Float’s range become ±Infinity. Explicit by design — there is no implicit Decimal→Float coercion.

Interactive Tests

assert Decimal.to_float(1.50d) == 1.5
fn from_float(f: Float, scale: Int, mode: RoundingMode): Maybe<Decimal>

type function on Decimal

Converts a Float to a Decimal at exactly the requested scale, rounding the Float’s exact binary value with mode. Requires an explicit scale and mode so it can never silently hand back 0.1000…0055. Returns None for NaN and ±Infinity. A runtime error results only when mode is Unnecessary and rounding would lose information.

Interactive Tests

assert Decimal.from_float(0.1, 2, RoundingMode.HalfEven) == Some(0.10d)
assert Decimal.from_float(3.14159, 2, RoundingMode.Floor) == Some(3.14d)
fn divide(a: Decimal, b: Decimal, scale: Int, mode: RoundingMode): Decimal

type function on Decimal

Divides a / b to exactly scale fractional digits, rounding the quotient with mode. This is the explicit-rounding division path — unlike the / operator (exact-or-trap), divide always returns a value at the named scale. Runtime error on a zero b, or when mode is Unnecessary and the exact quotient does not fit at scale.

Interactive Tests

assert Decimal.divide(10d, 3d, 2, RoundingMode.HalfEven) == 3.33d
assert Decimal.divide(10.00d, 4d, 2, RoundingMode.Down) == 2.50d
fn round(d: Decimal, scale: Int, mode: RoundingMode): Decimal

type function on Decimal

Quantizes d to exactly scale fractional digits using mode. Increasing the scale pads trailing zeros (always exact); decreasing it rounds. A negative scale rounds to tens/hundreds/… A runtime error results only when mode is Unnecessary and rounding would lose information.

Interactive Tests

assert Decimal.round(3.14159d, 2, RoundingMode.HalfEven) == 3.14d
assert Decimal.round(2.5d, 0, RoundingMode.HalfEven) == 2d
assert Decimal.round(1d, 2, RoundingMode.Up) == 1.00d
fn normalize(d: Decimal): Decimal

type function on Decimal

Strips trailing fractional zeros, returning the value at its minimal scale. Integer magnitude is never reduced (100d stays 100d, scale 0). Zero normalizes to scale 0. Equality/hashing already treat scale-equal values as equal; normalize is for when you want the minimal-scale representation for display.

Interactive Tests

assert Decimal.normalize(1.50d) == 1.5d
assert Decimal.normalize(100.00d) == 100d
fn scale(d: Decimal): Int

type function on Decimal

Returns the scale (number of fractional digits) of d. Scale is display-significant — 1.50d has scale 2, 1.5d has scale 1 — even though the two are ==-equal.

Interactive Tests

assert Decimal.scale(1.50d) == 2
assert Decimal.scale(5d) == 0
fn abs(d: Decimal): Decimal

type function on Decimal

Returns |d| (scale preserved).

Interactive Tests

assert Decimal.abs(-1.50d) == 1.50d
fn negate(d: Decimal): Decimal

type function on Decimal

Returns -d (scale preserved). Negating zero stays zero.

Interactive Tests

assert Decimal.negate(1.50d) == -1.50d
fn sign(d: Decimal): Int

type function on Decimal

Returns the sign of d as -1, 0, or +1 (by mathematical value).

Interactive Tests

assert Decimal.sign(-1.50d) == -1
assert Decimal.sign(0.00d) == 0
assert Decimal.sign(1.50d) == 1
fn zero?(d: Decimal): Bool

type function on Decimal

True if d is exactly zero (at any scale: 0d, 0.00d are both zero).

Interactive Tests

assert Decimal.zero?(0.00d)
refute Decimal.zero?(1.50d)
fn min(a: Decimal, b: Decimal): Decimal

type function on Decimal

Returns the lesser of a and b (by value; ties return a).

Interactive Tests

assert Decimal.min(1.50d, 2d) == 1.50d
fn max(a: Decimal, b: Decimal): Decimal

type function on Decimal

Returns the greater of a and b (by value; ties return a).

Interactive Tests

assert Decimal.max(1.50d, 2d) == 2d

impl Display

fn to_string(d: Decimal): String

impl Display.to_string

Returns the canonical scale-preserving string form of d, matching the runtime stringifier used by IO.print and string interpolation. Trailing zeros are kept (1.50d"1.50").

Interactive Tests

assert Decimal.to_string(1.50d) == "1.50"
assert Decimal.to_string(100d) == "100"

impl Add

fn add(lhs: Decimal, rhs: Decimal): Decimal

impl Add.add

impl Subtract

fn subtract(lhs: Decimal, rhs: Decimal): Decimal

impl Subtract.subtract

impl Multiply

fn multiply(lhs: Decimal, rhs: Decimal): Decimal

impl Multiply.multiply

impl Debug

fn inspect(d: Decimal): String

impl Debug.inspect

impl Equatable

fn equal?(a: Decimal, b: Decimal): Bool

impl Equatable.equal?

impl Hashable

fn hash(d: Decimal): Int

impl Hashable.hash

impl Comparable

fn compare(a: Decimal, b: Decimal): Ordering

impl Comparable.compare

impl Steppable

fn step_by(value: Decimal, by: Decimal): Maybe<Decimal>

impl Steppable.step_by

Returns value + by. Decimal stepping is exact because Decimal is arbitrary-precision base-10.

Interactive Tests

assert Decimal.step_by(1.0d, 0.1d) == Some(1.1d)
enum RoundingMode {
    Up
    Down
    Ceiling
    Floor
    HalfUp
    HalfDown
    HalfEven
    Unnecessary
}

Rounding policy for decimal division and quantization. There is no default: every rounding operation names its mode explicitly.

  • Up — away from zero
  • Down — toward zero (truncate)
  • Ceiling — toward +∞
  • Floor — toward −∞
  • HalfUp — nearest; ties away from zero
  • HalfDown — nearest; ties toward zero
  • HalfEven — nearest; ties to even (banker’s rounding)
  • Unnecessary — assert exactness; runtime error if rounding is needed

impl Display

fn to_string(value: RoundingMode): String

impl Display.to_string

impl Debug

fn inspect(value: RoundingMode): String

impl Debug.inspect