Files
improvise/crates/numtower/tests/zero_sign.rs
T
2026-10-10 12:11:47 -07:00

90 lines
2.7 KiB
Rust

//! ADR 009 step 8's last test: the sign of a zero sum is IEEE's. Evidence: `context/test-evidence/numtower.md`.
use numtower::ExactSum;
use proptest::prelude::*;
fn addend() -> impl Strategy<Value = f64> {
prop_oneof![
Just(-0.0),
Just(0.0),
Just(0.5),
Just(-0.5),
Just(3.0),
Just(-3.0),
]
}
/// IEEE addition from `-0.0`, the identity that keeps a `-0.0` addend's sign.
fn ieee(addends: &[f64]) -> f64 {
addends.iter().fold(-0.0, |sum, x| sum + x)
}
fn exact(addends: &[f64]) -> f64 {
let mut sum = ExactSum::new();
addends.iter().for_each(|x| sum.add(*x, 1));
sum.value()
}
#[test]
fn a_sum_of_negative_zeros_is_negative_zero() {
assert_eq!(exact(&[-0.0, -0.0]).to_bits(), (-0.0f64).to_bits());
assert_eq!(exact(&[-0.0]).to_bits(), (-0.0f64).to_bits());
}
#[test]
fn one_positive_zero_makes_the_zero_positive_and_so_does_a_cancellation() {
assert_eq!(exact(&[-0.0, 0.0]).to_bits(), 0.0f64.to_bits());
assert_eq!(exact(&[0.5, -0.5]).to_bits(), 0.0f64.to_bits());
assert_eq!(exact(&[-0.0, 0.5, -0.5]).to_bits(), 0.0f64.to_bits());
}
#[test]
fn an_empty_sum_is_positive_zero() {
assert_eq!(ExactSum::new().value().to_bits(), 0.0f64.to_bits());
}
/// Addends whose exact sum is zero: some zeros of either sign, and cancelling pairs.
fn zero_sum() -> impl Strategy<Value = Vec<f64>> {
let zeros = proptest::collection::vec(prop_oneof![Just(-0.0), Just(0.0)], 0..5);
let pairs = proptest::collection::vec(prop_oneof![Just(0.5), Just(-3.0)], 0..3);
(zeros, pairs)
.prop_map(|(mut all, pairs)| {
all.extend(pairs.iter().flat_map(|p| [*p, -*p]));
all
})
.prop_filter("non-empty", |all| !all.is_empty())
.prop_shuffle()
}
proptest! {
#[test]
fn a_zero_sum_has_the_sign_ieee_gives_the_same_addends(addends in zero_sum()) {
let want = ieee(&addends);
prop_assert_eq!(want, 0.0, "the generator must build zero sums");
prop_assert_eq!(exact(&addends).to_bits(), want.to_bits());
}
#[test]
fn a_retracted_addend_leaves_the_sign_of_the_rest(
keep in zero_sum(),
gone in proptest::collection::vec(addend(), 0..6),
) {
let mut sum = ExactSum::new();
keep.iter().chain(&gone).for_each(|x| sum.add(*x, 1));
gone.iter().for_each(|x| sum.add(*x, -1));
prop_assert_eq!(sum.value().to_bits(), ieee(&keep).to_bits());
let mut fresh = ExactSum::new();
keep.iter().for_each(|x| fresh.add(*x, 1));
prop_assert_eq!(sum, fresh);
}
}
#[test]
fn a_sum_that_is_not_zero_never_reads_as_negative_zero() {
let mut sum = ExactSum::new();
sum.add(-0.0, 1);
sum.add(5.0, 1);
sum.add(7.0, -1);
assert_eq!(sum.value(), -2.0);
}