#[must_use]
pub fn sum_neumaier(xs: &[f64]) -> f64 {
let mut sum = 0.0_f64;
let mut comp = 0.0_f64; for &x in xs {
let t = sum + x;
if sum.abs() >= x.abs() {
comp += (sum - t) + x;
} else {
comp += (x - t) + sum;
}
sum = t;
}
sum + comp
}
#[must_use]
pub fn sum_pairwise(xs: &[f64]) -> f64 {
const BASE: usize = 32;
if xs.len() <= BASE {
return xs.iter().sum();
}
let mid = xs.len() / 2;
sum_pairwise(&xs[..mid]) + sum_pairwise(&xs[mid..])
}
#[must_use]
pub fn dot_compensated(a: &[f64], b: &[f64]) -> f64 {
assert!(a.len() == b.len(), "dot_compensated requires equal-length slices");
let mut sum = 0.0_f64;
let mut comp = 0.0_f64;
for (&x, &y) in a.iter().zip(b.iter()) {
let p = x * y;
let t = sum + p;
if sum.abs() >= p.abs() {
comp += (sum - t) + p;
} else {
comp += (p - t) + sum;
}
sum = t;
}
sum + comp
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_neumaier_exact_small() {
assert_eq!(sum_neumaier(&[1.0, 2.0, 3.0]), 6.0);
assert_eq!(sum_neumaier(&[]), 0.0);
}
#[test]
fn test_sum_neumaier_catastrophic_case() {
let xs = [1.0, 1e100, 1.0, -1e100];
assert_eq!(sum_neumaier(&xs), 2.0);
}
#[test]
fn test_sum_neumaier_many_tenths() {
let xs = vec![0.1_f64; 1_000_000];
assert!((sum_neumaier(&xs) - 1e5).abs() < 1e-9);
}
#[test]
fn test_sum_pairwise_matches_exact() {
let xs: Vec<f64> = (1..=1000).map(|i| i as f64).collect();
assert_eq!(sum_pairwise(&xs), 500_500.0);
assert_eq!(sum_pairwise(&[]), 0.0);
assert_eq!(sum_pairwise(&[4.5]), 4.5);
}
#[test]
fn test_sum_pairwise_better_than_naive() {
let xs = vec![0.1_f64; 1_000_000];
let naive: f64 = xs.iter().sum();
let pairwise = sum_pairwise(&xs);
assert!((pairwise - 1e5).abs() < (naive - 1e5).abs());
}
#[test]
fn test_dot_compensated() {
let a = [1.0, 2.0, 3.0];
let b = [4.0, 5.0, 6.0];
assert_eq!(dot_compensated(&a, &b), 32.0);
}
#[test]
#[should_panic(expected = "equal-length")]
fn test_dot_compensated_length_mismatch() {
let _ = dot_compensated(&[1.0], &[1.0, 2.0]);
}
}