use super::funejson::Value; 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 = args[0].as_arr().iter().map(|v| v.as_f64()).collect(); Value::Float(standard_deviation(&values, args[1].as_str(), args[2].as_i64())) }