#![allow(clippy::float_cmp, clippy::unreadable_literal, clippy::unwrap_used)]
mod support;
use arithmetica::{RationalCoord2d, Sign, orient2d, orient2d_rational};
use support::{
Triple, all_orders, axis_aligned_triples, duplicate_triples, from_ordinal,
general_collinear_triple, ordinal,
};
fn float_sign(t: Triple) -> Sign {
orient2d(t[0], t[1], t[2]).expect("the fixtures are finite")
}
fn exact_sign(t: Triple) -> Sign {
let c = RationalCoord2d::from_f64(t[2]).expect("the fixtures are finite");
orient2d_rational(t[0], t[1], &c).expect("the fixtures are finite")
}
fn nudge(x: f64, steps: i64) -> f64 {
from_ordinal(ordinal(x) + steps)
}
#[test]
fn duplicate_points_are_zero_in_every_argument_order() {
for (name, triple) in duplicate_triples() {
for (i, order) in all_orders(triple).into_iter().enumerate() {
assert_eq!(float_sign(order), Sign::Zero, "{name}, order {i}");
assert_eq!(exact_sign(order), Sign::Zero, "{name}, order {i} (twin)");
}
}
}
#[test]
fn axis_aligned_triples_are_zero_in_every_argument_order() {
for (name, triple) in axis_aligned_triples() {
for (i, order) in all_orders(triple).into_iter().enumerate() {
assert_eq!(float_sign(order), Sign::Zero, "{name}, order {i}");
assert_eq!(exact_sign(order), Sign::Zero, "{name}, order {i} (twin)");
}
}
}
#[test]
fn general_collinear_triple_is_zero() {
for (i, order) in all_orders(general_collinear_triple())
.into_iter()
.enumerate()
{
assert_eq!(float_sign(order), Sign::Zero, "order {i}");
assert_eq!(exact_sign(order), Sign::Zero, "order {i} (twin)");
}
}
#[test]
fn one_ulp_perturbations_match_the_rational_twin() {
let mut bases: Vec<Triple> = Vec::new();
bases.extend(duplicate_triples().into_iter().map(|(_, t)| t));
bases.extend(axis_aligned_triples().into_iter().map(|(_, t)| t));
bases.push(general_collinear_triple());
let (mut compared, mut nonzero) = (0_u32, 0_u32);
for base in bases {
for point in 0..3 {
for axis in 0..2 {
for steps in [-1_i64, 1] {
let mut moved = base;
moved[point][axis] = nudge(base[point][axis], steps);
for order in all_orders(moved) {
let want = exact_sign(order);
assert_eq!(
float_sign(order),
want,
"base {base:?}, point {point}, axis {axis}, steps {steps}"
);
compared += 1;
nonzero += u32::from(want != Sign::Zero);
}
}
}
}
}
assert_eq!(compared, 504);
assert!(
nonzero > compared / 2,
"only {nonzero} of {compared} perturbed cases were non-degenerate"
);
}
#[test]
fn a_point_one_float_off_a_horizontal_line_is_not_collinear() {
let (a, c) = ([1.0, 1.0], [3.0, 1.0]);
let raised = [2.0, nudge(1.0, 1)];
let lowered = [2.0, nudge(1.0, -1)];
assert_eq!(
raised[1] - 1.0,
f64::EPSILON,
"one float above 1.0 is 1 + 2^-52"
);
assert_eq!(float_sign([a, [2.0, 1.0], c]), Sign::Zero);
assert_eq!(float_sign([a, raised, c]), Sign::Negative);
assert_eq!(float_sign([a, lowered, c]), Sign::Positive);
assert_eq!(float_sign([raised, a, c]), Sign::Positive);
assert_eq!(float_sign([lowered, a, c]), Sign::Negative);
}
#[test]
fn nonzero_subnormal_differences_are_not_zero() {
let u = f64::from_bits(1);
assert_eq!(u, 5e-324, "the smallest positive subnormal");
let (pa, pb, pc) = ([u, 0.0], [0.0, u], [0.0, 0.0]);
assert_eq!(float_sign([pa, pb, pc]), Sign::Positive);
assert_eq!(float_sign([pb, pa, pc]), Sign::Negative);
assert_eq!(exact_sign([pa, pb, pc]), Sign::Positive);
let o = 16.0 * u;
let (qa, qb, qc) = ([o + u, o], [o, o + u], [o, o]);
assert_eq!(qa[0] - qc[0], u, "subnormal subtraction is exact");
assert_eq!(float_sign([qa, qb, qc]), Sign::Positive);
assert_eq!(float_sign([qb, qa, qc]), Sign::Negative);
assert_eq!(exact_sign([qa, qb, qc]), Sign::Positive);
}