Functional Weave
Code in TypeScript

math.rational@1.0.0

impl/python.py

3,128 bytes · the Python 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.

from typing import Tuple

from .math_gcd_lcm import gcd_wide  ← from math.gcd-lcm ^1.0.0 · built alongside by fune
from .math_rational_types import Rational, RationalOp
from .math_round_div import RoundingMode, round_div  ← from math.round-div ^1.0.0 · built alongside by fune

MAX_SAFE = 9007199254740991


def _reduce(numerator: int, denominator: int) -> Rational:
    """Reduce a wide fraction and bring it back into the exact number range."""
    if denominator == 0:
        raise ValueError("denominator must not be zero")
    g = gcd_wide(numerator, denominator)
    n = numerator // g
    d = denominator // g
    if d < 0:
        n, d = -n, -d
    # Python ints never overflow, but TypeScript numbers stop being exact here,
    # and the three languages must agree.
    if n > MAX_SAFE or n < -MAX_SAFE or d > MAX_SAFE:
        raise ValueError("rational overflow: the reduced result exceeds 2^53 - 1")
    return Rational(numerator=n, denominator=d)


def rational(numerator: int, denominator: int) -> Rational:
    """Build a reduced fraction with a positive denominator."""
    for v in (numerator, denominator):
        if isinstance(v, bool) or not isinstance(v, int):
            raise TypeError("numerator and denominator must be integers")
        if v > MAX_SAFE or v < -MAX_SAFE:
            raise ValueError("rational overflow: numerator and denominator must be within 2^53 - 1")
    return _reduce(numerator, denominator)


def _parts(r: Rational) -> Tuple[int, int]:
    n = rational(r.numerator, r.denominator)
    return n.numerator, n.denominator


def add_rational(a: Rational, b: Rational) -> Rational:
    an, ad = _parts(a)
    bn, bd = _parts(b)
    return _reduce(an * bd + bn * ad, ad * bd)


def subtract_rational(a: Rational, b: Rational) -> Rational:
    an, ad = _parts(a)
    bn, bd = _parts(b)
    return _reduce(an * bd - bn * ad, ad * bd)


def multiply_rational(a: Rational, b: Rational) -> Rational:
    an, ad = _parts(a)
    bn, bd = _parts(b)
    return _reduce(an * bn, ad * bd)


def divide_rational(a: Rational, b: Rational) -> Rational:
    an, ad = _parts(a)
    bn, bd = _parts(b)
    if bn == 0:
        raise ValueError("division by zero")
    return _reduce(an * bd, ad * bn)


def compare_rational(a: Rational, b: Rational) -> int:
    """-1, 0 or 1. Exact: cross-multiplied, never through a float."""
    an, ad = _parts(a)
    bn, bd = _parts(b)
    left = an * bd
    right = bn * ad
    return -1 if left < right else 1 if left > right else 0


def rational_to_integer(r: Rational, mode: RoundingMode) -> int:
    """The nearest integer under an explicit rounding mode."""
    n, d = _parts(r)
    return round_div(n, d, mode)


def calculate_rational(a: Rational, op: RationalOp, b: Rational) -> Rational:
    """Add, subtract, multiply or divide two fractions exactly.

    The result is always reduced with a positive denominator, so equal values
    have equal fields.
    """
    if op == "add":
        return add_rational(a, b)
    if op == "subtract":
        return subtract_rational(a, b)
    if op == "multiply":
        return multiply_rational(a, b)
    if op == "divide":
        return divide_rational(a, b)
    raise ValueError('unknown operation "%s"' % (op,))