pub const UNIT_ROUNDOFF: f64 = f64::EPSILON / 2.0;
pub fn accumulation_growth(operations: usize) -> f64 {
let scaled = operations as f64 * UNIT_ROUNDOFF;
if !(scaled < 1.0) {
return f64::INFINITY;
}
scaled / (1.0 - scaled)
}
pub fn accumulation_band(terms: usize, absolute_sum: f64) -> f64 {
accumulation_growth(terms) * absolute_sum
}
pub fn compensated_band(formation_roundings: usize, absolute_sum: f64) -> f64 {
(2.0 + formation_roundings as f64) * UNIT_ROUNDOFF * absolute_sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unit_roundoff_is_half_an_epsilon_gap() {
assert_eq!(UNIT_ROUNDOFF * 2.0, f64::EPSILON);
assert_eq!(1.0_f64 + UNIT_ROUNDOFF, 1.0);
assert!(1.0_f64 + 2.0 * UNIT_ROUNDOFF > 1.0);
}
#[test]
fn growth_is_zero_for_exact_arithmetic_and_grows_linearly() {
assert_eq!(accumulation_growth(0), 0.0);
let one = accumulation_growth(1);
assert!((one - UNIT_ROUNDOFF).abs() <= UNIT_ROUNDOFF * UNIT_ROUNDOFF * 4.0);
let mut previous = 0.0_f64;
for n in [1_usize, 8, 64, 1024, 1 << 20] {
let gamma = accumulation_growth(n);
assert!(gamma > previous, "gamma must be monotone in n");
assert!(gamma >= n as f64 * UNIT_ROUNDOFF);
assert!(gamma <= n as f64 * UNIT_ROUNDOFF * 1.000_001);
previous = gamma;
}
}
#[test]
fn compensated_band_is_the_naive_one_divided_by_the_term_count() {
assert_eq!(compensated_band(0, 1.0), 2.0 * UNIT_ROUNDOFF);
assert_eq!(compensated_band(3, 1.0), 5.0 * UNIT_ROUNDOFF);
assert_eq!(compensated_band(3, 4.0), 4.0 * compensated_band(3, 1.0));
let mut previous = 0.0_f64;
for terms in [100_usize, 2_500, 10_000] {
let ratio = accumulation_band(terms, 1.0) / compensated_band(3, 1.0);
assert!(ratio > previous, "the gap must widen with n");
let expected = terms as f64 / 5.0;
assert!(
(ratio / expected - 1.0).abs() < 1.0e-6,
"n={terms}: ratio {ratio} is not ~n/5 ({expected})"
);
previous = ratio;
}
}
#[test]
fn growth_saturates_rather_than_going_negative() {
let vacuous = (1.0 / UNIT_ROUNDOFF).ceil() as usize;
assert_eq!(accumulation_growth(vacuous), f64::INFINITY);
assert_eq!(accumulation_growth(usize::MAX), f64::INFINITY);
}
#[test]
fn band_bounds_a_cancelling_sum_that_is_exactly_zero() {
let magnitudes: Vec<f64> = (1..=256)
.map(|k| (k as f64) * 0.1_f64.powi(k % 7))
.collect();
let terms: Vec<f64> = magnitudes
.iter()
.copied()
.chain(magnitudes.iter().map(|value| -value))
.collect();
let absolute_sum: f64 = terms.iter().map(|value| value.abs()).sum();
let computed: f64 = terms.iter().sum();
let band = accumulation_band(terms.len(), absolute_sum);
assert!(band > 0.0);
assert!(
computed.abs() <= band,
"computed {computed:e} escaped its band {band:e}"
);
}
}