# math.rational
A fraction held as two integers, so 1/3 stays 1/3 and 1/10 + 2/10 is exactly
3/10. Use it for exchange rates, unit conversions and pro-rata factors that
are applied more than once: a float rate drifts a little on every step, a
rational one never does. Money itself stays in `money.amount` minor units;
turn a rational back into an integer with `rationalToInteger` and an explicit
rounding mode.
Every result is reduced (numerator and denominator share no factor) and its
denominator is positive, so two equal values always have the same fields and
compare equal as plain data. Inputs need not be reduced: `{2, -4}` is read as
-1/2.
The functions:
- `rational(n, d)` builds and reduces a fraction.
- `addRational`, `subtractRational`, `multiplyRational` and `divideRational`
do the arithmetic; dividing by zero is an error ("division by zero").
- `calculateRational(a, op, b)` is the same four operations chosen by name,
for callers that hold the operation as data.
- `compareRational(a, b)` is -1, 0 or 1, exact by cross-multiplying wide,
never through a float, so fractions a double cannot tell apart still
compare correctly.
- `rationalToInteger(r, mode)` rounds to an integer with a `math.round-div`
mode: `half-up` and `up` round away from zero, `down` toward zero,
`half-even` to the even neighbour on a tie.
**Limits.** A numerator or denominator must be an integer within
±(2^53 - 1), the range where a JavaScript number is exact, in inputs and in
results. Intermediate products are computed wide (TypeScript `bigint`, Python
`int`, Rust `i128`) and reduced before the check, so `(2^53-1)/2 × 2/(2^53-1)`
is 1 even though the unreduced product is far past the limit. A result that
is still too large after reducing is an error ("rational overflow"), never a
silently wrong fraction.
Level 1 rather than the catalogue's 0, because it builds on `math.gcd-lcm` and
`math.round-div` instead of carrying private copies of them.
## What changed from 1.0.0
1.0.0 was one function, `calculateRational`, with `rational`, the four
operations, `compareRational` and `rationalToInteger` exported beside it but
unpinned: no signatures in the manifest and no vectors. 2.0.0 is a group of
eight functions, each with its own signature, file and vectors;
`calculateRational` keeps every 1.0.0 vector. Each file is its own module:
the operations import `rational`'s file for the shared reduction
(`rationalFromWide`, `rationalParts`, and in Rust `rational_to_value` and
`rational_from_value`), and `calculateRational` imports the four operations,
so `only=compareRational` installs just that and `rational`.
No answer changed. The Rust adapters now refuse a fractional numerator or
denominator with the "must be integers" wording TypeScript and Python use
(`rational_from_value` used to truncate it). It is a new major version
because the package's shape and public surface changed: it installs as one
module per function plus the group module (`math_rational` still re-exports
every function and helper), a project can take only some of it, the Python
module no longer exports its `MAX_SAFE` constant, and the formerly private
reduction helpers are now public under new names. Dependents stay on
`^1.0.0` until they move deliberately; 1.0.0 is unchanged.
It still requires `math.gcd-lcm ^1.0.0`, not `^2.0.0`. It imports only
`gcdWide`, which is identical in both, so there is nothing to gain from the
new major, and staying on `^1.0.0` lets a project that also has an older
dependent of `math.gcd-lcm ^1.0.0` resolve one flat graph.