36dc3bc9eb
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
48 lines
1.7 KiB
Rust
48 lines
1.7 KiB
Rust
//! `ExactSum::mean`: the exact total over a count, rounded once, against exact rationals.
|
|
//! Evidence: `context/test-evidence/numtower.md`.
|
|
|
|
mod oracle;
|
|
|
|
use num_bigint::BigInt;
|
|
use num_rational::BigRational;
|
|
use numtower::ExactSum;
|
|
use oracle::{finite_f64, is_nearest};
|
|
use proptest::prelude::*;
|
|
|
|
fn sum_of(values: &[f64]) -> ExactSum {
|
|
let mut sum = ExactSum::new();
|
|
values.iter().for_each(|v| sum.add(*v, 1));
|
|
sum
|
|
}
|
|
|
|
#[test]
|
|
fn a_mean_whose_sum_would_overflow_is_still_finite() {
|
|
let sum = sum_of(&[f64::MAX, f64::MAX]);
|
|
assert_eq!(sum.mean(2), f64::MAX, "the sum is inf, the mean is not");
|
|
}
|
|
|
|
#[test]
|
|
fn non_finite_sums_have_non_finite_means_and_zero_keeps_its_sign() {
|
|
assert_eq!(sum_of(&[f64::INFINITY, 1.0]).mean(2), f64::INFINITY);
|
|
assert_eq!(sum_of(&[f64::NEG_INFINITY]).mean(3), f64::NEG_INFINITY);
|
|
assert!(sum_of(&[f64::NAN, 1.0]).mean(2).is_nan());
|
|
assert!(sum_of(&[f64::INFINITY, f64::NEG_INFINITY]).mean(2).is_nan());
|
|
assert_eq!(sum_of(&[-0.0, -0.0]).mean(2).to_bits(), (-0.0f64).to_bits());
|
|
assert_eq!(sum_of(&[1.0, -1.0]).mean(2).to_bits(), 0.0f64.to_bits());
|
|
}
|
|
|
|
proptest! {
|
|
#[test]
|
|
fn the_mean_is_the_nearest_f64_to_the_exact_total_over_the_count(
|
|
values in prop::collection::vec(finite_f64(), 1..9),
|
|
) {
|
|
let total = values
|
|
.iter()
|
|
.map(|v| BigRational::from_float(*v).unwrap())
|
|
.fold(BigRational::from_integer(BigInt::from(0)), |a, b| a + b);
|
|
let exact = total / BigRational::from_integer(BigInt::from(values.len()));
|
|
let got = sum_of(&values).mean(values.len() as u64);
|
|
prop_assert!(is_nearest(got, &exact), "{got} for {values:?}");
|
|
}
|
|
}
|