1pub const MAX_EXPANSION_ORIENT2: usize = 8;
57
58pub const MAX_EXPANSION_ORIENT3: usize = 24;
62
63pub const MAX_EXPANSION_INCIRCLE: usize = 96;
67
68pub const MAX_EXPANSION_INSPHERE: usize = 2048;
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum ExpansionError {
87 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#[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#[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#[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#[inline]
156pub fn two_diff(a: f64, b: f64) -> (f64, f64) {
157 two_sum(a, -b)
158}
159
160pub 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 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
199pub 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 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 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
241pub 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 let mut ei = 0usize; let mut fi = 0usize; let mut hi = 0usize; 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 if from_e {
283 ei = 1;
284 } else {
285 fi = 1;
286 }
287
288 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 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 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 h[hi] = current;
330 hi += 1;
331
332 Ok(hi)
333}
334
335pub 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 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 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 if q != 0.0 || out == 0 {
395 h[out] = q;
396 out += 1;
397 }
398
399 Ok(out)
400}
401
402#[inline]
404pub fn negate_expansion(e: &mut [f64]) {
405 for x in e.iter_mut() {
406 *x = -*x;
407 }
408}
409
410#[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 #[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#[inline]
455pub fn sign_of_expansion(e: &[f64]) -> Sign {
456 if e.is_empty() {
457 return Sign::Zero;
458 }
459 Sign::from_f64(e[e.len() - 1])
462}
463
464#[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#[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#[cfg(test)]
502mod tests {
503 use super::*;
504
505 use num_bigint::BigInt;
514
515 #[derive(Debug, Clone)]
517 struct Exact {
518 mantissa: BigInt,
519 exponent: i32,
520 }
521
522 impl Exact {
523 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 Exact {
539 mantissa: BigInt::from(sign) * BigInt::from(raw_mant),
540 exponent: -1074,
541 }
542 } else {
543 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 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 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 if diff > 0 {
572 hi.mantissa <<= diff;
573 }
574 Exact {
575 mantissa: lo.mantissa + hi.mantissa,
576 exponent: lo.exponent,
577 }
578 }
579
580 fn mul(self, other: Self) -> Self {
582 Exact {
583 mantissa: self.mantissa * other.mantissa,
584 exponent: self.exponent + other.exponent,
585 }
586 }
587
588 fn neg(self) -> Self {
590 Exact {
591 mantissa: -self.mantissa,
592 exponent: self.exponent,
593 }
594 }
595
596 fn equals(&self, other: &Self) -> bool {
599 let a = self.clone().normalize();
601 let b = other.clone().normalize();
602 a.mantissa == b.mantissa && a.exponent == b.exponent
603 }
604
605 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 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 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 #[test]
648 fn two_sum_is_error_free() {
649 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 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 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 #[test]
742 fn grow_expansion_adds_scalar_exactly() {
743 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 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 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 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 let (s, e_err) = two_sum(-1.0, 1.0 + f64::EPSILON);
807 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 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 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 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 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 assert!(n <= e.len());
903 }
904
905 #[test]
906 fn compress_preserves_value() {
907 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 #[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 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 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 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 #[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 #[test]
1043 fn grow_expansion_rejects_small_buffer() {
1044 let e = [1.0, 2.0, 3.0];
1045 let mut h = [0.0f64; 3]; 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]; 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]; 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]; assert_eq!(
1078 compress_expansion(&e, &mut h),
1079 Err(ExpansionError::OutputTooSmall)
1080 );
1081 }
1082
1083 #[test]
1086 fn workspace_constants_are_sized_for_predicates() {
1087 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 #[test]
1099 fn full_pipeline_2x2_determinant_exact() {
1100 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 let mut ad = [0.0f64; 2];
1109 scalar_product(a, d, &mut ad).unwrap();
1110
1111 let mut bc = [0.0f64; 2];
1113 scalar_product(b, c, &mut bc).unwrap();
1114
1115 negate_expansion(&mut bc);
1117
1118 let mut det = [0.0f64; 8];
1120 let n = expansion_sum(&ad, &bc, &mut det).unwrap();
1121
1122 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 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 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 let a = 1e100;
1156 let b = -1e100;
1157 let c = -1e-100;
1158
1159 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 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 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 let mut ab = [0.0f64; 4];
1202 let nab = scale_expansion(&[a], b, &mut ab).unwrap();
1203
1204 let mut cd = [0.0f64; 4];
1206 let ncd = scale_expansion(&[c], d, &mut cd).unwrap();
1207
1208 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 #[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 #[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); 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 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 #[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}