use super::funejson::Value; use super::math_big_integer::{parse_big_integer, BigInt}; /// math.round-div, for integers too big for an i64. /// /// An exact product such as distance x gallon size x price per litre easily /// passes 2^63, where `round_div` would overflow. The rounding rules are the /// same as math.round-div's: every mode works on the magnitude and puts the /// sign back afterwards, so -7/2 half-up is -4. /// /// # Panics /// Panics on a malformed number, a zero denominator or an unknown mode. pub fn round_div_big(numerator: &str, denominator: &str, mode: &str) -> String { let n0 = parse_big_integer(numerator); let d0 = parse_big_integer(denominator); if d0.is_zero() { panic!("denominator must not be zero"); } let negative = n0.is_negative() != d0.is_negative(); let n = n0.abs(); let d = d0.abs(); let (mut quotient, remainder) = n.div_rem(&d); let twice = remainder.add(&remainder); let one = BigInt::from_i64(1); match mode { "down" => {} "up" => { if !remainder.is_zero() { quotient = quotient.add(&one); } } "half-even" => { let odd = !quotient.rem(&BigInt::from_i64(2)).is_zero(); if twice > d || (twice == d && odd) { quotient = quotient.add(&one); } } "half-up" => { if twice >= d { quotient = quotient.add(&one); } } other => panic!("unknown rounding mode \"{}\"", other), } if negative && !quotient.is_zero() { quotient = quotient.neg(); } quotient.to_string() } pub fn fune_vector(args: &[Value]) -> Value { for (i, name) in [(0usize, "numerator"), (1usize, "denominator")] { if !matches!(args[i], Value::Str(_)) { panic!("not a whole number in decimal: {} must be a string", name); } } Value::str(&round_div_big(args[0].as_str(), args[1].as_str(), args[2].as_str())) }