#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(uncommon_codepoints, mixed_script_confusables)]
use num_traits::float::Float;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Sub, SubAssign};
pub fn two_sum<T: Float>(a: T, b: T) -> (T, T) {
let s = a + b;
let aʹ = s - b;
let bʹ = s - aʹ;
let δa = a - aʹ;
let δb = b - bʹ;
let t = δa + δb;
(s, t)
}
fn two_sub<T: Float>(a: T, b: T) -> (T, T) {
let s = a - b;
let aʹ = s + b;
let bʹ = aʹ - s;
let δa = a - aʹ;
let δb = b - bʹ;
let t = δa - δb;
(s, t)
}
pub fn fast_two_sum<T: Float>(a: T, b: T) -> (T, T) {
let s = a + b;
let bʹ = s - a;
let δb = b - bʹ;
(s, δb)
}
#[derive(Clone, Debug, PartialEq)]
pub struct KahanBabuska<T> {
pub sum: T,
pub comp: T,
}
impl<T: Float> KahanBabuska<T> {
pub fn new() -> Self {
Self {
sum: T::zero(),
comp: T::zero(),
}
}
pub fn total(&self) -> T {
self.sum + self.comp
}
}
impl<T: Float> Default for KahanBabuska<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Float> Add<T> for KahanBabuska<T> {
type Output = Self;
fn add(mut self, rhs: T) -> Self::Output {
self += rhs;
self
}
}
impl<T: Float> AddAssign<T> for KahanBabuska<T> {
fn add_assign(&mut self, rhs: T) {
let (s, c) = fast_two_sum(self.sum, rhs + self.comp);
self.sum = s;
self.comp = c;
}
}
impl<T: Float> Sub<T> for KahanBabuska<T> {
type Output = Self;
fn sub(mut self, rhs: T) -> Self::Output {
self -= rhs;
self
}
}
impl<T: Float> SubAssign<T> for KahanBabuska<T> {
fn sub_assign(&mut self, rhs: T) {
let (s, c) = fast_two_sum(self.sum, self.comp - rhs);
self.sum = s;
self.comp = c;
}
}
impl<T: Float> Add<&T> for KahanBabuska<T> {
type Output = Self;
fn add(self, rhs: &T) -> Self::Output {
self + *rhs
}
}
impl<T: Float> AddAssign<&T> for KahanBabuska<T> {
fn add_assign(&mut self, rhs: &T) {
*self += *rhs;
}
}
impl<T: Float> Sub<&T> for KahanBabuska<T> {
type Output = Self;
fn sub(self, rhs: &T) -> Self::Output {
self - *rhs
}
}
impl<T: Float> SubAssign<&T> for KahanBabuska<T> {
fn sub_assign(&mut self, rhs: &T) {
*self -= *rhs;
}
}
impl<T: Float, V> Sum<V> for KahanBabuska<T>
where
Self: AddAssign<V>,
{
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = V>,
{
let mut sum = KahanBabuska::new();
for x in iter {
sum += x;
}
sum
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct KahanBabuskaNeumaier<T> {
pub sum: T,
pub comp: T,
}
impl<T: Float> KahanBabuskaNeumaier<T> {
pub fn new() -> Self {
Self {
sum: T::zero(),
comp: T::zero(),
}
}
pub fn total(&self) -> T {
self.sum + self.comp
}
}
impl<T: Float> Default for KahanBabuskaNeumaier<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Float> Add<T> for KahanBabuskaNeumaier<T> {
type Output = Self;
fn add(mut self, rhs: T) -> Self::Output {
self += rhs;
self
}
}
impl<T: Float> AddAssign<T> for KahanBabuskaNeumaier<T> {
fn add_assign(&mut self, rhs: T) {
let (s, c) = two_sum(self.sum, rhs);
self.sum = s;
self.comp = self.comp + c;
}
}
impl<T: Float> Sub<T> for KahanBabuskaNeumaier<T> {
type Output = Self;
fn sub(mut self, rhs: T) -> Self::Output {
self -= rhs;
self
}
}
impl<T: Float> SubAssign<T> for KahanBabuskaNeumaier<T> {
fn sub_assign(&mut self, rhs: T) {
let (s, c) = two_sub(self.sum, rhs);
self.sum = s;
self.comp = self.comp + c;
}
}
impl<T: Float> Add<&T> for KahanBabuskaNeumaier<T> {
type Output = Self;
fn add(self, rhs: &T) -> Self::Output {
self + *rhs
}
}
impl<T: Float> AddAssign<&T> for KahanBabuskaNeumaier<T> {
fn add_assign(&mut self, rhs: &T) {
*self += *rhs;
}
}
impl<T: Float> Sub<&T> for KahanBabuskaNeumaier<T> {
type Output = Self;
fn sub(self, rhs: &T) -> Self::Output {
self - *rhs
}
}
impl<T: Float> SubAssign<&T> for KahanBabuskaNeumaier<T> {
fn sub_assign(&mut self, rhs: &T) {
*self -= *rhs;
}
}
impl<T: Float, V> Sum<V> for KahanBabuskaNeumaier<T>
where
Self: AddAssign<V>,
{
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = V>,
{
let mut sum = KahanBabuskaNeumaier::new();
for x in iter {
sum += x;
}
sum
}
}
pub type KahanBabuška<T> = KahanBabuska<T>;
pub type KahanBabuškaNeumaier<T> = KahanBabuskaNeumaier<T>;
#[cfg(any(test, feature = "dev"))]
pub mod dev;
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn test_two_sum() {
use rand::prelude::*;
use rand_distr::LogNormal;
use rand_xoshiro::Xoshiro256PlusPlus;
let mut rng = Xoshiro256PlusPlus::seed_from_u64(42);
let dist = LogNormal::new(0.0, 40.0).unwrap();
for _ in 0..1000 {
let a = rng.sample(dist);
let b = rng.sample(dist);
assert_eq!(two_sum(a, b), dev::abs_two_sum(a, b));
}
}
#[test]
fn test_two_sub() {
use rand::prelude::*;
use rand_distr::LogNormal;
use rand_xoshiro::Xoshiro256PlusPlus;
let mut rng = Xoshiro256PlusPlus::seed_from_u64(42);
let dist = LogNormal::new(0.0, 40.0).unwrap();
for _ in 0..1000 {
let a = rng.sample(dist);
let b = rng.sample(dist);
assert_eq!(two_sub(a, b), two_sum(a, -b));
}
}
#[test]
fn kahan_123() {
let mut k = KahanBabuska::new();
let mut s = 0.0;
k += 0.1;
s += 0.1;
k += 0.2;
s += 0.2;
k += -0.3;
s += -0.3;
assert_eq!(k.sum, 0.0);
assert_eq!(k.comp, 0.0);
assert_eq!(s, f64::EPSILON / 4.);
}
#[test]
fn kahan_tenth() {
let mut k = KahanBabuska::new();
let mut s = 0.0;
for _ in 0..10 {
k += 0.1;
s += 0.1;
}
k += -1.0;
s += -1.0;
assert_eq!(k.sum, 0.0);
assert_eq!(k.comp, 0.0);
assert_eq!(s, -f64::EPSILON / 2.);
}
#[test]
fn kahan_123_iter() {
assert_eq!(
[0.1, 0.2, -0.3].iter().sum::<KahanBabuska<f64>>().total(),
0.0
);
assert_eq!(
[0.1, 0.2, -0.3]
.iter()
.cloned()
.sum::<KahanBabuska<f64>>()
.total(),
0.0
);
}
#[test]
fn kahan_tenth_iter() {
assert_eq!([0.1; 10].iter().sum::<KahanBabuska<f64>>().total(), 1.0);
assert_eq!(
[0.1; 10].iter().cloned().sum::<KahanBabuska<f64>>().total(),
1.0
);
}
#[test]
fn kahan_large() {
assert_eq!(
[1.0, 1e100, 1.0, -1e100]
.iter()
.sum::<KahanBabuska<f64>>()
.total(),
0.0
);
}
#[test]
fn neumaier_large() {
assert_eq!(
[1.0, 1e100, 1.0, -1e100]
.iter()
.sum::<KahanBabuskaNeumaier<f64>>()
.total(),
2.0
);
}
#[test]
fn test_correctness() {
use rand::prelude::*;
use rand_distr::LogNormal;
use rand_xoshiro::Xoshiro256PlusPlus;
for seed in 0..100 {
let rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let values: Vec<f64> = rng
.sample_iter(LogNormal::new(0.0, 40.0).unwrap())
.take(1_000)
.collect();
assert_eq!(
values.iter().sum::<KahanBabuska<_>>().total(),
dev::kahan_babuska_sum(values.iter().cloned())
);
assert_eq!(
values.iter().sum::<KahanBabuskaNeumaier<_>>().total(),
dev::kahan_babuska_neumaier_sum(values.iter().cloned())
);
assert_eq!(
values.iter().sum::<KahanBabuskaNeumaier<_>>().total(),
dev::kahan_babuska_neumaier_abs_sum(values.iter().cloned())
);
assert_eq!(
values.iter().sum::<KahanBabuskaNeumaier<_>>().total(),
dev::kahan_babuska_neumaier_abs_two_sum(values.iter().cloned())
);
}
}
}