Functional Weave
Code in TypeScript

stats.standard-deviation@1.0.0

impl/rust.rs

2,347 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,
];

// Half away from zero on the binary64 value. A deviation is never negative,
// so there is no sign to restore.
fn round_to(x: f64, decimals: usize) -> f64 {
    let scale = POW10[decimals];
    let y = x * scale;
    let mut r = y.floor();
    if y - r >= 0.5 {
        r += 1.0;
    }
    r / scale + 0.0
}

/// Population or sample standard deviation, two-pass.
///
/// The one-pass "mean of squares minus square of mean" shortcut cancels
/// catastrophically on large, close values; two passes do not.
///
/// # Panics
/// Panics on a non-finite value, an unknown kind, decimals outside 0..=12, an
/// empty list, or a sample of fewer than two values.
pub fn standard_deviation(values: &[f64], kind: &str, decimals: i64) -> f64 {
    for v in values {
        if !v.is_finite() {
            panic!("values must be finite numbers, received {}", v);
        }
    }
    if kind != "population" && kind != "sample" {
        panic!("unknown standard deviation kind \"{}\"", kind);
    }
    if !(0..=12).contains(&decimals) {
        panic!("decimals must be a whole number from 0 to 12, received {}", decimals);
    }
    let n = values.len();
    if n == 0 {
        panic!("values must not be empty");
    }
    if kind == "sample" && n < 2 {
        panic!("sample standard deviation needs at least 2 values");
    }

    let mut sum = 0.0;
    for v in values {
        sum += *v;
    }
    let mean = sum / n as f64;
    let mut squares = 0.0;
    for v in values {
        let d = *v - mean;
        squares += d * d;
    }
    let divisor = if kind == "sample" { n - 1 } else { n } as f64;
    round_to((squares / divisor).sqrt(), decimals as usize)
}

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.
    for v in args[0].as_arr() {
        if !matches!(v, Value::Int(_) | Value::Float(_)) {
            panic!("values must be finite numbers, received {:?}", v);
        }
    }
    let values: Vec<f64> = args[0].as_arr().iter().map(|v| v.as_f64()).collect();
    Value::Float(standard_deviation(&values, args[1].as_str(), args[2].as_i64()))
}