use super::funejson::Value; use super::math_gcd_lcm::gcd_wide; use super::math_round_div::round_div; const MAX_SAFE: i128 = 9_007_199_254_740_991; /// Reduce a wide fraction and bring it back into the exact number range. fn reduce(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"); } reduce(n, d) } fn parts(r: &Rational) -> (i128, i128) { let n = rational(r.numerator, r.denominator); (n.numerator as i128, n.denominator as i128) } pub fn add_rational(a: &Rational, b: &Rational) -> Rational { let (an, ad) = parts(a); let (bn, bd) = parts(b); reduce(an * bd + bn * ad, ad * bd) } pub fn subtract_rational(a: &Rational, b: &Rational) -> Rational { let (an, ad) = parts(a); let (bn, bd) = parts(b); reduce(an * bd - bn * ad, ad * bd) } pub fn multiply_rational(a: &Rational, b: &Rational) -> Rational { let (an, ad) = parts(a); let (bn, bd) = parts(b); reduce(an * bn, ad * bd) } /// # Panics /// Panics if `b` is zero. pub fn divide_rational(a: &Rational, b: &Rational) -> Rational { let (an, ad) = parts(a); let (bn, bd) = parts(b); if bn == 0 { panic!("division by zero"); } reduce(an * bd, ad * bn) } /// -1, 0 or 1. Exact: cross-multiplied in i128, never through a float. pub fn compare_rational(a: &Rational, b: &Rational) -> i64 { let (an, ad) = parts(a); let (bn, bd) = parts(b); match (an * bd).cmp(&(bn * ad)) { std::cmp::Ordering::Less => -1, std::cmp::Ordering::Equal => 0, std::cmp::Ordering::Greater => 1, } } /// The nearest integer under an explicit rounding mode. pub fn rational_to_integer(r: &Rational, mode: &str) -> i64 { let n = rational(r.numerator, r.denominator); round_div(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. /// /// # Panics /// Panics on a zero denominator, division by zero, an unknown operation, or a /// result outside ±(2^53 - 1). pub fn calculate_rational(a: &Rational, op: &str, b: &Rational) -> Rational { match op { "add" => add_rational(a, b), "subtract" => subtract_rational(a, b), "multiply" => multiply_rational(a, b), "divide" => divide_rational(a, b), other => panic!("unknown operation \"{}\"", other), } } pub fn rational_to_value(r: &Rational) -> Value { Value::obj(vec![ ("numerator", Value::Int(r.numerator)), ("denominator", Value::Int(r.denominator)), ]) } pub fn rational_from_value(v: &Value) -> Rational { Rational { numerator: v.get("numerator").as_i64(), denominator: v.get("denominator").as_i64(), } } pub fn fune_vector(args: &[Value]) -> Value { rational_to_value(&calculate_rational( &rational_from_value(&args[0]), args[1].as_str(), &rational_from_value(&args[2]), )) }