Functional Weave
Code in Rust

math.round-float@1.0.0

impl/rust.rs

2,286 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

const POW10: [f64; 13] = [
    1.0, 10.0, 100.0, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12,
];

// 2^52: at or above this every double is a whole number, so a scaled value
// this large has no digits left to round.
const INTEGRAL: f64 = 4503599627370496.0;
// Veltkamp's splitting constant, 2^27 + 1.
const SPLIT: f64 = 134217729.0;

/// a * b - fl(a * b), exactly (Dekker's TwoProduct), with only * and -.
/// `mul_add` would do it in one step, but JavaScript has no equivalent, and
/// the point is the same operations everywhere.
fn product_error(a: f64, b: f64, p: f64) -> f64 {
    let ca = SPLIT * a;
    let ah = ca - (ca - a);
    let al = a - ah;
    let cb = SPLIT * b;
    let bh = cb - (cb - b);
    let bl = b - bh;
    ((ah * bh - p) + ah * bl + al * bh) + al * bl
}

/// Round half away from zero to `decimals` places, deciding on the exact value
/// of the double: 2.675 is stored as 2.674999..., so it rounds to 2.67.
///
/// # Panics
/// Panics if `value` is not finite or `decimals` is outside 0..=12.
pub fn round_float(value: f64, decimals: i64) -> f64 {
    if !value.is_finite() {
        panic!("value must be a finite number, received {}", value);
    }
    if !(0..=12).contains(&decimals) {
        panic!("decimals must be a whole number from 0 to 12, received {}", decimals);
    }
    let scale = POW10[decimals as usize];
    let a = value.abs();
    let y = a * scale;
    if y >= INTEGRAL {
        return value + 0.0;
    }
    let mut r = y.floor();
    // y - r is exact, and so is subtracting a half from it; adding the
    // product's error cannot change the sign, only settle a tie.
    let above = (y - r - 0.5) + product_error(a, scale, y);
    if above >= 0.0 {
        r += 1.0;
    }
    let out = r / scale;
    // + 0.0 turns -0.0 into 0.0.
    (if value < 0.0 { -out } else { out }) + 0.0
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion below quietly change it.
    if matches!(args[1], Value::Float(f) if f.fract() != 0.0) {
        panic!("decimals must be a whole number from 0 to 12");
    }
    Value::Float(round_float(args[0].as_f64(), args[1].as_i64()))
}