Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { gcdWide } from "./math_gcd_lcm.ts"; ← from math.gcd-lcm ^1.0.0 · built alongside by fune
import { type RoundingMode, roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Rational, type RationalOp } from "./math_rational_types.ts";
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
/** Reduce a wide fraction and bring it back into the exact number range. */
function reduce(numerator: bigint, denominator: bigint): Rational {
if (denominator === 0n) {
throw new RangeError("denominator must not be zero");
}
const g = gcdWide(numerator, denominator);
let n = numerator / g;
let d = denominator / g;
if (d < 0n) {
n = -n;
d = -d;
}
if (n > MAX_SAFE || n < -MAX_SAFE || d > MAX_SAFE) {
throw new RangeError("rational overflow: the reduced result exceeds 2^53 - 1");
}
// Number(-0n) is 0, so a zero result never comes back as -0.
return { numerator: Number(n), denominator: Number(d) };
}
/** Build a reduced fraction with a positive denominator. */
export function rational(numerator: number, denominator: number): Rational {
if (!Number.isInteger(numerator) || !Number.isInteger(denominator)) {
throw new TypeError("numerator and denominator must be integers");
}
if (!Number.isSafeInteger(numerator) || !Number.isSafeInteger(denominator)) {
throw new RangeError("rational overflow: numerator and denominator must be within 2^53 - 1");
}
return reduce(BigInt(numerator), BigInt(denominator));
}
function wide(r: Rational): [bigint, bigint] {
const n = rational(r.numerator, r.denominator);
return [BigInt(n.numerator), BigInt(n.denominator)];
}
export function addRational(a: Rational, b: Rational): Rational {
const [an, ad] = wide(a);
const [bn, bd] = wide(b);
return reduce(an * bd + bn * ad, ad * bd);
}
export function subtractRational(a: Rational, b: Rational): Rational {
const [an, ad] = wide(a);
const [bn, bd] = wide(b);
return reduce(an * bd - bn * ad, ad * bd);
}
export function multiplyRational(a: Rational, b: Rational): Rational {
const [an, ad] = wide(a);
const [bn, bd] = wide(b);
return reduce(an * bn, ad * bd);
}
export function divideRational(a: Rational, b: Rational): Rational {
const [an, ad] = wide(a);
const [bn, bd] = wide(b);
if (bn === 0n) {
throw new RangeError("division by zero");
}
return reduce(an * bd, ad * bn);
}
/** -1, 0 or 1. Exact: cross-multiplied wide, never through a float. */
export function compareRational(a: Rational, b: Rational): number {
const [an, ad] = wide(a);
const [bn, bd] = wide(b);
const left = an * bd;
const right = bn * ad;
return left < right ? -1 : left > right ? 1 : 0;
}
/** The nearest integer under an explicit rounding mode. */
export function rationalToInteger(r: Rational, mode: RoundingMode): number {
const n = rational(r.numerator, r.denominator);
return roundDiv(n.numerator, n.denominator, mode);
}
/**
* Add, subtract, multiply or divide two fractions exactly.
*
* The result is always reduced with a positive denominator, so equal values
* have equal fields.
*/
export function calculateRational(a: Rational, op: RationalOp, b: Rational): Rational {
switch (op) {
case "add":
return addRational(a, b);
case "subtract":
return subtractRational(a, b);
case "multiply":
return multiplyRational(a, b);
case "divide":
return divideRational(a, b);
default:
throw new RangeError(`unknown operation "${op}"`);
}
}