Functional Weave
Code in Rust

math.rational@1.0.0

impl/typescript.ts

3,418 bytes · the TypeScript implementation · view raw

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}"`);
  }
}