Functional Weave
Code in Rust

math.rational@2.0.0

impl/rust/rational.rs

2,681 bytes · the Rust 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.

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

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]),
    ))
}