use super::{sort_by_magnitude, sum_equivariant};
fn bits<const N: usize>(t: [f64; N]) -> [u64; N] {
t.map(f64::to_bits)
}
const CANCELLING: [f64; 3] = [1.0e-16, 1.0, -1.0];
#[test]
fn the_sum_does_not_depend_on_the_order_the_terms_arrive_in() {
let mut seen = None;
for permutation in [
[CANCELLING[0], CANCELLING[1], CANCELLING[2]],
[CANCELLING[0], CANCELLING[2], CANCELLING[1]],
[CANCELLING[1], CANCELLING[0], CANCELLING[2]],
[CANCELLING[1], CANCELLING[2], CANCELLING[0]],
[CANCELLING[2], CANCELLING[0], CANCELLING[1]],
[CANCELLING[2], CANCELLING[1], CANCELLING[0]],
] {
let got = sum_equivariant(permutation);
match seen {
None => seen = Some(got),
Some(first) => assert_eq!(
got.to_bits(),
first.to_bits(),
"permutation {permutation:?} summed to {got:e}, not {first:e}"
),
}
}
}
#[test]
fn an_unsorted_sum_of_the_same_terms_really_does_disagree() {
let naive = |t: [f64; 3]| t[0] + t[1] + t[2];
assert_ne!(
naive([CANCELLING[0], CANCELLING[1], CANCELLING[2]]).to_bits(),
naive([CANCELLING[1], CANCELLING[2], CANCELLING[0]]).to_bits(),
"the fixture cannot detect an accumulation-order defect"
);
}
#[test]
fn sorting_on_magnitude_alone_would_still_depend_on_the_arrival_order() {
let sum = |t: [f64; 3]| t.iter().fold(0.0f64, |a, b| a + b);
assert_ne!(
sum([1.0e-16, 1.0, -1.0]).to_bits(),
sum([1.0e-16, -1.0, 1.0]).to_bits(),
"a magnitude-only sort cannot be what makes the reduction equivariant"
);
assert_eq!(
sum_equivariant(CANCELLING).to_bits(),
sum([1.0e-16, -1.0, 1.0]).to_bits()
);
}
#[test]
fn zero_padding_a_reduction_does_not_change_a_single_bit() {
let mut padded = [0.0f64; 12];
padded[4] = CANCELLING[0];
padded[9] = CANCELLING[1];
padded[1] = CANCELLING[2];
assert_eq!(
sum_equivariant(padded).to_bits(),
sum_equivariant(CANCELLING).to_bits()
);
}
#[test]
fn negative_zero_survives_padding_because_the_accumulator_starts_positive() {
assert_eq!(sum_equivariant([-0.0f64; 3]).to_bits(), 0.0f64.to_bits());
let mut padded = [0.0f64; 12];
padded[0] = -0.0;
padded[5] = -0.0;
padded[11] = -0.0;
assert_eq!(
sum_equivariant(padded).to_bits(),
sum_equivariant([-0.0f64; 3]).to_bits()
);
}
#[test]
fn sorting_is_by_magnitude_and_keeps_signs() {
let mut t = [3.0f64, -1.0, 2.0, -0.5];
sort_by_magnitude(&mut t);
assert_eq!(bits(t), bits([-0.5, -1.0, 2.0, 3.0]));
}
#[test]
fn equal_magnitudes_come_out_negative_first() {
for start in [[1.0f64, -1.0], [-1.0, 1.0]] {
let mut t = start;
sort_by_magnitude(&mut t);
assert_eq!(bits(t), bits([-1.0, 1.0]), "from {start:?}");
}
}
#[test]
fn negation_equivariance_is_not_achievable_by_ordering_and_here_is_the_witness() {
let terms = [1.0e-16f64, 1.0, -1.0];
let negated = terms.map(|t| -t);
assert_ne!(
sum_equivariant(negated).to_bits(),
(-sum_equivariant(terms)).to_bits(),
"if this ever passes, revisit A-016's successor -- the obstruction moved"
);
for permuted in [[1.0f64, -1.0, 1.0e-16], [-1.0, 1.0e-16, 1.0]] {
assert_eq!(
sum_equivariant(permuted).to_bits(),
sum_equivariant(terms).to_bits()
);
}
}
#[test]
fn a_nan_does_not_make_the_order_depend_on_the_arrangement() {
let mut a = [f64::NAN, 1.0, -2.0];
let mut b = [-2.0, f64::NAN, 1.0];
sort_by_magnitude(&mut a);
sort_by_magnitude(&mut b);
assert_eq!(bits(a), bits(b));
}
#[test]
fn f32_reduces_the_same_way() {
let terms = [1.0e-8f32, 1.0, -1.0];
assert_eq!(
sum_equivariant([terms[2], terms[0], terms[1]]).to_bits(),
sum_equivariant(terms).to_bits()
);
}