Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_gcd_lcm::gcd_wide; ← from math.gcd-lcm ^1.0.0 · built alongside by fune
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
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]),
))
}