from typing import Tuple from .math_gcd_lcm import gcd_wide from .math_rational_types import Rational, RationalOp from .math_round_div import RoundingMode, round_div 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,))