pub fn kahan_sum(values: &[f32]) -> f32 {
let mut sum = 0.0_f32;
let mut compensation = 0.0_f32;
for &val in values {
let y = val - compensation;
let t = sum + y;
compensation = (t - sum) - y;
sum = t;
}
sum
}
pub fn kahan_sum_f64(values: &[f64]) -> f64 {
let mut sum = 0.0_f64;
let mut compensation = 0.0_f64;
for &val in values {
let y = val - compensation;
let t = sum + y;
compensation = (t - sum) - y;
sum = t;
}
sum
}
pub fn kahan_mean(values: &[f32]) -> f32 {
if values.is_empty() {
return 0.0;
}
kahan_sum(values) / values.len() as f32
}
pub fn kahan_variance(values: &[f32]) -> f32 {
if values.len() < 2 {
return 0.0;
}
let mean = kahan_mean(values);
let sq_diffs: Vec<f32> = values.iter().map(|&x| (x - mean) * (x - mean)).collect();
kahan_sum(&sq_diffs) / values.len() as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kahan_sum_basic() {
let values = vec![1.0, 2.0, 3.0, 4.0];
assert!((kahan_sum(&values) - 10.0).abs() < 1e-7);
}
#[test]
fn test_kahan_sum_accumulated_error() {
let values: Vec<f32> = vec![0.1; 100_000];
let naive: f32 = values.iter().sum();
let compensated = kahan_sum(&values);
let expected = 10_000.0_f32;
let kahan_err = (compensated - expected).abs();
let naive_err = (naive - expected).abs();
assert!(
kahan_err <= naive_err + 1e-3,
"Kahan error {kahan_err} should be <= naive error {naive_err}"
);
assert!(
kahan_err < 0.01,
"Kahan sum = {compensated}, expected ~{expected}, error = {kahan_err}"
);
}
#[test]
fn test_kahan_sum_many_small_values() {
let values: Vec<f32> = vec![1e-7; 1_000_000];
let compensated = kahan_sum(&values);
let expected = 0.1_f32;
assert!(
(compensated - expected).abs() < 1e-6,
"Kahan sum of 1M * 1e-7 = {compensated}, expected ~{expected}"
);
}
#[test]
fn test_kahan_sum_empty() {
assert_eq!(kahan_sum(&[]), 0.0);
}
#[test]
fn test_kahan_sum_single() {
assert_eq!(kahan_sum(&[42.0]), 42.0);
}
#[test]
fn test_kahan_mean() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((kahan_mean(&values) - 3.0).abs() < 1e-7);
}
#[test]
fn test_kahan_variance() {
let values = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let var = kahan_variance(&values);
assert!((var - 4.0).abs() < 1e-5, "Kahan variance = {var}, expected 4.0");
}
#[test]
fn test_kahan_sum_f64_precision() {
let values: Vec<f64> = vec![0.1; 1_000_000];
let compensated = kahan_sum_f64(&values);
let expected = 100_000.0_f64;
assert!(
(compensated - expected).abs() < 1e-8,
"Kahan f64 sum = {compensated}, expected {expected}"
);
}
#[test]
fn verification_test_kahan_analytical_solution() {
let n = 1000;
let values: Vec<f32> = (1..=n).map(|i| i as f32).collect();
let analytical_solution = (n * (n + 1) / 2) as f32;
let computed = kahan_sum(&values);
let tolerance = 1e-3;
assert!(
(computed - analytical_solution).abs() < tolerance,
"convergence_test: computed={computed}, exact={analytical_solution}"
);
}
#[test]
fn test_kahan_vs_naive_accuracy() {
let n = 10_000;
let values: Vec<f32> = (0..n).map(|i| if i % 2 == 0 { 1e6 } else { 1e-6 }).collect();
let kahan = kahan_sum(&values);
let naive: f32 = values.iter().sum();
let exact = (n / 2) as f32 * 1e6 + (n / 2) as f32 * 1e-6;
let kahan_err = (kahan - exact).abs();
let naive_err = (naive - exact).abs();
assert!(
kahan_err <= naive_err + 1e-3,
"Kahan error {kahan_err} should be <= naive error {naive_err}"
);
}
}