Standard library · Numbers and collections

decimal

Exact decimal numbers for money and accounting, with none of the rounding error of floating point.

import decimal
On this page
  1. Types
  2. Functions

In f64, 0.1 + 0.2 is not exactly 0.3. For science and graphics that tiny error does not matter; for money it does, because a lost cent in the accounts is not acceptable. The decimal package keeps a number as a large whole number and a "scale", the count of digits after the point, so 10.05 is 1005 with scale 2 and no error can creep in.

Addition, subtraction and multiplication are always exact. Division may not terminate, as one third does not, so you say how many decimal places you want and how to round. Rounding offers seven methods; accounting usually wants HalfEven.

Types

Rounding

enum Rounding
How to round when digits have to be dropped. The example below shows every method applied to −2.5.
NameTypeDescription
DownTowards zero: the extra digits are thrown away.
UpAway from zero.
FloorDownwards, towards negative infinity.
CeilUpwards, towards positive infinity.
HalfUpTo the nearest, with a half going away from zero. This is the rounding taught at school.
HalfDownTo the nearest, with a half going towards zero.
HalfEvenTo the nearest, with a half going to the even number, known as banker's rounding. Over many additions it keeps the rounding error at zero on average.
decimal-Round.salam
import decimal

func main:
    x := decimal.FromScaled(-25, 1)
    println decimal.ToStr(decimal.Round(x, 0)), decimal.ToStr(decimal.RoundHalfUp(x, 0)), decimal.ToStr(decimal.Truncate(x, 0))
    each mode in [decimal.Rounding.Down, decimal.Rounding.Up, decimal.Rounding.Floor, decimal.Rounding.Ceil, decimal.Rounding.HalfUp, decimal.Rounding.HalfDown, decimal.Rounding.HalfEven]:
        print decimal.ToStr(decimal.Rescale(x, 0, mode)), ""
    end
    println ""
    println decimal.ToStr(decimal.Trim(decimal.FromScaled(12500, 4)))
end
Output-2 -3 -2 -2 -3 -3 -2 -3 -2 -2 1.25

Dec

struct Dec
An exact decimal number.
NameTypeDescription
unscaledbigint.IntThe whole number without the point; for 10.05 it is 1005.
scaleint = 0The number of digits after the point.
decimal-Dec.salam
import decimal
import bigint

func main:
    mut a := decimal.FromScaled(1005, 2)
    defer a.free()
    println a.to_str(), a.is_zero(), a.sign()
    b := a.clone()
    println decimal.ToStr(b), decimal.Scale(b), bigint.ToStr(decimal.Unscaled(b))
    println decimal.ToStr(decimal.Zero()), decimal.ToStr(decimal.FromInt(42))
    println decimal.ToStr(decimal.New(bigint.FromInt(31415), 4))
    println decimal.ToStr(decimal.FromFloat(0.1, 3))
    println decimal.IsZero(decimal.Zero()), decimal.IsNegative(decimal.FromInt(-1)), decimal.Sign(a)
    mut c := decimal.Clone(a)
    decimal.Free(c)
end
Output10.05 false 1 10.05 2 1005 0 42 3.1415 0.100 true true 1

free

func free()
Releases the number's memory.

The example for this item is shown with the ones above: decimal-Dec.salam

is_zero

func is_zero(): bool
True when the number is zero.

The example for this item is shown with the ones above: decimal-Dec.salam

sign

func sign(): int
The sign: minus one, zero or one.

The example for this item is shown with the ones above: decimal-Dec.salam

clone

func clone(): Dec
An independent copy of the number.

The example for this item is shown with the ones above: decimal-Dec.salam

to_str

func to_str(): str
The number as text with all its decimal places.

The example for this item is shown with the ones above: decimal-Dec.salam

Functions

Split

func Split(total: Dec, n: int): Vector<Dec>
Divides an amount into n equal shares whose total is exactly the amount; the odd remainder goes to the first shares.
decimal-Split.salam
import decimal

func main:
    total := decimal.FromScaled(10000, 2)
    mut shares := decimal.Split(total, 3)
    defer decimal.FreeAll(shares)
    each s in shares:
        print decimal.ToStr(s), ""
    end
    println ""
    println decimal.ToStr(decimal.Sum(shares))
    mut ratios := Vector {} as Vector<i64>
    defer ratios.free()
    ratios.push(1 as i64)
    ratios.push(2 as i64)
    mut parts := decimal.Allocate(total, ratios)
    defer decimal.FreeAll(parts)
    each p in parts:
        print decimal.ToStr(p), ""
    end
    println ""
end
Output33.34 33.33 33.33 100.00 33.34 66.66

Allocate

func Allocate(total: Dec, ratios: Vector<i64>): Vector<Dec>
Divides an amount by the given ratios, keeping the total exactly the amount.

The example for this item is shown with the ones above: decimal-Split.salam

