use super::funejson::Value; use super::math_gcd_lcm::gcd_wide; const MAX_SAFE: i128 = 9_007_199_254_740_991; /// Reduce a wide fraction and bring it back into the exact number range. The /// arithmetic works on i128 products, which may be past 2^53, and only the /// reduced result has to fit. /// /// # Panics /// Panics if the denominator is zero or the reduced result is outside ±(2^53 - 1). pub fn rational_from_wide(numerator: i128, denominator: i128) -> Rational { if denominator == 0 { panic!("denominator must not be zero"); } let g = gcd_wide(numerator, denominator); let mut n = numerator / g; let mut d = denominator / g; if d < 0 { n = -n; d = -d; } // i64 could hold more, but TypeScript numbers stop being exact here, and // the three languages must agree. if n > MAX_SAFE || n < -MAX_SAFE || d > MAX_SAFE { panic!("rational overflow: the reduced result exceeds 2^53 - 1"); } Rational { numerator: n as i64, denominator: d as i64, } } /// Build a reduced fraction with a positive denominator. /// /// # Panics /// Panics if the denominator is zero or either part is outside ±(2^53 - 1). pub fn rational(numerator: i64, denominator: i64) -> Rational { let (n, d) = (numerator as i128, denominator as i128); if n > MAX_SAFE || n < -MAX_SAFE || d > MAX_SAFE || d < -MAX_SAFE { panic!("rational overflow: numerator and denominator must be within 2^53 - 1"); } rational_from_wide(n, d) } /// A checked, reduced operand as i128s, ready to multiply without overflow. pub fn rational_parts(r: &Rational) -> (i128, i128) { let n = rational(r.numerator, r.denominator); (n.numerator as i128, n.denominator as i128) } pub fn rational_to_value(r: &Rational) -> Value { Value::obj(vec![ ("numerator", Value::Int(r.numerator)), ("denominator", Value::Int(r.denominator)), ]) } /// Read an integer from JSON, refusing a fraction with the wording TypeScript /// and Python use rather than let `as_i64` quietly truncate it. pub fn rational_int_from_value(v: &Value) -> i64 { if let Value::Float(f) = v { if f.fract() != 0.0 { panic!("numerator and denominator must be integers"); } } v.as_i64() } pub fn rational_from_value(v: &Value) -> Rational { Rational { numerator: rational_int_from_value(v.get("numerator")), denominator: rational_int_from_value(v.get("denominator")), } } pub fn fune_vector(args: &[Value]) -> Value { rational_to_value(&rational( rational_int_from_value(&args[0]), rational_int_from_value(&args[1]), )) }