pub fn finite(xs: &[f64]) -> Vec<f64> {
xs.iter().copied().filter(|x| x.is_finite()).collect()
}
pub fn det_sum(xs: &[f64]) -> f64 {
let mut sorted: Vec<f64> = xs.to_vec();
sorted.sort_by(|a, b| a.total_cmp(b));
let mut sum = 0.0_f64;
let mut comp = 0.0_f64; for &x in &sorted {
let t = sum + x;
if sum.abs() >= x.abs() {
comp += (sum - t) + x;
} else {
comp += (x - t) + sum;
}
sum = t;
}
sum + comp
}
pub fn mean(xs: &[f64]) -> Option<f64> {
if xs.is_empty() {
return None;
}
Some(det_sum(xs) / xs.len() as f64)
}
pub fn variance(xs: &[f64]) -> Option<f64> {
let n = xs.len();
if n < 2 {
return None;
}
let m = mean(xs)?;
let sq: Vec<f64> = xs
.iter()
.map(|x| {
let d = x - m;
d * d
})
.collect();
Some(det_sum(&sq) / (n - 1) as f64)
}
pub fn std_dev(xs: &[f64]) -> Option<f64> {
variance(xs).map(f64::sqrt)
}
pub fn quantile(xs: &[f64], q: f64) -> Option<f64> {
if xs.is_empty() {
return None;
}
let q = q.clamp(0.0, 1.0);
let mut sorted: Vec<f64> = xs.to_vec();
sorted.sort_by(|a, b| a.total_cmp(b));
let n = sorted.len();
if n == 1 {
return Some(sorted[0]);
}
let pos = q * (n - 1) as f64;
let lo = pos.floor() as usize;
let hi = pos.ceil() as usize;
if lo == hi {
return Some(sorted[lo]);
}
let frac = pos - lo as f64;
Some(sorted[lo] * (1.0 - frac) + sorted[hi] * frac)
}
pub fn median(xs: &[f64]) -> Option<f64> {
quantile(xs, 0.5)
}
pub fn mad(xs: &[f64]) -> Option<f64> {
let med = median(xs)?;
let dev: Vec<f64> = xs.iter().map(|x| (x - med).abs()).collect();
median(&dev).map(|m| 1.4826 * m)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finite_drops_non_finite_keeps_order() {
let xs = [1.0, f64::NAN, 2.0, f64::INFINITY, -f64::INFINITY, 3.0];
assert_eq!(finite(&xs), vec![1.0, 2.0, 3.0]);
}
#[test]
fn det_sum_exact_value() {
assert_eq!(det_sum(&[1.0, 2.0, 3.0, 4.0]), 10.0);
assert_eq!(det_sum(&[]), 0.0);
}
#[test]
fn det_sum_compensation_recovers_small_term() {
assert_eq!(det_sum(&[1e16, 1.0, -1e16]), 1.0);
assert_eq!(det_sum(&[-1e16, 1e16, 1.0]), 1.0);
}
#[test]
fn det_sum_else_branch_compensation_is_exact() {
assert_eq!(det_sum(&[0.1, 0.2, 0.3]), 0.6);
let naive = std::hint::black_box([0.1, 0.2, 0.3]).iter().sum::<f64>();
assert_ne!(naive, 0.6);
}
#[test]
fn det_sum_is_permutation_invariant() {
let a = [1.0, 2.0, 3.0, 1e16, -1e16, 4.0];
let mut b = a;
b.reverse();
assert_eq!(det_sum(&a).to_bits(), det_sum(&b).to_bits());
}
#[test]
fn variance_and_std_exact() {
assert_eq!(variance(&[2.0, 4.0, 6.0]), Some(4.0));
assert_eq!(std_dev(&[2.0, 4.0, 6.0]), Some(2.0));
}
#[test]
fn mad_of_spread_is_nonzero_exact() {
assert_eq!(mad(&[1.0, 2.0, 3.0, 4.0, 5.0]), Some(1.4826));
}
#[test]
fn mean_of_empty_is_none() {
assert_eq!(mean(&[]), None);
}
#[test]
fn variance_needs_two() {
assert_eq!(variance(&[5.0]), None);
assert_eq!(variance(&[2.0, 4.0]), Some(2.0));
}
#[test]
fn quantile_endpoints_and_middle() {
let xs = [1.0, 2.0, 3.0, 4.0];
assert_eq!(quantile(&xs, 0.0), Some(1.0));
assert_eq!(quantile(&xs, 1.0), Some(4.0));
assert_eq!(median(&xs), Some(2.5));
}
#[test]
fn mad_of_constant_is_zero() {
assert_eq!(mad(&[7.0, 7.0, 7.0]), Some(0.0));
}
}