FreeAll

func FreeAll(v &: Vector<Dec>)
Releases the memory of every number in a vector, and of the vector itself.

The example for this item is shown with the ones above: decimal-Split.salam

Rescale

func Rescale(d: Dec, scale: int, mode: Rounding): Dec
Moves the number to a new scale, rounding with the given method when it has to.

The example for this item is shown with the ones above: decimal-Round.salam

Round

func Round(d: Dec, scale: int): Dec
Rounds the number to the given number of decimal places, half to even.

The example for this item is shown with the ones above: decimal-Round.salam

RoundHalfUp

func RoundHalfUp(d: Dec, scale: int): Dec
The ordinary school rounding: a half goes away from zero.

The example for this item is shown with the ones above: decimal-Round.salam

Truncate

func Truncate(d: Dec, scale: int): Dec
Throws the extra digits away without rounding.

The example for this item is shown with the ones above: decimal-Round.salam

Trim

func Trim(d: Dec): Dec
Removes the trailing zeros; 1.2500 becomes 1.25.

The example for this item is shown with the ones above: decimal-Round.salam

Neg

func Neg(d: Dec): Dec
The number with its sign reversed.
decimal-Add.salam
import decimal

func main:
    a := decimal.FromScaled(10, 1)
    b := decimal.FromScaled(20, 1)
    println decimal.ToStr(decimal.Add(a, b)), decimal.ToStr(decimal.Sub(a, b)), decimal.ToStr(decimal.Mul(a, b))
    println decimal.ToStr(decimal.Div(a, decimal.FromInt(3), 4, decimal.Rounding.HalfEven))
    println decimal.ToStr(decimal.DivRound(a, decimal.FromInt(3), 2))
    println decimal.ToStr(decimal.Pow(b, 3)), decimal.ToStr(decimal.Neg(a)), decimal.ToStr(decimal.Abs(decimal.Neg(a)))
    println decimal.ToStr(decimal.MulInt(a, 3)), decimal.ToStr(decimal.AddInt(a, 1)), decimal.ToStr(decimal.SubInt(a, 1))
    println decimal.ToStr(decimal.DivInt(decimal.FromInt(10), 4, 2, decimal.Rounding.Floor))
    println decimal.ToStr(decimal.Percent(decimal.FromInt(250000), decimal.FromInt(9), 0))
end
Output3.0 -1.0 2.00 0.3333 0.33 8.000 -1.0 1.0 3.0 2.0 0.0 2.50 22500

Abs

func Abs(d: Dec): Dec
The absolute value.

The example for this item is shown with the ones above: decimal-Add.salam

Add

func Add(a: Dec, b: Dec): Dec
Exact addition; the result's scale is the larger of the two.

The example for this item is shown with the ones above: decimal-Add.salam

Sub

func Sub(a: Dec, b: Dec): Dec
Exact subtraction.

The example for this item is shown with the ones above: decimal-Add.salam

Mul

func Mul(a: Dec, b: Dec): Dec
Exact multiplication; the result's scale is the sum of the two.

The example for this item is shown with the ones above: decimal-Add.salam

Div

func Div(a: Dec, b: Dec, scale: int, mode: Rounding): Dec
Division with the number of decimal places and the rounding method you give.

The example for this item is shown with the ones above: decimal-Add.salam

DivRound

func DivRound(a: Dec, b: Dec, scale: int): Dec
Division rounded half to even.

The example for this item is shown with the ones above: decimal-Add.salam

Pow

func Pow(d: Dec, n: int): Dec
The number raised to the whole power n.

The example for this item is shown with the ones above: decimal-Add.salam

MulInt

func MulInt(d: Dec, v: i64): Dec
Multiplies by a whole number.

The example for this item is shown with the ones above: decimal-Add.salam

AddInt

func AddInt(d: Dec, v: i64): Dec
Adds a whole number.

The example for this item is shown with the ones above: decimal-Add.salam

SubInt

func SubInt(d: Dec, v: i64): Dec
Subtracts a whole number.

The example for this item is shown with the ones above: decimal-Add.salam

DivInt

func DivInt(d: Dec, v: i64, scale: int, mode: Rounding): Dec
Divides by a whole number, with the places and rounding you choose.

The example for this item is shown with the ones above: decimal-Add.salam

Percent

func Percent(d: Dec, pct: Dec, scale: int): Dec
pct per cent of the number, to the given number of decimal places; a 9 per cent tax, for instance.

The example for this item is shown with the ones above: decimal-Add.salam

Sum

func Sum(items: Vector<Dec>): Dec
The sum of every number in a vector.

The example for this item is shown with the ones above: decimal-Split.salam

ToStr

func ToStr(d: Dec): str
The number as text with all its decimal places.
decimal-Parse.salam
import decimal

