#[inline]
#[must_use]
pub fn two_sum(a: f64, b: f64) -> (f64, f64) {
let sum = a + b;
let b_virtual = sum - a;
let a_virtual = sum - b_virtual;
let b_roundoff = b - b_virtual;
let a_roundoff = a - a_virtual;
(sum, a_roundoff + b_roundoff)
}
#[inline]
#[must_use]
pub fn two_diff(a: f64, b: f64) -> (f64, f64) {
let difference = a - b;
let b_virtual = a - difference;
let a_virtual = difference + b_virtual;
let b_roundoff = b_virtual - b;
let a_roundoff = a - a_virtual;
(difference, a_roundoff + b_roundoff)
}
const SPLITTER: f64 = 134_217_729.0;
#[inline]
#[must_use]
fn split(value: f64) -> (f64, f64) {
let c = SPLITTER * value;
let big = c - value;
let high = c - big;
(high, value - high)
}
#[inline]
#[must_use]
pub fn two_product(a: f64, b: f64) -> (f64, f64) {
let product = a * b;
let (a_high, a_low) = split(a);
let (b_high, b_low) = split(b);
let error = a_low * b_low - (((product - a_high * b_high) - a_low * b_high) - a_high * b_low);
(product, error)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn two_sum_is_exact_where_plain_addition_is_not() {
let a = 1.0;
let b = 2.0_f64.powi(-60);
assert_eq!(a + b, 1.0, "precondition: the naive sum loses b entirely");
let (sum, error) = two_sum(a, b);
assert_eq!(sum, 1.0);
assert_eq!(error, b, "the lost addend must survive as the error term");
}
#[test]
fn two_diff_is_exact_where_plain_subtraction_is_not() {
let a = 1.0;
let b = 2.0_f64.powi(-60);
assert_eq!(a - b, 1.0, "precondition: the naive difference loses b");
let (difference, error) = two_diff(a, b);
assert_eq!(difference, 1.0);
assert_eq!(error, -b);
}
#[test]
fn two_product_recovers_the_bits_a_single_f64_cannot_hold() {
let a = 1.0 + 2.0_f64.powi(-52);
let b = 1.0 + 2.0_f64.powi(-52);
let (product, error) = two_product(a, b);
assert_ne!(error, 0.0, "a rounded product must report its lost bits");
assert_eq!(product, 1.0 + 2.0_f64.powi(-51));
assert_eq!(error, 2.0_f64.powi(-104));
}
#[test]
fn splitting_produces_non_overlapping_halves() {
let value = 1.0 + 2.0_f64.powi(-52);
let (high, low) = split(value);
assert_eq!(high + low, value, "the split must be lossless");
}
#[test]
fn transformations_are_exact_across_many_magnitudes() {
let mut state = 0x2545_F491_4F6C_DD1D_u64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let mantissa = f64::from(((state >> 32) as u32) as i32) / f64::from(i32::MAX);
let exponent = ((state >> 8) % 40) as i32 - 20;
mantissa * 2.0_f64.powi(exponent)
};
for _ in 0..2_000 {
let (a, b) = (next(), next());
let (sum, error) = two_sum(a, b);
assert_eq!(sum + error, a + b);
let (product, perror) = two_product(a, b);
assert!(product.is_finite() && perror.is_finite());
assert_eq!(product + perror, a * b + (perror + (product - a * b)));
}
}
}