Decimal

Decimal is exact base-ten arithmetic such as money; a number literal adapts to it and keeps every digit and its scale, and both back ends compute it.

Decimal is the type for money and anything else where base-ten rounding has to be exact - 0.1 + 0.2 has to be 0.3, which no binary floating-point type can promise. The type is declared in std/number, and a decimal literal adapts to it the same way an integer literal adapts to Int8. Both back ends compute it, and a literal keeps every digit it is written with.

Example#

const price: Decimal = 19.99
const quantity: Int = 3
print(price * Decimal.from(quantity))

Syntax#

Decimal                                  the type
const price: Decimal = 19.99             a literal, adapted like any other numeric literal
Decimal.tryFrom("19.99")                 a value read from text, a Result

Rules#

  1. Decimal is declared native type Decimal with Signed, Hash, next to the other numeric types in std/number. It has the same trait list a signed integer type has, so the same arithmetic and comparison operators are written the same way once it runs.

  2. A number literal adapts to Decimal the way any numeric literal adapts to its expected type, and keeps every digit it is written with. 19.99 is the number 1999 at the scale 2, 1.50 keeps its trailing zero, 0xFF is 255 at the scale 0, 1e3 has the scale -3 and -19.99 is the negation of 19.99. There is no limit on the digits; only a written scale outside of Int32 (1e-3000000000) is a compile error. A literal never passes through a Float64, and without an expected type 19.99 stays a Float64. Decimal.zero and Decimal.one are the literals 0 and 1. In a match, a literal pattern compares by numeric value like ==, so 1.0 matches 1.00, and a match on a Decimal always needs a wildcard arm.

  3. Decimal is Hash, unlike Float32 and Float64. Exact base-ten arithmetic has no nan and no -0.0 to complicate equality, so a Decimal can be a Map key.

  4. +, - and * are exact, and / stops at 34 significant digits. A quotient with an exact answer keeps it; any other is rounded half to even. rounded and divided take a scale and one of the six cases of Rounding, and % takes the sign of the dividend. Dividing by zero panics.

  5. A Decimal keeps every digit through JSON, YAML and TOML. JSON and YAML write the number with the digits of show() (19.90 stays 19.90) and read a number into a Decimal from its text, so 0.1234567890123456789, a value of 40 digits and 1e3 survive; a string is refused. TOML has no decimal type: a Decimal is written as a string and read back from one. A Float64 field still reads a number as the nearest Float64.

What this is not#

Decimal is not Float64 with more digits. Float64 is binary floating point and is not Hash; Decimal is base-ten and is Hash, because it does not have the two IEEE-754 exceptions that make hashing a float unsound. See Floating-point numbers for what those exceptions are.

A Decimal literal is not a Float64 rounded afterwards. const price: Decimal = 19.99 is exactly 1999 at the scale 2, the same value Decimal.tryFrom("19.99") reads from text, and a literal with more digits than a Float64 holds keeps all of them.

Esc