use super::*;
#[test]
fn test_kahan_accuracy_advantage() {
let n = 10_000;
let big = 1.0f32;
let tiny = 1e-8f32;
let mut plain = big;
for _ in 0..n {
plain += tiny;
}
let plain_error = (plain - (big + n as f32 * tiny)).abs();
let mut acc = KahanF32::new(big);
for _ in 0..n {
acc.add(tiny);
}
let kahan_error = (acc.value() - (big + n as f32 * tiny)).abs();
assert!(
kahan_error < plain_error,
"Kahan error {} should be smaller than plain error {}",
kahan_error,
plain_error
);
}
#[test]
fn test_kahan_struct_vs_inline() {
let n = 1000;
let mut acc_struct = KahanF32::new(0.0);
let mut sum_inline = 0.0f32;
let mut comp_inline = 0.0f32;
for i in 1..=n {
let x = (i as f32).sqrt();
acc_struct.add(x);
let (s, c) = kahan_add(sum_inline, comp_inline, x);
sum_inline = s;
comp_inline = c;
}
let diff = (acc_struct.value() - sum_inline).abs();
assert!(diff < 1e-6, "struct vs inline divergence: {}", diff);
}
#[test]
fn test_kahan4_independent_channels() {
let mut acc = Kahan4F32::new([0.0; 4]);
for _ in 0..100 {
acc.add([1e-8, 1.0, -1e-8, 100.0]);
}
let v = acc.value();
assert!((v[0] - 1e-6).abs() < 1e-12, "ch0");
assert!((v[1] - 100.0).abs() < 1e-6, "ch1");
assert!((v[2] + 1e-6).abs() < 1e-12, "ch2");
assert!((v[3] - 10000.0).abs() < 1e-3, "ch3");
}
#[test]
fn test_kahan_deep_convolution_drift() {
let total_terms: usize = 15 * 3 * 256;
let mut rng_state: u32 = 0xDEAD_BEEF;
let mut next_f32 = move || -> f32 {
rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
let bits = (rng_state >> 9) | 0x3F80_0000;
f32::from_bits(bits) - 1.0
};
let mut f64_sum = 0.0f64;
let mut terms = Vec::with_capacity(total_terms);
for i in 0..total_terms {
let large = (next_f32() * 1e4).abs() + 1e3;
let tiny = next_f32() * 1e-7;
let term = if i % 128 == 0 {
large
} else if i % 64 == 0 {
large * 0.1
} else {
tiny
};
terms.push(term);
f64_sum += term as f64;
}
let mut plain_sum = 0.0f32;
for &t in &terms {
plain_sum += t;
}
let mut kahan_sum = 0.0f32;
let mut comp = 0.0f32;
for &t in &terms {
let (s, c) = kahan_add(kahan_sum, comp, t);
kahan_sum = s;
comp = c;
}
let plain_err = ((plain_sum as f64 - f64_sum).abs()) / f64_sum.abs().max(1e-30);
let kahan_err = ((kahan_sum as f64 - f64_sum).abs()) / f64_sum.abs().max(1e-30);
let plain_drift_db = 20.0 * plain_err.max(1e-30).log10();
let kahan_drift_db = 20.0 * kahan_err.max(1e-30).log10();
let improvement_db = plain_drift_db - kahan_drift_db;
eprintln!(
"Deep conv drift ({total_terms} terms, {:.3e} f64 sum):",
f64_sum
);
eprintln!(
" Plain f32: {:.12e} (err={:.3e}, drift={:.2} dB)",
plain_sum, plain_err, plain_drift_db
);
eprintln!(
" Kahan f32: {:.12e} (err={:.3e}, drift={:.2} dB)",
kahan_sum, kahan_err, kahan_drift_db
);
eprintln!(" Improvement: {:.2} dB", improvement_db);
assert!(
improvement_db >= 2.0,
"Kahan improvement ({:.2} dB) < 2 dB",
improvement_db
);
}
#[test]
fn test_horizontal_sum_drift_reduction() {
let n: usize = 1_000_000;
let base = 1.0f32;
let tiny = 1e-8f32;
let f64_ref = base as f64 + n as f64 * tiny as f64;
let plain_sum: f32 = {
let mut s = base;
for _ in 0..n {
s += tiny;
}
s
};
let mut data = vec![base];
data.extend(std::iter::repeat_n(tiny, n));
let kahan_sum = unsafe {
crate::math::common::scalar_ref::utility::horizontal_sum_fallback(data.as_ptr(), data.len())
};
let plain_err = ((plain_sum as f64 - f64_ref).abs()) / f64_ref.abs();
let kahan_err = ((kahan_sum as f64 - f64_ref).abs()) / f64_ref.abs();
let improvement_ratio = plain_err / kahan_err.max(1e-30);
eprintln!("1M-sample Kahan drift test (f64 ref = {:.12}):", f64_ref);
eprintln!(" Plain f32: {:.12} (err={:.3e})", plain_sum, plain_err);
eprintln!(" Kahan f32: {:.12} (err={:.3e})", kahan_sum, kahan_err);
eprintln!(" Improvement ratio: {:.1}×", improvement_ratio);
assert!(
improvement_ratio >= 100.0,
"Kahan improvement ratio ({:.1}×) < 100×",
improvement_ratio
);
}