use axiolid_contracts::{Certified, Precision, Sign};
use axiolid_core::Point2;
use axiolid_reference::{orient2d, orient2d_filter};
fn p(x: f64, y: f64) -> Point2 {
Point2::new(x, y)
}
fn sign_of(result: Certified) -> Sign {
match result {
Certified::Certain { sign, .. } => sign,
other => panic!("orient2d must always certify, got {other:?}"),
}
}
#[test]
fn obvious_orientations_are_correct() {
let (a, b) = (p(0.0, 0.0), p(1.0, 0.0));
assert_eq!(sign_of(orient2d(a, b, p(0.0, 1.0))), Sign::Positive);
assert_eq!(sign_of(orient2d(a, b, p(0.0, -1.0))), Sign::Negative);
assert_eq!(sign_of(orient2d(a, b, p(2.0, 0.0))), Sign::Zero);
}
#[test]
fn exact_collinearity_is_reported_as_zero() {
let a = p(0.0, 0.0);
let b = p(1.0, 1.0);
for step in 1..50 {
let t = f64::from(step);
assert_eq!(
sign_of(orient2d(a, b, p(t, t))),
Sign::Zero,
"points on y = x are collinear at t = {t}"
);
}
}
#[test]
fn the_exact_path_beats_naive_f64_where_naive_f64_is_wrong() {
let cases = [
(
p(-0.8362899784084603, -0.3995017629087494),
p(-0.00976728088948886, -0.3130486200832534),
p(1.3309231823978052, -0.17281423274545576),
Sign::Negative,
),
(
p(0.9603496949851642, -0.7638684434900758),
p(-0.1637543564295456, 0.5142818591304987),
p(-1.1529224600475325, 1.6390047078657146),
Sign::Negative,
),
(
p(-0.9918127932298721, -0.16210699774934412),
p(-0.26149285421054924, 0.1326824474127839),
p(1.8438331624214392, 0.9824851916206433),
Sign::Positive,
),
];
for (a, b, c, truth) in cases {
let naive = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
assert_eq!(
naive, 0.0,
"precondition: naive f64 must claim collinear for {a:?} {b:?} {c:?}"
);
assert_eq!(
sign_of(orient2d(a, b, c)),
truth,
"exact arithmetic must overrule the naive determinant"
);
assert!(matches!(
orient2d_filter(a, b, c),
Certified::Uncertain { .. }
));
}
}
#[test]
fn the_predicate_is_antisymmetric_including_near_degenerate_inputs() {
let mut state = 0x9E37_79B9_7F4A_7C15_u64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
f64::from(((state >> 40) as u32) as i32) / 8.0
};
for _ in 0..500 {
let a = p(next(), next());
let b = p(next(), next());
let c = p(a.x + (b.x - a.x) * 3.0, a.y + (b.y - a.y) * 3.0);
let forward = sign_of(orient2d(a, b, c));
let swapped = sign_of(orient2d(b, a, c));
assert_eq!(
forward,
swapped.flip(),
"orient2d({a:?}, {b:?}, {c:?}) must be antisymmetric"
);
}
}
#[test]
fn the_filter_defers_where_the_full_predicate_still_decides() {
let a = p(0.0, 0.0);
let b = p(1.0, 1.0);
let c = p(3.0, 3.0);
assert!(
matches!(orient2d_filter(a, b, c), Certified::Uncertain { .. }),
"a zero determinant cannot be certified by a filter"
);
assert!(matches!(
orient2d(a, b, c),
Certified::Certain {
sign: Sign::Zero,
precision: Precision::Exact
}
));
}
#[test]
fn the_filter_settles_ordinary_inputs_without_escalating() {
let (a, b) = (p(0.0, 0.0), p(1.0, 0.0));
assert!(matches!(
orient2d_filter(a, b, p(0.0, 1.0)),
Certified::Certain {
sign: Sign::Positive,
precision: Precision::F64
}
));
}
#[test]
fn certified_signs_agree_with_an_independent_exact_oracle() {
fn oracle(a: (i64, i64), b: (i64, i64), c: (i64, i64)) -> Sign {
let d = (i128::from(a.0) - i128::from(c.0)) * (i128::from(b.1) - i128::from(c.1))
- (i128::from(a.1) - i128::from(c.1)) * (i128::from(b.0) - i128::from(c.0));
match d.cmp(&0) {
core::cmp::Ordering::Greater => Sign::Positive,
core::cmp::Ordering::Less => Sign::Negative,
core::cmp::Ordering::Equal => Sign::Zero,
}
}
let mut state = 0xDEAD_BEEF_CAFE_1234_u64;
let mut next = |range: i64| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 33) as i64 % range - range / 2
};
let mut escalations = 0_u32;
let total = 20_000;
for _ in 0..total {
let a = (next(64), next(64));
let b = (next(64), next(64));
let c = (next(64), next(64));
let fa = p(a.0 as f64, a.1 as f64);
let fb = p(b.0 as f64, b.1 as f64);
let fc = p(c.0 as f64, c.1 as f64);
assert_eq!(
sign_of(orient2d(fa, fb, fc)),
oracle(a, b, c),
"disagreement on a={a:?} b={b:?} c={c:?}"
);
if matches!(orient2d_filter(fa, fb, fc), Certified::Uncertain { .. }) {
escalations += 1;
}
}
assert!(
escalations > 0,
"no input escalated; the differential test never exercised exact arithmetic"
);
}
#[test]
fn certified_signs_survive_mixed_magnitude_coordinates() {
fn decompose(value: f64) -> (i128, i32) {
if value == 0.0 {
return (0, 0);
}
let bits = value.to_bits();
let raw_exponent = ((bits >> 52) & 0x7FF) as i32;
let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
let (mantissa, exponent) = if raw_exponent == 0 {
(fraction, -1074)
} else {
(fraction | 0x0010_0000_0000_0000, raw_exponent - 1075)
};
let mut signed = if value < 0.0 {
-(mantissa as i128)
} else {
mantissa as i128
};
let mut exponent = exponent;
while signed != 0 && signed % 2 == 0 {
signed /= 2;
exponent += 1;
}
(signed, exponent)
}
fn oracle(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Sign {
let parts = [a.0, a.1, b.0, b.1, c.0, c.1].map(decompose);
let min_exponent = parts.iter().map(|&(_, e)| e).min().expect("non-empty");
let max_shift = parts
.iter()
.map(|&(_, e)| e - min_exponent)
.max()
.expect("non-empty");
assert!(
max_shift < 40,
"exponent spread {max_shift} too wide for i128"
);
let scaled = parts.map(|(m, e)| m << (e - min_exponent));
let [ax, ay, bx, by, cx, cy] = scaled;
let d = (ax - cx) * (by - cy) - (ay - cy) * (bx - cx);
match d.cmp(&0) {
core::cmp::Ordering::Greater => Sign::Positive,
core::cmp::Ordering::Less => Sign::Negative,
core::cmp::Ordering::Equal => Sign::Zero,
}
}
let mut state = 0x1234_5678_9ABC_DEF0_u64;
let mut rng = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let mut escalations = 0_u32;
let mut rounding_losses = 0_u32;
for _ in 0..20_000 {
let coordinate = |rng: &mut dyn FnMut() -> u64| {
let r = rng();
let mantissa = ((r >> 11) as i64) - (1 << 52);
let exponent = ((r % 9) as i32) - 4;
(mantissa as f64) * 2.0_f64.powi(exponent)
};
let ax = coordinate(&mut rng);
let ay = coordinate(&mut rng);
let bx = coordinate(&mut rng);
let by = coordinate(&mut rng);
let k = 2.0 + (rng() % 3) as f64;
let cx = ax + (bx - ax) * k;
let cy = ay + (by - ay) * k;
let (fa, fb, fc) = (p(ax, ay), p(bx, by), p(cx, cy));
let (dm, de) = decompose(ax - cx);
let (am, ae) = decompose(ax);
let (cm, ce) = decompose(cx);
let exact_difference =
(am << (ae - ae.min(ce).min(de))) - (cm << (ce - ae.min(ce).min(de)));
if (dm << (de - ae.min(ce).min(de))) != exact_difference {
rounding_losses += 1;
}
assert_eq!(
sign_of(orient2d(fa, fb, fc)),
oracle((ax, ay), (bx, by), (cx, cy)),
"disagreement on a=({ax}, {ay}) b=({bx}, {by}) c=({cx}, {cy})"
);
if matches!(orient2d_filter(fa, fb, fc), Certified::Uncertain { .. }) {
escalations += 1;
}
}
assert!(escalations > 0, "the exact path was never exercised");
assert!(
rounding_losses > 0,
"no coordinate difference rounded; correction terms stayed unreachable"
);
}