Arithmetic Policies
Sometimes an arithmetic result simply does not exist: the sum is too large for the result type, or the divisor is zero. By default ReifyDB fails the statement rather than silently producing a wrong number. When failing is not what you want, each arithmetic function family - math::add, math::sub, math::mul, math::div, math::rem - comes in six policy variants that name, in the query itself, what happens instead.
The default: fail loudly
The bare operators (+, -, *, /, %) and the bare functions (math::add and friends) apply the same rule: a result that cannot be represented is an error, not a wrong answer. Overflow fails with NUMBER_002 (number out of range):
and division or remainder by zero fails with NUMBER_007. The functions report it wrapped in a FUNCTION_007 execution error; the cause is the same:
For floating-point inputs the same rule covers non-finite results: an operation that would produce infinity or NaN counts as overflow.
One family, seven spellings
Each policy is a separate function named with a suffix: math::add_saturate, math::div_none, math::mul_strict, and so on. All five families - add, sub, mul, div, rem - have all six variants. Shown here for math::add:
| Signature | On overflow | On division by zero |
|---|---|---|
| math::add(a, b) | Fails the statement (NUMBER_002) | Fails the statement (NUMBER_007) |
| math::add_saturate(a, b) | Clamps to the result type's minimum or maximum | Returns 0 |
| math::add_wrap(a, b) | Wraps around (two's complement) | Returns 0 |
| math::add_zero(a, b) | Returns 0 | Returns 0 |
| math::add_none(a, b) | Returns none | Returns none |
| math::add_default(a, b, fallback) | Returns fallback for that row | Returns fallback for that row |
| math::add_strict(a, b, message) | Fails the statement with your message (FUNCTION_007) | Fails the statement with your message (FUNCTION_007) |
The row-level policies (_saturate, _wrap, _zero, _none, _default) affect only the offending row; every other row computes normally. The failing policies (the bare function and _strict) abort the whole statement on the first impossible result.
saturate: clamp to the type range
_saturate pins an out-of-range result to the result type's minimum or maximum. Use it when "as large as representable" is a better answer than no answer - accumulating counters, scores with a cap:
On unsigned types, subtracting below zero clamps to zero - a natural fit for "remaining quantity" arithmetic:
wrap and zero
_wrap applies two's-complement wraparound, the behavior of unchecked machine arithmetic - useful for hash-style computations where wrapping is intended. _zero substitutes 0 for any result that does not exist:
none: make the result missing
_none turns an impossible result into a missing value, which then flows through the rest of the query with the usual none semantics: filters drop it, aggregates skip it. This is the honest choice for ratios with an empty denominator - an average over nothing is not zero, it is unknown:
default: substitute your own value
_default takes a third argument and returns it for the rows where the real result does not exist. The fallback is coerced to the result type; a none fallback yields none:
strict: fail with your own message
_strict also fails the statement, like the bare function, but the third argument replaces the generic diagnostic with a message that says what actually went wrong in your domain:
Missing input is not a policy decision
The policies decide what happens when a result cannot exist. A missing input is different: the result is simply missing too, in every variant - even _strict does not fail on a none input, and _zero does not invent a zero for one:
To replace missing inputs, handle them explicitly before the arithmetic - see Working with none.
Types widen before they overflow
Arithmetic first promotes both operands to a wider result type: two int4 values add as int8, two float4 values as float8, and mixed signed/unsigned operands promote to a signed type that holds both. Promotion caps at the 128-bit types (int16, uint16), so integer overflow only occurs at the extremes of those widest types - which is why the examples above use them. Division by zero, not overflow, is the case these policies handle day to day:
