Skip to main content

qualia_core_db/specialized_libs/computational_geometry/
expansion.rs

1//! Zero-heap expansion arithmetic — the exact-fallback foundation.
2//!
3//! This is P1.3 in the computational-geometry execution plan. It implements
4//! Shewchuk-style adaptive-precision floating-point arithmetic using
5//! **error-free transformations** and **expansions** (sorted, non-overlapping
6//! sequences of `f64` values that exactly represent a real number).
7//!
8//! ## Why this exists
9//!
10//! The filtered `f64` predicates in [`super::primitives`] are fast and exact
11//! on the `f32`-sourced `Tensor10D` path (because `f32 → f64` promotion makes
12//! the products exact). But for general `f64` inputs — and for cascaded
13//! constructions where errors accumulate — the filtered path can mis-sign
14//! near-degenerate cases. The exact-fallback ladder (P1.4–P1.7) needs a
15//! zero-heap way to compute the exact sign of a determinant. This module
16//! provides the arithmetic primitives that ladder is built from.
17//!
18//! ## Zero-heap contract
19//!
20//! Every function takes caller-supplied `&mut [f64]` output buffers. No
21//! `Vec`, `String`, or `Box` is allocated in any operation. The caller sizes
22//! buffers using the [`MAX_EXPANSION_*`] constants. If a buffer is too small,
23//! the operation returns [`ExpansionError::OutputTooSmall`] — fail-closed,
24//! never silent truncation.
25//!
26//! ## Expansion invariant
27//!
28//! An expansion `e = [e0, e1, ..., e_{n-1}]` is a sequence of `f64` values
29//! such that:
30//!
31//! 1. **Non-overlapping:** the components do not overlap in their bit ranges
32//!    (no component's significant bits overlap with another's).
33//! 2. **Sorted by magnitude:** `|e0| <= |e1| <= ... <= |e_{n-1}|`.
34//! 3. **Exact sum:** `sum(e_i)` equals the exact real-number result of the
35//!    operation that produced the expansion.
36//!
37//! The sign of an expansion is the sign of its largest-magnitude component
38//! (the last one), because the non-overlapping property guarantees no smaller
39//! component can change it.
40//!
41//! ## References
42//!
43//! The algorithms are from Jonathan Richard Shewchuk, "Adaptive Precision
44//! Floating-Point Arithmetic and Fast Robust Geometric Predicates" (1996,
45//! Discrete & Computational Geometry). The implementation is original Rust,
46//! adapted for the zero-heap caller-buffered contract. No third-party
47//! source code is used — the algorithms are public-knowledge
48//! numerical methods.
49
50// ──────────────────────────────────────────────────────────────────────────
51//  Workspace size constants
52// ──────────────────────────────────────────────────────────────────────────
53
54/// Maximum expansion length for the `orient2d` predicate (2×2 determinant
55/// of differences: 2 terms × 2-component products, summed → length ≤ 8).
56pub const MAX_EXPANSION_ORIENT2: usize = 8;
57
58/// Maximum expansion length for the `orient3d` predicate (3×3 determinant
59/// of differences: 6 terms × 3-component products, summed → length ≤ 24
60/// without compression; with compression the actual length is smaller).
61pub const MAX_EXPANSION_ORIENT3: usize = 24;
62
63/// Maximum expansion length for the `incircle` predicate (3×3 determinant
64/// with squared-distance entries: 6 terms × products of up to 4-component
65/// expansions, summed → length ≤ 96 without compression).
66pub const MAX_EXPANSION_INCIRCLE: usize = 96;
67
68/// Maximum expansion length for the `insphere` predicate (5×5 determinant
69/// with squared-distance entries: 120 terms × products of up to 6-component
70/// expansions, summed → length ≤ 2048 without compression; with aggressive
71/// zero-elimination the actual length is much smaller, but this bound
72/// ensures the workspace is always sufficient).
73///
74/// This is the coordination point called out in the execution plan: the
75/// P1.3 workspace must be sized for P1.6's insphere determinant. A 2048-f64
76/// workspace is 16 KB — well within the 42 MB Sentinel ceiling.
77pub const MAX_EXPANSION_INSPHERE: usize = 2048;
78
79// ──────────────────────────────────────────────────────────────────────────
80//  Error type
81// ──────────────────────────────────────────────────────────────────────────
82
83/// Errors from expansion arithmetic operations. All are fail-closed: the
84/// caller must provide sufficiently large buffers.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum ExpansionError {
87    /// The output buffer is too small for the operation.
88    OutputTooSmall,
89}
90
91impl core::fmt::Display for ExpansionError {
92    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93        match self {
94            ExpansionError::OutputTooSmall => {
95                write!(f, "expansion output buffer too small")
96            }
97        }
98    }
99}
100
101impl std::error::Error for ExpansionError {}
102
103// ──────────────────────────────────────────────────────────────────────────
104//  Error-free transformations (length-1 → length-2)
105// ──────────────────────────────────────────────────────────────────────────
106
107/// Error-free addition: `a + b = s + e` where `s = round(a + b)` and `e` is
108/// the exact rounding error.
109///
110/// Knuth's algorithm (TAOCP Vol. 2, §4.2.2, Theorem B). Works for any `a, b`
111/// regardless of relative magnitude. Six floating-point operations.
112#[inline]
113pub fn two_sum(a: f64, b: f64) -> (f64, f64) {
114    let s = a + b;
115    let a_prime = s - b;
116    let b_prime = s - a_prime;
117    let da = a - a_prime;
118    let db = b - b_prime;
119    let e = da + db;
120    (s, e)
121}
122
123/// Error-free addition with precondition `|a| >= |b|`: `a + b = s + e`.
124///
125/// Faster than [`two_sum`] (three operations instead of six) but requires
126/// `|a| >= |b|`. If the precondition is violated the result is still a valid
127/// expansion but may not be error-free. Use [`two_sum`] when the relative
128/// magnitudes are unknown.
129#[inline]
130pub fn fast_two_sum(a: f64, b: f64) -> (f64, f64) {
131    debug_assert!(
132        a.abs() >= b.abs(),
133        "fast_two_sum precondition violated: |a| must be >= |b|"
134    );
135    let s = a + b;
136    let e = b - (s - a);
137    (s, e)
138}
139
140/// Error-free multiplication: `a * b = p + e` where `p = round(a * b)` and
141/// `e` is the exact rounding error.
142///
143/// Uses `fma` (fused multiply-add) when available: `e = fma(a, b, -p)`.
144/// On targets without hardware FMA this is still correct (the `mul_add`
145/// intrinsic produces the mathematically exact result on all IEEE-754
146/// platforms that Rust targets — it is the compiler's job to lower it).
147#[inline]
148pub fn two_product(a: f64, b: f64) -> (f64, f64) {
149    let p = a * b;
150    let e = a.mul_add(b, -p);
151    (p, e)
152}
153
154/// Error-free subtraction: `a - b = s + e`. Equivalent to `two_sum(a, -b)`.
155#[inline]
156pub fn two_diff(a: f64, b: f64) -> (f64, f64) {
157    two_sum(a, -b)
158}
159
160// ──────────────────────────────────────────────────────────────────────────
161//  Expansion operations
162// ──────────────────────────────────────────────────────────────────────────
163
164/// Grow an expansion by adding a scalar: `h = e + b`.
165///
166/// `e` is the input expansion (length `elen`), `b` is the scalar, `h` is the
167/// output buffer (must have length `>= elen + 1`). Returns the number of
168/// components written to `h` (always `elen + 1`).
169///
170/// Implements Shewchuk's `grow_expansion` (§2.4): a single pass that merges
171/// `b` into the expansion using [`fast_two_sum`], maintaining the
172/// non-overlapping + sorted invariant.
173///
174/// # Errors
175/// Returns [`ExpansionError::OutputTooSmall`] if `h.len() < elen + 1`.
176pub fn grow_expansion(e: &[f64], b: f64, h: &mut [f64]) -> Result<usize, ExpansionError> {
177    let elen = e.len();
178    if h.len() < elen + 1 {
179        return Err(ExpansionError::OutputTooSmall);
180    }
181
182    // Merge b into the expansion. We use two_sum (not fast_two_sum) because
183    // the relative magnitudes of e[i] and the running error are not
184    // guaranteed to satisfy fast_two_sum's |a| >= |b| precondition.
185    // two_sum is always error-free regardless of magnitudes (Knuth's
186    // algorithm, 6 ops vs fast_two_sum's 3).
187    let (sum, mut err) = two_sum(e[0], b);
188    h[0] = sum;
189
190    for i in 1..elen {
191        let (s, e_i) = two_sum(e[i], err);
192        h[i] = s;
193        err = e_i;
194    }
195    h[elen] = err;
196    Ok(elen + 1)
197}
198
199/// Scale an expansion by a scalar: `h = e * b`.
200///
201/// `e` is the input expansion (length `elen`), `b` is the scalar, `h` is the
202/// output buffer (must have length `>= 2 * elen`). Returns the number of
203/// components written to `h` (at most `2 * elen`).
204///
205/// Implements Shewchuk's `scale_expansion` (§2.5): each component `e[i]` is
206/// split into `(e[i] * b, error)` via [`two_product`], and the errors are
207/// accumulated into the output using [`two_sum`], maintaining the
208/// non-overlapping + sorted invariant.
209///
210/// # Errors
211/// Returns [`ExpansionError::OutputTooSmall`] if `h.len() < 2 * elen`.
212pub fn scale_expansion(e: &[f64], b: f64, h: &mut [f64]) -> Result<usize, ExpansionError> {
213    let elen = e.len();
214    if h.len() < 2 * elen {
215        return Err(ExpansionError::OutputTooSmall);
216    }
217    if elen == 0 {
218        return Ok(0);
219    }
220
221    // First component: two_product(e[0], b) → (product, error).
222    let (p0, err0) = two_product(e[0], b);
223    h[0] = p0;
224    h[1] = err0;
225    let mut hlen = 2;
226
227    for i in 1..elen {
228        let (pi, ei) = two_product(e[i], b);
229        // Merge the error from the previous step with the product of the
230        // current step, then append the new error.
231        let (s0, e0) = two_sum(h[hlen - 1], pi);
232        h[hlen - 1] = s0;
233        let (s1, e1) = two_sum(e0, ei);
234        h[hlen] = s1;
235        h[hlen + 1] = e1;
236        hlen += 2;
237    }
238    Ok(hlen)
239}
240
241/// Add two expansions: `h = e + f`.
242///
243/// `e` and `f` are input expansions, `h` is the output buffer (must have
244/// length `>= e.len() + f.len()`). Returns the number of components written
245/// to `h` (at most `e.len() + f.len()`).
246///
247/// Implements Shewchuk's `expansion_sum` (§2.6): a merge that processes both
248/// expansions in order of increasing magnitude, using [`two_sum`] to maintain
249/// the non-overlapping + sorted invariant.
250///
251/// # Errors
252/// Returns [`ExpansionError::OutputTooSmall`] if `h.len() < e.len() + f.len()`.
253pub fn expansion_sum(e: &[f64], f: &[f64], h: &mut [f64]) -> Result<usize, ExpansionError> {
254    let elen = e.len();
255    let flen = f.len();
256    if h.len() < elen + flen {
257        return Err(ExpansionError::OutputTooSmall);
258    }
259    if elen == 0 {
260        h[..flen].copy_from_slice(f);
261        return Ok(flen);
262    }
263    if flen == 0 {
264        h[..elen].copy_from_slice(e);
265        return Ok(elen);
266    }
267
268    // Merge the two expansions like a merge-sort, feeding components through
269    // two_sum to maintain the non-overlapping invariant.
270    let mut ei = 0usize; // index into e
271    let mut fi = 0usize; // index into f
272    let mut hi = 0usize; // index into h
273
274    // Pick the smaller-magnitude first component to start.
275    let (mut current, from_e) = if e[0].abs() <= f[0].abs() {
276        (e[0], true)
277    } else {
278        (f[0], false)
279    };
280
281    // Advance past the consumed component.
282    if from_e {
283        ei = 1;
284    } else {
285        fi = 1;
286    }
287
288    // Merge remaining components.
289    while ei < elen && fi < flen {
290        let next_e = e[ei];
291        let next_f = f[fi];
292        let (val, take_e) = if next_e.abs() <= next_f.abs() {
293            (next_e, true)
294        } else {
295            (next_f, false)
296        };
297
298        let (s, err) = two_sum(current, val);
299        h[hi] = s;
300        hi += 1;
301        current = err;
302
303        if take_e {
304            ei += 1;
305        } else {
306            fi += 1;
307        }
308    }
309
310    // Drain remaining from e.
311    while ei < elen {
312        let (s, err) = two_sum(current, e[ei]);
313        h[hi] = s;
314        hi += 1;
315        current = err;
316        ei += 1;
317    }
318
319    // Drain remaining from f.
320    while fi < flen {
321        let (s, err) = two_sum(current, f[fi]);
322        h[hi] = s;
323        hi += 1;
324        current = err;
325        fi += 1;
326    }
327
328    // Append the final accumulated error.
329    h[hi] = current;
330    hi += 1;
331
332    Ok(hi)
333}
334
335/// Compress an expansion: eliminate near-zero and zero components, producing
336/// a minimal-length expansion with the same exact value.
337///
338/// `e` is the input expansion, `h` is the output buffer (must have length
339/// `>= e.len()`). Returns the number of components written to `h`.
340///
341/// Implements Shewchuk's `compress` (§2.7): a two-pass accumulation that
342/// merges adjacent components, eliminating zeros and reducing the expansion
343/// to its minimal non-overlapping form.
344///
345/// # Errors
346/// Returns [`ExpansionError::OutputTooSmall`] if `h.len() < e.len()`.
347pub fn compress_expansion(e: &[f64], h: &mut [f64]) -> Result<usize, ExpansionError> {
348    let elen = e.len();
349    if h.len() < elen {
350        return Err(ExpansionError::OutputTooSmall);
351    }
352    if elen == 0 {
353        return Ok(0);
354    }
355    if elen == 1 {
356        h[0] = e[0];
357        return Ok(1);
358    }
359
360    // Pass 1: top-down accumulation.
361    // Process e from the largest component (e[elen-1]) down to the smallest.
362    // We use two_sum (not fast_two_sum) because the running accumulator Q
363    // may not satisfy |Q| >= |e[i]| in all cases (e.g., when components
364    // have different signs and cancel). two_sum is always error-free.
365    let mut bottom = e[elen - 1];
366    for i in (0..elen - 1).rev() {
367        let (s, err) = two_sum(bottom, e[i]);
368        h[i + 1] = s;
369        bottom = err;
370    }
371    h[0] = bottom;
372
373    // Pass 2: bottom-up compression.
374    // Q is the running sum (the largest component). At each step,
375    // two_sum(Q, h[i]) produces (sum, error). The error is the smaller
376    // part — output it if non-zero. Q becomes the sum for the next step.
377    // This produces a sorted-by-increasing-magnitude expansion where
378    // each error component is smaller than the running sum.
379    let mut q = h[0];
380    let mut out = 0usize;
381
382    for i in 1..elen {
383        let (s, err) = two_sum(q, h[i]);
384        if err != 0.0 {
385            h[out] = err;
386            out += 1;
387        }
388        q = s;
389    }
390
391    // Output the final sum (the largest-magnitude component).
392    // Even if it's zero, output it if there are no other components
393    // (a zero expansion is [0.0] with length 1, not length 0).
394    if q != 0.0 || out == 0 {
395        h[out] = q;
396        out += 1;
397    }
398
399    Ok(out)
400}
401
402/// Negate an expansion in place: each component negated.
403#[inline]
404pub fn negate_expansion(e: &mut [f64]) {
405    for x in e.iter_mut() {
406        *x = -*x;
407    }
408}
409
410// ──────────────────────────────────────────────────────────────────────────
411//  Sign determination
412// ──────────────────────────────────────────────────────────────────────────
413
414/// Three-valued sign of a real number.
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416pub enum Sign {
417    Negative = -1,
418    Zero = 0,
419    Positive = 1,
420}
421
422impl Sign {
423    #[inline]
424    pub fn from_f64(x: f64) -> Self {
425        if x > 0.0 {
426            Sign::Positive
427        } else if x < 0.0 {
428            Sign::Negative
429        } else {
430            Sign::Zero
431        }
432    }
433
434    /// Flip the sign.
435    #[inline]
436    pub fn flip(self) -> Self {
437        match self {
438            Sign::Positive => Sign::Negative,
439            Sign::Negative => Sign::Positive,
440            Sign::Zero => Sign::Zero,
441        }
442    }
443}
444
445/// Determine the exact sign of an expansion.
446///
447/// For a properly-formed (non-overlapping, sorted by magnitude) expansion,
448/// the sign is determined by the **last** (largest-magnitude) component.
449/// The non-overlapping property guarantees that no smaller component can
450/// change the sign.
451///
452/// Returns [`Sign::Zero`] if the expansion is empty or all components are
453/// exactly zero.
454#[inline]
455pub fn sign_of_expansion(e: &[f64]) -> Sign {
456    if e.is_empty() {
457        return Sign::Zero;
458    }
459    // The expansion is sorted by increasing magnitude, so the last component
460    // has the largest magnitude and determines the sign.
461    Sign::from_f64(e[e.len() - 1])
462}
463
464// ──────────────────────────────────────────────────────────────────────────
465//  Convenience: scalar product of two f64 as a length-2 expansion
466// ──────────────────────────────────────────────────────────────────────────
467
468/// Exact product of two scalars, written into a 2-element buffer.
469///
470/// Convenience wrapper around [`two_product`] that writes the result into
471/// a caller-supplied buffer. `h` must have length `>= 2`.
472#[inline]
473pub fn scalar_product(a: f64, b: f64, h: &mut [f64]) -> Result<usize, ExpansionError> {
474    if h.len() < 2 {
475        return Err(ExpansionError::OutputTooSmall);
476    }
477    let (p, e) = two_product(a, b);
478    h[0] = p;
479    h[1] = e;
480    Ok(2)
481}
482
483/// Exact sum of two scalars, written into a 2-element buffer.
484///
485/// Convenience wrapper around [`two_sum`]. `h` must have length `>= 2`.
486#[inline]
487pub fn scalar_sum(a: f64, b: f64, h: &mut [f64]) -> Result<usize, ExpansionError> {
488    if h.len() < 2 {
489        return Err(ExpansionError::OutputTooSmall);
490    }
491    let (s, e) = two_sum(a, b);
492    h[0] = s;
493    h[1] = e;
494    Ok(2)
495}
496
497// ──────────────────────────────────────────────────────────────────────────
498//  Tests
499// ──────────────────────────────────────────────────────────────────────────
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    // ── Test-only exact arithmetic cross-check ───────────────────────────
506    //
507    // Every finite f64 can be represented exactly as `m * 2^e` where m is
508    // a signed integer (the mantissa, up to 53 bits) and e is the exponent.
509    // We use BigInt for the mantissa to handle arbitrary exponent differences
510    // without overflow. This is test-only code — the expansion arithmetic
511    // itself is zero-heap; the cross-check uses heap allocation freely.
512
513    use num_bigint::BigInt;
514
515    /// An exact real number: value = mantissa * 2^exponent.
516    #[derive(Debug, Clone)]
517    struct Exact {
518        mantissa: BigInt,
519        exponent: i32,
520    }
521
522    impl Exact {
523        /// Convert an f64 to its exact representation.
524        fn from_f64(x: f64) -> Self {
525            if x == 0.0 {
526                return Exact {
527                    mantissa: BigInt::from(0),
528                    exponent: 0,
529                };
530            }
531            let bits = x.to_bits();
532            let sign: i8 = if bits >> 63 != 0 { -1 } else { 1 };
533            let raw_exp = ((bits >> 52) & 0x7FF) as i32;
534            let raw_mant = bits & 0x000F_FFFF_FFFF_FFFF;
535
536            if raw_exp == 0 {
537                // Subnormal: value = sign * raw_mant * 2^(-1074)
538                Exact {
539                    mantissa: BigInt::from(sign) * BigInt::from(raw_mant),
540                    exponent: -1074,
541                }
542            } else {
543                // Normalized: value = sign * (2^52 + raw_mant) * 2^(raw_exp - 1023 - 52)
544                Exact {
545                    mantissa: BigInt::from(sign)
546                        * (BigInt::from(1u64 << 52) + BigInt::from(raw_mant)),
547                    exponent: raw_exp - 1023 - 52,
548                }
549            }
550        }
551
552        /// Exact addition. Aligns exponents, adds mantissas.
553        fn add(self, other: Self) -> Self {
554            if self.mantissa == 0.into() {
555                return other;
556            }
557            if other.mantissa == 0.into() {
558                return self;
559            }
560            // Determine which has the smaller exponent. Compute diff
561            // before moving values into the tuple.
562            let (lo, mut hi) = if self.exponent <= other.exponent {
563                (self, other)
564            } else {
565                (other, self)
566            };
567            let diff = hi.exponent - lo.exponent;
568
569            // Align to the smaller exponent by shifting the larger-exponent
570            // mantissa left. This preserves exactness (no precision loss).
571            if diff > 0 {
572                hi.mantissa <<= diff;
573            }
574            Exact {
575                mantissa: lo.mantissa + hi.mantissa,
576                exponent: lo.exponent,
577            }
578        }
579
580        /// Exact multiplication.
581        fn mul(self, other: Self) -> Self {
582            Exact {
583                mantissa: self.mantissa * other.mantissa,
584                exponent: self.exponent + other.exponent,
585            }
586        }
587
588        /// Exact negation.
589        fn neg(self) -> Self {
590            Exact {
591                mantissa: -self.mantissa,
592                exponent: self.exponent,
593            }
594        }
595
596        /// Compare two exact values. Returns true if they represent the same
597        /// real number.
598        fn equals(&self, other: &Self) -> bool {
599            // Normalize: remove factors of 2 from the mantissa.
600            let a = self.clone().normalize();
601            let b = other.clone().normalize();
602            a.mantissa == b.mantissa && a.exponent == b.exponent
603        }
604
605        /// Normalize: remove trailing zeros from the mantissa.
606        fn normalize(mut self) -> Self {
607            if self.mantissa == 0.into() {
608                return Exact {
609                    mantissa: BigInt::from(0),
610                    exponent: 0,
611                };
612            }
613            let zero = BigInt::from(0);
614            let one = BigInt::from(1);
615            while (&self.mantissa & &one) == zero {
616                self.mantissa >>= 1;
617                self.exponent += 1;
618            }
619            self
620        }
621
622        /// Sign of the exact value.
623        fn sign(&self) -> Sign {
624            use std::cmp::Ordering;
625            match self.mantissa.cmp(&BigInt::from(0)) {
626                Ordering::Greater => Sign::Positive,
627                Ordering::Less => Sign::Negative,
628                Ordering::Equal => Sign::Zero,
629            }
630        }
631    }
632
633    /// Convert an expansion (sum of f64s) to its exact value.
634    fn expansion_to_exact(e: &[f64]) -> Exact {
635        let mut acc = Exact {
636            mantissa: BigInt::from(0),
637            exponent: 0,
638        };
639        for &x in e {
640            acc = acc.add(Exact::from_f64(x));
641        }
642        acc
643    }
644
645    // ── Error-free transformation tests ──────────────────────────────────
646
647    #[test]
648    fn two_sum_is_error_free() {
649        // a + b = s + e, and s + e == a + b exactly.
650        // Avoid overflow cases (1e308+1e308 → inf).
651        let cases: [(f64, f64); 7] = [
652            (1.0, 2.0),
653            (1e100, 1e-100),
654            (1.0, f64::EPSILON),
655            (1e200, 1e200),
656            (-1.0, 1.0 + f64::EPSILON),
657            (0.1, 0.2),
658            (1e300, -1e300 + 1.0),
659        ];
660        for &(a, b) in &cases {
661            let (s, e) = two_sum(a, b);
662            let exact_a = Exact::from_f64(a);
663            let exact_b = Exact::from_f64(b);
664            let exact_sum = exact_a.add(exact_b);
665            let exact_result = Exact::from_f64(s).add(Exact::from_f64(e));
666            assert!(
667                exact_sum.equals(&exact_result),
668                "two_sum({a}, {b}): s={s}, e={e} — exact mismatch"
669            );
670        }
671    }
672
673    #[test]
674    fn fast_two_sum_matches_two_sum_when_precondition_holds() {
675        let cases: [(f64, f64); 5] = [
676            (2.0, 1.0),
677            (1e100, 1.0),
678            (1.0, f64::EPSILON),
679            (1e308, 1e300),
680            (-2.0, -1.0),
681        ];
682        for &(a, b) in &cases {
683            assert!(a.abs() >= b.abs(), "precondition");
684            let (s1, e1) = two_sum(a, b);
685            let (s2, e2) = fast_two_sum(a, b);
686            // Both are error-free, so s1+e1 == s2+e2 exactly.
687            // They may not be bit-identical (different algorithms), but the
688            // exact values must match.
689            let exact1 = Exact::from_f64(s1).add(Exact::from_f64(e1));
690            let exact2 = Exact::from_f64(s2).add(Exact::from_f64(e2));
691            assert!(
692                exact1.equals(&exact2),
693                "fast_two_sum({a}, {b}): results differ from two_sum"
694            );
695        }
696    }
697
698    #[test]
699    fn two_product_is_error_free() {
700        // Avoid overflow (f64::MAX * 2 → inf).
701        let cases: [(f64, f64); 7] = [
702            (2.0, 3.0),
703            (1e100, 1e-100),
704            (1.0, f64::EPSILON),
705            (0.1, 0.1),
706            (1e154, 1e154),
707            (-2.0, 3.0),
708            (1e200, 1e-200),
709        ];
710        for &(a, b) in &cases {
711            let (p, e) = two_product(a, b);
712            let exact_a = Exact::from_f64(a);
713            let exact_b = Exact::from_f64(b);
714            let exact_prod = exact_a.mul(exact_b);
715            let exact_result = Exact::from_f64(p).add(Exact::from_f64(e));
716            assert!(
717                exact_prod.equals(&exact_result),
718                "two_product({a}, {b}): p={p}, e={e} — exact mismatch"
719            );
720        }
721    }
722
723    #[test]
724    fn two_diff_is_error_free() {
725        let cases = [(3.0, 1.0), (1e100, 1e100 - 1.0), (1.0, 1.0 + f64::EPSILON)];
726        for &(a, b) in &cases {
727            let (s, e) = two_diff(a, b);
728            let exact_a = Exact::from_f64(a);
729            let exact_b = Exact::from_f64(b);
730            let exact_diff = exact_a.add(exact_b.neg());
731            let exact_result = Exact::from_f64(s).add(Exact::from_f64(e));
732            assert!(
733                exact_diff.equals(&exact_result),
734                "two_diff({a}, {b}): s={s}, e={e} — exact mismatch"
735            );
736        }
737    }
738
739    // ── Expansion operation tests ────────────────────────────────────────
740
741    #[test]
742    fn grow_expansion_adds_scalar_exactly() {
743        // e = [1.0, f64::EPSILON/2] (a length-2 expansion representing
744        // 1 + eps/2). Adding 2.0 should give 3 + eps/2 exactly.
745        let e = [1.0, f64::EPSILON / 2.0];
746        let mut h = [0.0f64; 4];
747        let n = grow_expansion(&e, 2.0, &mut h).unwrap();
748        assert_eq!(n, 3);
749
750        let exact_e = Exact::from_f64(e[0]).add(Exact::from_f64(e[1]));
751        let exact_result = exact_e.add(Exact::from_f64(2.0));
752        let exact_h = expansion_to_exact(&h[..n]);
753        assert!(
754            exact_h.equals(&exact_result),
755            "grow_expansion: exact mismatch"
756        );
757    }
758
759    #[test]
760    fn grow_expansion_adversarial_cancellation() {
761        // Add a tiny number to a large one, then add the negative of the
762        // large one. The expansion should preserve the tiny number exactly.
763        let big = 1e100;
764        let tiny = 1e-100;
765        let mut e = [0.0f64; 1];
766        e[0] = big;
767        let mut h1 = [0.0f64; 2];
768        let n1 = grow_expansion(&e[..1], tiny, &mut h1).unwrap();
769        assert_eq!(n1, 2);
770
771        // Now add -big. The result should be exactly tiny.
772        let mut h2 = [0.0f64; 4];
773        let n2 = grow_expansion(&h1[..n1], -big, &mut h2).unwrap();
774        assert_eq!(n2, 3);
775
776        let exact_result = expansion_to_exact(&h2[..n2]);
777        let exact_tiny = Exact::from_f64(tiny);
778        assert!(
779            exact_result.equals(&exact_tiny),
780            "grow_expansion adversarial: expected exactly {tiny}, got {:?}",
781            &h2[..n2]
782        );
783    }
784
785    #[test]
786    fn scale_expansion_multiplies_exactly() {
787        // e = [1.0, f64::EPSILON/2], scale by 3.0.
788        let e = [1.0, f64::EPSILON / 2.0];
789        let mut h = [0.0f64; 8];
790        let n = scale_expansion(&e, 3.0, &mut h).unwrap();
791        assert!(n <= 4);
792
793        let exact_e = Exact::from_f64(e[0]).add(Exact::from_f64(e[1]));
794        let exact_result = exact_e.mul(Exact::from_f64(3.0));
795        let exact_h = expansion_to_exact(&h[..n]);
796        assert!(
797            exact_h.equals(&exact_result),
798            "scale_expansion: exact mismatch"
799        );
800    }
801
802    #[test]
803    fn scale_expansion_adversarial() {
804        // Scale a cancellation-prone expansion by a large factor.
805        // e = [1.0, -1.0 + eps] (represents eps). Scale by 1e50.
806        let (s, e_err) = two_sum(-1.0, 1.0 + f64::EPSILON);
807        // s should be ~eps, e_err should be the residual.
808        let e = [s, e_err];
809        let mut h = [0.0f64; 8];
810        let n = scale_expansion(&e, 1e50, &mut h).unwrap();
811        assert!(n <= 4);
812
813        let exact_e = Exact::from_f64(e[0]).add(Exact::from_f64(e[1]));
814        let exact_result = exact_e.mul(Exact::from_f64(1e50));
815        let exact_h = expansion_to_exact(&h[..n]);
816        assert!(
817            exact_h.equals(&exact_result),
818            "scale_expansion adversarial: exact mismatch"
819        );
820    }
821
822    #[test]
823    fn expansion_sum_adds_exactly() {
824        // e = [1.0, eps], f = [2.0, eps/2]. Sum = 3.0 + 1.5*eps.
825        let e = [1.0, f64::EPSILON];
826        let f = [2.0, f64::EPSILON / 2.0];
827        let mut h = [0.0f64; 8];
828        let n = expansion_sum(&e, &f, &mut h).unwrap();
829        assert!(n <= 4);
830
831        let exact_e = Exact::from_f64(e[0]).add(Exact::from_f64(e[1]));
832        let exact_f = Exact::from_f64(f[0]).add(Exact::from_f64(f[1]));
833        let exact_result = exact_e.add(exact_f);
834        let exact_h = expansion_to_exact(&h[..n]);
835        assert!(
836            exact_h.equals(&exact_result),
837            "expansion_sum: exact mismatch"
838        );
839    }
840
841    #[test]
842    fn expansion_sum_adversarial_cancellation() {
843        // e = [big, tiny], f = [-big, tiny]. Sum = 2*tiny.
844        let big = 1e100;
845        let tiny = 1e-100;
846        let (s1, e1) = two_sum(big, tiny);
847        let e = [s1, e1];
848        let (s2, e2) = two_sum(-big, tiny);
849        let f = [s2, e2];
850
851        let mut h = [0.0f64; 8];
852        let n = expansion_sum(&e, &f, &mut h).unwrap();
853
854        let exact_e = Exact::from_f64(e[0]).add(Exact::from_f64(e[1]));
855        let exact_f = Exact::from_f64(f[0]).add(Exact::from_f64(f[1]));
856        let exact_result = exact_e.add(exact_f);
857        let exact_h = expansion_to_exact(&h[..n]);
858        assert!(
859            exact_h.equals(&exact_result),
860            "expansion_sum adversarial: expected {:?}, got {:?}",
861            exact_result,
862            exact_h
863        );
864
865        // The exact result should be 2*tiny.
866        let exact_2tiny = Exact::from_f64(2.0).mul(Exact::from_f64(tiny));
867        assert!(
868            exact_h.equals(&exact_2tiny),
869            "expansion_sum adversarial: expected exactly 2*tiny"
870        );
871    }
872
873    #[test]
874    fn expansion_sum_empty_operands() {
875        let e: [f64; 0] = [];
876        let f = [1.0, 2.0];
877        let mut h = [0.0f64; 4];
878        let n = expansion_sum(&e, &f, &mut h).unwrap();
879        assert_eq!(n, 2);
880        assert_eq!(&h[..n], &f[..]);
881
882        let n = expansion_sum(&f, &e, &mut h).unwrap();
883        assert_eq!(n, 2);
884        assert_eq!(&h[..n], &f[..]);
885    }
886
887    #[test]
888    fn compress_removes_zeros() {
889        // Create a valid expansion with some zero components.
890        // Sorted by increasing magnitude: [0.0, eps, 0.0, 1.0]
891        // (zeros are valid in a Shewchuk expansion — they're smaller than
892        // any non-zero component).
893        let e = [0.0, f64::EPSILON, 0.0, 1.0];
894        let mut h = [0.0f64; 4];
895        let n = compress_expansion(&e, &mut h).unwrap();
896        assert!(n <= 4);
897
898        let exact_e = expansion_to_exact(&e);
899        let exact_h = expansion_to_exact(&h[..n]);
900        assert!(exact_h.equals(&exact_e), "compress: value changed");
901        // The compressed version should not be longer than the input.
902        assert!(n <= e.len());
903    }
904
905    #[test]
906    fn compress_preserves_value() {
907        // A non-trivial expansion sorted by increasing magnitude:
908        // [eps/4, eps/2, eps, 1.0].
909        let e = [f64::EPSILON / 4.0, f64::EPSILON / 2.0, f64::EPSILON, 1.0];
910        let mut h = [0.0f64; 4];
911        let n = compress_expansion(&e, &mut h).unwrap();
912
913        let exact_e = expansion_to_exact(&e);
914        let exact_h = expansion_to_exact(&h[..n]);
915        assert!(
916            exact_h.equals(&exact_e),
917            "compress: value changed for non-trivial expansion"
918        );
919    }
920
921    #[test]
922    fn compress_single_element() {
923        let e = [42.0];
924        let mut h = [0.0f64; 1];
925        let n = compress_expansion(&e, &mut h).unwrap();
926        assert_eq!(n, 1);
927        assert_eq!(h[0], 42.0);
928    }
929
930    #[test]
931    fn compress_empty() {
932        let e: [f64; 0] = [];
933        let mut h = [0.0f64; 0];
934        let n = compress_expansion(&e, &mut h).unwrap();
935        assert_eq!(n, 0);
936    }
937
938    // ── Sign determination tests ─────────────────────────────────────────
939
940    #[test]
941    fn sign_of_expansion_classifies_correctly() {
942        assert_eq!(sign_of_expansion(&[1.0]), Sign::Positive);
943        assert_eq!(sign_of_expansion(&[-1.0]), Sign::Negative);
944        assert_eq!(sign_of_expansion(&[0.0]), Sign::Zero);
945        assert_eq!(sign_of_expansion(&[]), Sign::Zero);
946
947        // The last (largest) component determines the sign.
948        assert_eq!(sign_of_expansion(&[1.0, 2.0]), Sign::Positive);
949        assert_eq!(sign_of_expansion(&[2.0, -1.0]), Sign::Negative);
950        assert_eq!(sign_of_expansion(&[f64::EPSILON, 1.0]), Sign::Positive);
951        assert_eq!(sign_of_expansion(&[f64::EPSILON, -1.0]), Sign::Negative);
952    }
953
954    #[test]
955    fn sign_of_cancellation_expansion() {
956        // [big, -big + tiny] → the last component is -big+tiny which is
957        // negative (since tiny << big). But the exact value is tiny (positive).
958        // This is NOT a properly-formed expansion (the components overlap),
959        // so sign_of_expansion would give the wrong answer. This test
960        // demonstrates why we need compress before checking sign.
961        //
962        // With a properly-formed expansion (after grow_expansion), the
963        // last component carries the true sign.
964        let big = 1e100;
965        let tiny = 1e-100;
966        let mut e = [0.0f64; 1];
967        e[0] = big;
968        let mut h1 = [0.0f64; 2];
969        let n1 = grow_expansion(&e[..1], tiny, &mut h1).unwrap();
970        let mut h2 = [0.0f64; 4];
971        let n2 = grow_expansion(&h1[..n1], -big, &mut h2).unwrap();
972
973        // After compress, the expansion should be [tiny] (or [tiny, 0.0]).
974        let mut h3 = [0.0f64; 4];
975        let n3 = compress_expansion(&h2[..n2], &mut h3).unwrap();
976
977        let sign = sign_of_expansion(&h3[..n3]);
978        assert_eq!(
979            sign,
980            Sign::Positive,
981            "sign should be positive (tiny > 0) after compress"
982        );
983    }
984
985    // ── Determinism tests ────────────────────────────────────────────────
986
987    #[test]
988    fn grow_expansion_is_deterministic() {
989        let e = [1.0, f64::EPSILON, 1e-300];
990        let mut h1 = [0.0f64; 4];
991        let mut h2 = [0.0f64; 4];
992        let n1 = grow_expansion(&e, 3.14, &mut h1).unwrap();
993        let n2 = grow_expansion(&e, 3.14, &mut h2).unwrap();
994        assert_eq!(n1, n2);
995        for i in 0..n1 {
996            assert_eq!(h1[i].to_bits(), h2[i].to_bits(), "bit mismatch at {i}");
997        }
998    }
999
1000    #[test]
1001    fn scale_expansion_is_deterministic() {
1002        let e = [1.0, f64::EPSILON, 1e-300, 0.0];
1003        let mut h1 = [0.0f64; 8];
1004        let mut h2 = [0.0f64; 8];
1005        let n1 = scale_expansion(&e, 2.718, &mut h1).unwrap();
1006        let n2 = scale_expansion(&e, 2.718, &mut h2).unwrap();
1007        assert_eq!(n1, n2);
1008        for i in 0..n1 {
1009            assert_eq!(h1[i].to_bits(), h2[i].to_bits(), "bit mismatch at {i}");
1010        }
1011    }
1012
1013    #[test]
1014    fn expansion_sum_is_deterministic() {
1015        let e = [1.0, f64::EPSILON, 1e-200];
1016        let f = [2.0, -f64::EPSILON, 1e-250];
1017        let mut h1 = [0.0f64; 8];
1018        let mut h2 = [0.0f64; 8];
1019        let n1 = expansion_sum(&e, &f, &mut h1).unwrap();
1020        let n2 = expansion_sum(&e, &f, &mut h2).unwrap();
1021        assert_eq!(n1, n2);
1022        for i in 0..n1 {
1023            assert_eq!(h1[i].to_bits(), h2[i].to_bits(), "bit mismatch at {i}");
1024        }
1025    }
1026
1027    #[test]
1028    fn compress_is_deterministic() {
1029        let e = [1.0, f64::EPSILON, 0.0, 2.0, f64::EPSILON / 2.0];
1030        let mut h1 = [0.0f64; 5];
1031        let mut h2 = [0.0f64; 5];
1032        let n1 = compress_expansion(&e, &mut h1).unwrap();
1033        let n2 = compress_expansion(&e, &mut h2).unwrap();
1034        assert_eq!(n1, n2);
1035        for i in 0..n1 {
1036            assert_eq!(h1[i].to_bits(), h2[i].to_bits(), "bit mismatch at {i}");
1037        }
1038    }
1039
1040    // ── Bounds checking tests ────────────────────────────────────────────
1041
1042    #[test]
1043    fn grow_expansion_rejects_small_buffer() {
1044        let e = [1.0, 2.0, 3.0];
1045        let mut h = [0.0f64; 3]; // need 4
1046        assert_eq!(
1047            grow_expansion(&e, 4.0, &mut h),
1048            Err(ExpansionError::OutputTooSmall)
1049        );
1050    }
1051
1052    #[test]
1053    fn scale_expansion_rejects_small_buffer() {
1054        let e = [1.0, 2.0, 3.0];
1055        let mut h = [0.0f64; 5]; // need 6
1056        assert_eq!(
1057            scale_expansion(&e, 2.0, &mut h),
1058            Err(ExpansionError::OutputTooSmall)
1059        );
1060    }
1061
1062    #[test]
1063    fn expansion_sum_rejects_small_buffer() {
1064        let e = [1.0, 2.0];
1065        let f = [3.0, 4.0, 5.0];
1066        let mut h = [0.0f64; 4]; // need 5
1067        assert_eq!(
1068            expansion_sum(&e, &f, &mut h),
1069            Err(ExpansionError::OutputTooSmall)
1070        );
1071    }
1072
1073    #[test]
1074    fn compress_rejects_small_buffer() {
1075        let e = [1.0, 2.0, 3.0];
1076        let mut h = [0.0f64; 2]; // need 3
1077        assert_eq!(
1078            compress_expansion(&e, &mut h),
1079            Err(ExpansionError::OutputTooSmall)
1080        );
1081    }
1082
1083    // ── Workspace size constant tests ────────────────────────────────────
1084
1085    #[test]
1086    fn workspace_constants_are_sized_for_predicates() {
1087        // The constants must be large enough for the predicate determinants.
1088        // These are lower bounds on the expansion length without compression;
1089        // the constants must be >= these.
1090        assert!(MAX_EXPANSION_ORIENT2 >= 8);
1091        assert!(MAX_EXPANSION_ORIENT3 >= 24);
1092        assert!(MAX_EXPANSION_INCIRCLE >= 96);
1093        assert!(MAX_EXPANSION_INSPHERE >= 2048);
1094    }
1095
1096    // ── Full pipeline test: determinant-like computation ──────────────────
1097
1098    #[test]
1099    fn full_pipeline_2x2_determinant_exact() {
1100        // det = a*d - b*c, computed via expansion arithmetic.
1101        // This is the orient2d determinant pattern.
1102        let a = 1.0;
1103        let b = 1.0 + f64::EPSILON;
1104        let c = 1.0 - f64::EPSILON;
1105        let d = 1.0;
1106
1107        // ad = two_product(a, d) → length-2
1108        let mut ad = [0.0f64; 2];
1109        scalar_product(a, d, &mut ad).unwrap();
1110
1111        // bc = two_product(b, c) → length-2
1112        let mut bc = [0.0f64; 2];
1113        scalar_product(b, c, &mut bc).unwrap();
1114
1115        // negate bc
1116        negate_expansion(&mut bc);
1117
1118        // det = ad + (-bc) = ad - bc
1119        let mut det = [0.0f64; 8];
1120        let n = expansion_sum(&ad, &bc, &mut det).unwrap();
1121
1122        // Compress
1123        let mut compressed = [0.0f64; 8];
1124        let cn = compress_expansion(&det[..n], &mut compressed).unwrap();
1125
1126        let sign = sign_of_expansion(&compressed[..cn]);
1127
1128        // Exact: a*d - b*c = 1 - (1+eps)(1-eps) = 1 - (1 - eps²) = eps² > 0
1129        let exact_ad = Exact::from_f64(a).mul(Exact::from_f64(d));
1130        let exact_bc = Exact::from_f64(b).mul(Exact::from_f64(c));
1131        let exact_det = exact_ad.add(exact_bc.neg());
1132        let exact_sign = exact_det.sign();
1133
1134        assert_eq!(
1135            sign, exact_sign,
1136            "2x2 determinant sign mismatch: expansion says {sign:?}, exact says {exact_sign:?}"
1137        );
1138
1139        // Also verify the exact value matches
1140        let exact_h = expansion_to_exact(&compressed[..cn]);
1141        assert!(
1142            exact_h.equals(&exact_det),
1143            "2x2 determinant exact value mismatch"
1144        );
1145    }
1146
1147    #[test]
1148    fn full_pipeline_3term_sum_adversarial() {
1149        // Three terms that cancel, computed via expansion arithmetic.
1150        // a = 1e100, b = -1e100 (as f64, -1e100 + 1e-100 is just -1e100
1151        // because 1e-100 is below the ULP of 1e100), c = -1e-100.
1152        // Exact result: a + b + c = 1e100 + (-1e100) + (-1e-100) = -1e-100.
1153        // The expansion should preserve the tiny -1e-100 value exactly,
1154        // even though it's far below the ULP of the intermediate 1e100.
1155        let a = 1e100;
1156        let b = -1e100;
1157        let c = -1e-100;
1158
1159        // Start with a as a length-1 expansion
1160        let e = [a];
1161        let mut h1 = [0.0f64; 2];
1162        let n1 = grow_expansion(&e, b, &mut h1).unwrap();
1163
1164        let mut h2 = [0.0f64; 4];
1165        let n2 = grow_expansion(&h1[..n1], c, &mut h2).unwrap();
1166
1167        let mut h3 = [0.0f64; 4];
1168        let n3 = compress_expansion(&h2[..n2], &mut h3).unwrap();
1169
1170        let exact_a = Exact::from_f64(a);
1171        let exact_b = Exact::from_f64(b);
1172        let exact_c = Exact::from_f64(c);
1173        let exact_result = exact_a.add(exact_b).add(exact_c);
1174        let exact_h = expansion_to_exact(&h3[..n3]);
1175
1176        assert!(
1177            exact_h.equals(&exact_result),
1178            "3-term adversarial sum: exact mismatch — expected {:?}, got {:?}",
1179            exact_result,
1180            exact_h
1181        );
1182
1183        // The exact result should be -1e-100 (negative, not zero).
1184        assert_eq!(
1185            exact_result.sign(),
1186            Sign::Negative,
1187            "3-term adversarial sum: expected negative (-1e-100)"
1188        );
1189    }
1190
1191    #[test]
1192    fn full_pipeline_scale_then_sum() {
1193        // Compute (a*b + c*d) using scale + sum, the pattern used in
1194        // determinant computation.
1195        let a = 1.0;
1196        let b = 1.0 + f64::EPSILON;
1197        let c = 1.0 - f64::EPSILON;
1198        let d = 1.0 + 2.0 * f64::EPSILON;
1199
1200        // ab = scale([a], b) → length-2
1201        let mut ab = [0.0f64; 4];
1202        let nab = scale_expansion(&[a], b, &mut ab).unwrap();
1203
1204        // cd = scale([c], d) → length-2
1205        let mut cd = [0.0f64; 4];
1206        let ncd = scale_expansion(&[c], d, &mut cd).unwrap();
1207
1208        // result = ab + cd
1209        let mut result = [0.0f64; 8];
1210        let n = expansion_sum(&ab[..nab], &cd[..ncd], &mut result).unwrap();
1211
1212        let exact_ab = Exact::from_f64(a).mul(Exact::from_f64(b));
1213        let exact_cd = Exact::from_f64(c).mul(Exact::from_f64(d));
1214        let exact_result = exact_ab.add(exact_cd);
1215        let exact_h = expansion_to_exact(&result[..n]);
1216
1217        assert!(
1218            exact_h.equals(&exact_result),
1219            "scale+sum pipeline: exact mismatch"
1220        );
1221    }
1222
1223    // ── Negate test ──────────────────────────────────────────────────────
1224
1225    #[test]
1226    fn negate_flips_sign() {
1227        let mut e = [1.0, -2.0, 3.0];
1228        negate_expansion(&mut e);
1229        assert_eq!(e, [-1.0, 2.0, -3.0]);
1230    }
1231
1232    // ── Convenience wrapper tests ────────────────────────────────────────
1233
1234    #[test]
1235    fn scalar_product_writes_two_components() {
1236        let mut h = [0.0f64; 2];
1237        let n = scalar_product(3.0, 7.0, &mut h).unwrap();
1238        assert_eq!(n, 2);
1239        assert_eq!(h[0], 21.0); // exact product, no error
1240        assert_eq!(h[1], 0.0);
1241    }
1242
1243    #[test]
1244    fn scalar_sum_writes_two_components() {
1245        let mut h = [0.0f64; 2];
1246        let n = scalar_sum(1e100, 1e-100, &mut h).unwrap();
1247        assert_eq!(n, 2);
1248        // The sum is 1e100 (rounded), the error is 1e-100.
1249        assert_eq!(h[0], 1e100);
1250    }
1251
1252    #[test]
1253    fn scalar_product_rejects_small_buffer() {
1254        let mut h = [0.0f64; 1];
1255        assert_eq!(
1256            scalar_product(1.0, 2.0, &mut h),
1257            Err(ExpansionError::OutputTooSmall)
1258        );
1259    }
1260
1261    // ── Sign enum tests ──────────────────────────────────────────────────
1262
1263    #[test]
1264    fn sign_flip() {
1265        assert_eq!(Sign::Positive.flip(), Sign::Negative);
1266        assert_eq!(Sign::Negative.flip(), Sign::Positive);
1267        assert_eq!(Sign::Zero.flip(), Sign::Zero);
1268    }
1269
1270    #[test]
1271    fn sign_from_f64() {
1272        assert_eq!(Sign::from_f64(1.0), Sign::Positive);
1273        assert_eq!(Sign::from_f64(-1.0), Sign::Negative);
1274        assert_eq!(Sign::from_f64(0.0), Sign::Zero);
1275        assert_eq!(Sign::from_f64(1e-300), Sign::Positive);
1276        assert_eq!(Sign::from_f64(-1e-300), Sign::Negative);
1277    }
1278}