axiolid_exact/
root.rs

1//! Numbers of the form `(a + b*sqrt(c)) / d`.
2//!
3//! A line meets a circle at such a parameter; comparing two such hits, or
4//! asking which side of a line one lies on, is a sign question about them.
5//! Signs are decided by squaring with case analysis (the approach of CGAL's
6//! `Root_of_2`), never by evaluating a root, so in exact arithmetic the
7//! answer is exact, including an exact zero.
8//!
9//! Degree note: `sign_root` squares once (degree 2 in `a`, `b`, `c`);
10//! `sign_two_roots` squares twice. Mantissa length, and so exact-tier cost,
11//! grows with that degree. Both run the interval filter first.
12
13use axiolid_guarantees::Sign;
14
15use crate::arith::{sign_product, Arith};
16
17/// The value `(a + b*sqrt(c)) / d`, with `c >= 0` and `d != 0`.
18#[derive(Debug, Clone, PartialEq)]
19pub struct Root2<T> {
20    /// Rational part of the numerator.
21    pub a: T,
22    /// Coefficient of the root.
23    pub b: T,
24    /// Radicand; must not be negative.
25    pub c: T,
26    /// Denominator; must not be zero.
27    pub d: T,
28}
29
30impl<T: Arith> Root2<T> {
31    /// Sign of the value, or `None` when `T` cannot decide (or the
32    /// preconditions fail: negative radicand, zero denominator).
33    #[must_use]
34    pub fn sign(&self) -> Option<Sign> {
35        let denominator = self.d.sign()?;
36        if denominator == Sign::Zero {
37            return None;
38        }
39        Some(sign_product(
40            sign_root(&self.a, &self.b, &self.c)?,
41            denominator,
42        ))
43    }
44
45    /// Sign of `self - other`: which of the two values is larger.
46    ///
47    /// Works for different radicands. With `x = (a1 + b1*sqrt(c1)) / d1`
48    /// and `y` likewise, `x - y` has the sign of `d1 * d2` times
49    /// `(a1*d2 - a2*d1) + b1*d2*sqrt(c1) - b2*d1*sqrt(c2)`.
50    #[must_use]
51    pub fn cmp_sign(&self, other: &Self) -> Option<Sign> {
52        let d1 = self.d.sign()?;
53        let d2 = other.d.sign()?;
54        if d1 == Sign::Zero || d2 == Sign::Zero {
55            return None;
56        }
57        let p = self.a.mul(&other.d).sub(&other.a.mul(&self.d));
58        let q = self.b.mul(&other.d);
59        let r = other.b.mul(&self.d).neg();
60        let inner = sign_two_roots(&p, &q, &self.c, &r, &other.c)?;
61        Some(sign_product(sign_product(inner, d1), d2))
62    }
63}
64
65/// Sign of `a + b*sqrt(c)` for `c >= 0`.
66///
67/// If `a` and `b*sqrt(c)` agree in sign (or one is zero), that is the
68/// answer. If they disagree, the larger magnitude wins, and comparing
69/// magnitudes is comparing squares: the result is `sign(a) *
70/// sign(a^2 - b^2*c)`. An exact zero comes out when they cancel exactly.
71///
72/// Returns `None` when `T` cannot decide, or when `c` is negative (the
73/// value is not real).
74#[must_use]
75pub fn sign_root<T: Arith>(a: &T, b: &T, c: &T) -> Option<Sign> {
76    let radicand = c.sign()?;
77    if radicand == Sign::Negative {
78        return None;
79    }
80    let sa = a.sign()?;
81    // b*sqrt(c) has the sign of b, unless c is zero.
82    let sb = if radicand == Sign::Zero {
83        Sign::Zero
84    } else {
85        b.sign()?
86    };
87    if sb == Sign::Zero {
88        return Some(sa);
89    }
90    if sa == Sign::Zero || sa == sb {
91        return Some(sb);
92    }
93    let dominance = a.square().sub(&b.square().mul(c)).sign()?;
94    Some(sign_product(sa, dominance))
95}
96
97/// Sign of `p + q*sqrt(c) + r*sqrt(e)` for `c, e >= 0`.
98///
99/// Split as `u + v` with `u = p + q*sqrt(c)` and `v = r*sqrt(e)`. If they
100/// agree in sign (or one is zero) that is the answer. Otherwise the result
101/// is `sign(u) * sign(u^2 - v^2)`, and `u^2 - v^2 = (p^2 + q^2*c - r^2*e) +
102/// 2*p*q*sqrt(c)` is again one root, decided by [`sign_root`].
103#[must_use]
104pub fn sign_two_roots<T: Arith>(p: &T, q: &T, c: &T, r: &T, e: &T) -> Option<Sign> {
105    let su = sign_root(p, q, c)?;
106    let radicand = e.sign()?;
107    if radicand == Sign::Negative {
108        return None;
109    }
110    let sv = if radicand == Sign::Zero {
111        Sign::Zero
112    } else {
113        r.sign()?
114    };
115    if sv == Sign::Zero {
116        return Some(su);
117    }
118    if su == Sign::Zero || su == sv {
119        return Some(sv);
120    }
121    let rational = p.square().add(&q.square().mul(c)).sub(&r.square().mul(e));
122    let two = T::from_f64(2.0);
123    let irrational = two.mul(p).mul(q);
124    let dominance = sign_root(&rational, &irrational, c)?;
125    Some(sign_product(su, dominance))
126}