func main:
    mut ok := false
    d := decimal.Parse("1234567.891", ok)
    println ok, decimal.ToStr(d)
    println decimal.ToStrFixed(d, 1), decimal.FormatGrouped(d, 2, ",", ".")
    println decimal.ToStr(decimal.ParseOr("not a number"))
    mut fits := false
    println decimal.ToInt(decimal.FromInt(42), fits), fits, decimal.ToFloat(d)
end
Outputtrue 1234567.891 1234567.9 1,234,567.89 0 42 true 1234567.891

ToStrFixed

func ToStrFixed(d: Dec, scale: int): str
The number as text with exactly scale decimal places.

The example for this item is shown with the ones above: decimal-Parse.salam

FormatGrouped

func FormatGrouped(d: Dec, scale: int, group: str, point: str): str
The number as text with the thousands and decimal separators you choose.

The example for this item is shown with the ones above: decimal-Parse.salam

Parse

func Parse(s: str, ok &: bool): Dec
Turns text into a decimal; when it cannot, ok turns false.

The example for this item is shown with the ones above: decimal-Parse.salam

ParseOr

func ParseOr(s: str): Dec
Turns text into a decimal, returning zero when it cannot.

The example for this item is shown with the ones above: decimal-Parse.salam

ToInt

func ToInt(d: Dec, ok &: bool): i64
The whole part of the number; when it does not fit in an i64, ok turns false.

The example for this item is shown with the ones above: decimal-Parse.salam

ToFloat

func ToFloat(d: Dec): f64
The nearest f64, for display or an approximate calculation only.

The example for this item is shown with the ones above: decimal-Parse.salam

Zero

func Zero(): Dec
The number zero.

The example for this item is shown with the ones above: decimal-Dec.salam

FromInt

func FromInt(v: i64): Dec
Builds a decimal with no decimal places from a whole number.

The example for this item is shown with the ones above: decimal-Dec.salam

FromScaled

func FromScaled(unscaled: i64, scale: int): Dec
Builds the number from a whole number and a scale; FromScaled(1005, 2) is 10.05. It suits amounts stored in minor units such as cents.

The example for this item is shown with the ones above: decimal-Dec.salam

New

func New(unscaled: bigint.Int, scale: int): Dec
Builds the number from a big number and a scale.

The example for this item is shown with the ones above: decimal-Dec.salam

Clone

func Clone(d: Dec): Dec
An independent copy of the number.

The example for this item is shown with the ones above: decimal-Dec.salam

Free

func Free(d &: Dec)
Releases the number's memory.

The example for this item is shown with the ones above: decimal-Dec.salam

FromFloat

func FromFloat(f: f64, scale: int): Dec
Converts an f64 with the number of decimal places you ask for. The f64's own error is rounded away, so 0.1 to three places is exactly 0.100.

The example for this item is shown with the ones above: decimal-Dec.salam

IsZero

func IsZero(d: Dec): bool
True when the number is zero.

The example for this item is shown with the ones above: decimal-Dec.salam

Sign

func Sign(d: Dec): int
The sign of the number.

The example for this item is shown with the ones above: decimal-Dec.salam

IsNegative

func IsNegative(d: Dec): bool
True when the number is negative.

The example for this item is shown with the ones above: decimal-Dec.salam

Scale

func Scale(d: Dec): int
The number of decimal places.

The example for this item is shown with the ones above: decimal-Dec.salam

Unscaled

func Unscaled(d: Dec): bigint.Int
The whole number without the point, as a big number.

The example for this item is shown with the ones above: decimal-Dec.salam

Cmp

func Cmp(a: Dec, b: Dec): int
Compares two numbers whatever their scales; 1.50 and 1.5 are equal.
decimal-Cmp.salam
import decimal

func main:
    a := decimal.FromScaled(150, 2)
    b := decimal.FromScaled(15, 1)
    println decimal.Cmp(a, b), decimal.Equals(a, b)
    println decimal.Less(a, decimal.FromInt(2)), decimal.Greater(a, decimal.FromInt(2))
    println decimal.ToStr(decimal.Min(a, decimal.FromInt(1))), decimal.ToStr(decimal.Max(a, decimal.FromInt(1)))
end
Output0 true true false 1 1.50

Equals

func Equals(a: Dec, b: Dec): bool
True when the two numbers are equal in value.

The example for this item is shown with the ones above: decimal-Cmp.salam

Less

func Less(a: Dec, b: Dec): bool
True when the first is smaller.

The example for this item is shown with the ones above: decimal-Cmp.salam

Greater

func Greater(a: Dec, b: Dec): bool
True when the first is larger.

The example for this item is shown with the ones above: decimal-Cmp.salam

Min

func Min(a: Dec, b: Dec): Dec
The smaller of two numbers.

The example for this item is shown with the ones above: decimal-Cmp.salam

Max

func Max(a: Dec, b: Dec): Dec
The larger of two numbers.

The example for this item is shown with the ones above: decimal-Cmp.salam