The q* stdlib docs
qnumber: arbitrary-precision arithmetic
qnumber provides the number.* built-ins: exact, arbitrary-precision arithmetic backed by qvalue's BigDecimal and BigInt. Where math.* is fast f64 that may round, number.* never rounds to a fixed range — it handles any-length integers, exact decimals, and scientific-notation input. Use it for money, very large integers, and any computation where 0.1 + 0.2 must equal exactly 0.3.
What it gives you#
qnumber registers seven public built-ins. Most accept multiple positional arguments and fold over them; div and cmp take exactly two. Results are returned as Decimal values (exact), except cmp which returns an Int.
| Function | Arguments | Returns | Behavior |
|---|---|---|---|
| number.add | n values | Decimal | Exact sum (starts from 0, folds all args). |
| number.sub | n values | Decimal | First arg minus each remaining arg, in order. |
| number.mul | n values | Decimal | Exact product (folds from the first arg). |
| number.neg | 1 value | Decimal | 0 minus the argument (negation). |
| number.abs | 1 value | Decimal | Absolute value (negate only when below zero). |
| number.div | a, b | Decimal | a divided by an integer divisor b, to 20 extra digits. |
| number.cmp | a, b | Int | -1 if a<b, 0 if equal, 1 if a>b. |
Input coercion to BigDecimal#
Each argument is read as an exact BigDecimal. Numeric Value types convert directly; a Str is parsed as a decimal (this is how you pass values beyond f64 range and scientific notation). Anything that is neither numeric nor a parseable numeric string becomes 0.
Division rules#
number.div(a, b) requires an INTEGER divisor b. Internally it determines the magnitude and sign of b: if b is a whole number it divides a by that magnitude carrying 20 extra digits of precision, then applies the sign. If b is zero, or not a whole number (it contains a decimal point), the function returns 0 — the caller is expected to validate the divisor. So number.div(1, 8) gives the exact 0.125, but number.div(1, 0) and number.div(1, 2.5) both give 0.
- never rounds to a fixed range
- Add/sub/mul/neg/abs are exact: adding two 30-digit integers gives the exact 30-digit result, and 0.1 + 0.2 is exactly 0.3.
- div precision
- Division carries 20 extra digits beyond the operands.
- cmp
- Uses the value model's compare; non-numbers default to 0 for the comparison.
Resource budget#
Unlike fast f64 math.*, exact number.* allocates arbitrary-precision digit buffers, folds over the arguments, and (for div) carries 20 extra digits — cost is proportional to operand width. Every number.* function declares a 2048-byte (2 KB) memory budget and zero storage.
Examples#
The defining example: exact decimal addition. In f64, 0.1 + 0.2 is 0.30000000000000004; in number.* it is exactly 0.3.
Request
number.add(0.1, 0.2)
# wire form (decimals passed as numeric values):
{ "0": {"Decimal": "0.1"}, "1": {"Decimal": "0.2"} }Response
{ "result": { "Decimal": "0.3" } }Output
0.3Request
number.add("111111111111111111111111111111", "111111111111111111111111111111")
number.div(1, 8)
number.cmp(2, 9)Response
{ "result": { "Decimal": "222222222222222222222222222222" } }
{ "result": { "Decimal": "0.125" } }
{ "result": { "Int": -1 } }Output
222222222222222222222222222222
0.125
-1