#[derive(Debug, Clone, Copy)]
pub struct KahanAccumulatorF64 {
sum: f64,
compensation: f64,
count: u64,
}
impl KahanAccumulatorF64 {
#[inline]
pub fn new() -> Self {
KahanAccumulatorF64 {
sum: 0.0,
compensation: 0.0,
count: 0,
}
}
#[inline]
pub fn add(&mut self, value: f64) {
if value == 0.0 {
self.count += 1;
return;
}
let y = value - self.compensation;
let t = self.sum + y;
self.compensation = (t - self.sum) - y;
self.sum = t;
self.count += 1;
}
#[inline]
pub fn add_slice(&mut self, values: &[f64]) {
for &v in values {
self.add(v);
}
}
#[inline]
pub fn finalize(&self) -> f64 {
self.sum
}
#[inline]
pub fn count(&self) -> u64 {
self.count
}
#[inline]
pub fn compensation_bits(&self) -> u64 {
self.compensation.to_bits()
}
#[inline]
pub fn from_components(sum: f64, compensation: f64, count: u64) -> Self {
Self {
sum,
compensation,
count,
}
}
}
impl Default for KahanAccumulatorF64 {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct KahanAccumulatorF32 {
sum: f32,
compensation: f32,
count: u64,
}
impl KahanAccumulatorF32 {
#[inline]
pub fn new() -> Self {
KahanAccumulatorF32 {
sum: 0.0,
compensation: 0.0,
count: 0,
}
}
#[inline]
pub fn add(&mut self, value: f32) {
if value == 0.0 {
self.count += 1;
return;
}
let y = value - self.compensation;
let t = self.sum + y;
self.compensation = (t - self.sum) - y;
self.sum = t;
self.count += 1;
}
#[inline]
pub fn add_slice(&mut self, values: &[f32]) {
for &v in values {
self.add(v);
}
}
#[inline]
pub fn finalize(&self) -> f32 {
self.sum
}
#[inline]
pub fn count(&self) -> u64 {
self.count
}
}
impl Default for KahanAccumulatorF32 {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kahan_accumulator_simple() {
let mut acc = KahanAccumulatorF64::new();
for i in 1..=10 {
acc.add(i as f64);
}
assert_eq!(acc.finalize(), 55.0);
assert_eq!(acc.count(), 10);
}
#[test]
fn test_kahan_accumulator_stability() {
let mut acc = KahanAccumulatorF64::new();
for _ in 0..10000 {
acc.add(0.0001);
}
assert!((acc.finalize() - 1.0).abs() < 1e-10);
}
#[test]
fn test_kahan_accumulator_f32() {
let mut acc = KahanAccumulatorF32::new();
for _ in 0..10000 {
acc.add(0.0001f32);
}
assert!((acc.finalize() - 1.0).abs() < 1e-4);
}
#[test]
fn test_kahan_matches_existing_function() {
let values: Vec<f64> = (1..=1000).map(|i| i as f64 * 0.001).collect();
let mut acc = KahanAccumulatorF64::new();
acc.add_slice(&values);
let func_result = crate::kahan_sum_f64(&values);
assert_eq!(acc.finalize().to_bits(), func_result.to_bits());
}
}