use axiolid_exact::{
certify, filter, sign_root, sign_two_roots, Arith, Dyadic, Interval, Root2, SignExpr,
};
use axiolid_guarantees::Sign;
use num_bigint::BigInt;
const FIXED_BITS: u32 = 256;
fn scaled_sqrt(c: i64) -> BigInt {
(BigInt::from(c) << (2 * FIXED_BITS)).sqrt()
}
fn oracle(p: i64, q: i64, c: i64, r: i64, e: i64) -> Sign {
let approx = (BigInt::from(p) << FIXED_BITS)
+ BigInt::from(q) * scaled_sqrt(c)
+ BigInt::from(r) * scaled_sqrt(e);
let bound = BigInt::from(q.abs() + r.abs());
if approx > bound {
Sign::Positive
} else if approx < -bound {
Sign::Negative
} else {
Sign::Zero
}
}
fn exact<T: Arith>(value: i64) -> T {
T::from_f64(value as f64)
}
#[test]
fn one_root_matches_the_oracle_exhaustively() {
let (mut zeros, mut cancellations) = (0, 0);
for a in -7..=7 {
for b in -7..=7 {
for c in 0..=50 {
let want = oracle(a, b, c, 0, 0);
let got = sign_root::<Dyadic>(&exact(a), &exact(b), &exact(c));
assert_eq!(got, Some(want), "sign({a} + {b}*sqrt({c}))");
if let Some(fast) = sign_root::<Interval>(&exact(a), &exact(b), &exact(c)) {
assert_eq!(fast, want, "interval sign({a} + {b}*sqrt({c}))");
}
if want == Sign::Zero {
zeros += 1;
cancellations += usize::from(a != 0);
}
}
}
}
assert_eq!((zeros, cancellations), (97, 32));
}
#[test]
fn two_roots_match_the_oracle_exhaustively() {
let (mut zeros, mut cancellations) = (0, 0);
for p in -3..=3 {
for q in -3..=3 {
for r in -3..=3 {
for c in 0..=9 {
for e in 0..=9 {
let want = oracle(p, q, c, r, e);
let got = sign_two_roots::<Dyadic>(
&exact(p),
&exact(q),
&exact(c),
&exact(r),
&exact(e),
);
assert_eq!(got, Some(want), "{p} + {q}√{c} + {r}√{e}");
if want == Sign::Zero {
zeros += 1;
let both_live = q != 0 && c != 0 && r != 0 && e != 0;
cancellations += usize::from(p != 0 || both_live);
}
}
}
}
}
}
assert_eq!((zeros, cancellations), (740, 484));
}
#[test]
fn a_negative_radicand_or_zero_denominator_is_undefined() {
assert_eq!(sign_root::<Dyadic>(&exact(1), &exact(1), &exact(-1)), None);
let bad = Root2 {
a: exact::<Dyadic>(1),
b: exact(1),
c: exact(2),
d: exact(0),
};
assert_eq!(bad.sign(), None);
}
#[test]
fn comparison_orders_values_with_different_radicands() {
let x = Root2 {
a: exact::<Dyadic>(1),
b: exact(1),
c: exact(2),
d: exact(2),
};
let y = Root2 {
a: exact::<Dyadic>(3),
b: exact(-1),
c: exact(3),
d: exact(1),
};
assert_eq!(x.cmp_sign(&y), Some(Sign::Negative));
assert_eq!(y.cmp_sign(&x), Some(Sign::Positive));
assert_eq!(x.cmp_sign(&x), Some(Sign::Zero));
let x_flipped = Root2 {
a: exact::<Dyadic>(-1),
b: exact(-1),
c: exact(2),
d: exact(-2),
};
assert_eq!(x.cmp_sign(&x_flipped), Some(Sign::Zero));
}
struct PellGap {
p: f64,
q: f64,
}
impl SignExpr for PellGap {
fn sign_in<T: Arith>(&self) -> Option<Sign> {
sign_root(
&T::from_f64(self.p),
&T::from_f64(-self.q),
&T::from_f64(2.0),
)
}
}
#[test]
fn pell_convergents_need_the_exact_tier_and_get_it_right() {
let (mut p, mut q) = (1i128, 1i128);
let mut largest = Vec::new();
while p < (1i128 << 53) {
largest.push((p, q));
(p, q) = (p + 2 * q, p + q);
}
let mut escalated = 0;
for &(p, q) in largest.iter().rev().take(4) {
let norm = p * p - 2 * q * q;
assert!(norm == 1 || norm == -1, "Pell identity for {p}, {q}");
let want = if norm > 0 {
Sign::Positive
} else {
Sign::Negative
};
let expr = PellGap {
p: p as f64,
q: q as f64,
};
assert_eq!(certify(&expr), Ok(want), "sign of {p} - {q}*sqrt(2)");
escalated += usize::from(!filter(&expr).is_certain());
}
assert!(escalated >= 2, "the largest convergents must escalate");
}