# 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.