1#![forbid(unsafe_code)]
2
3use core::hint::cold_path;
98use core::mem::take;
99use core::num::NonZeroU64;
100use std::array::from_fn;
101
102use num_bigint::{BigInt, Sign};
103use num_rational::BigRational;
104use num_traits::ToPrimitive;
105
106use crate::matrix::Matrix;
107use crate::rational::RationalVector;
108use crate::vector::Vector;
109use crate::{LaError, UnrepresentableReason};
110
111#[must_use]
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub enum DeterminantSign {
129 Negative,
131 Zero,
133 Positive,
135}
136
137impl DeterminantSign {
138 #[inline]
140 #[must_use]
141 pub const fn as_i8(self) -> i8 {
142 match self {
143 Self::Negative => -1,
144 Self::Zero => 0,
145 Self::Positive => 1,
146 }
147 }
148}
149
150pub trait ExactF64Conversion {
186 type Output;
188
189 fn try_to_f64(&self) -> Result<Self::Output, LaError>;
203
204 fn to_rounded_f64(&self) -> Result<Self::Output, LaError>;
213}
214
215const F64_SIGNIFICAND_BITS: i64 = 53;
216const F64_FRACTION_BITS: i64 = 52;
217const F64_MIN_BINARY_EXPONENT: i64 = -1074;
218const F64_MIN_NORMAL_EXPONENT: i64 = -1022;
219const F64_MAX_BINARY_EXPONENT: i64 = 1023;
220const F64_EXPONENT_BIAS: i64 = 1023;
221const F64_FRACTION_MASK: u64 = (1u64 << 52) - 1;
222
223const fn decompose_proven_finite_f64(x: f64) -> Component {
230 let bits = x.to_bits();
231 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
232 let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
233
234 if biased_exp == 0 && fraction == 0 {
236 return Component::Zero;
237 }
238
239 let (mantissa, raw_exp) = if biased_exp == 0 {
240 (fraction, -1074_i32)
243 } else {
244 ((1u64 << 52) | fraction, biased_exp - 1075)
247 };
248
249 let tz = mantissa.trailing_zeros();
252 let Some(mantissa) = NonZeroU64::new(mantissa >> tz) else {
253 return Component::Zero;
254 };
255
256 Component::NonZero {
257 mantissa,
258 exponent: raw_exp + tz.cast_signed(),
259 is_negative: bits >> 63 != 0,
260 }
261}
262
263fn big_int_exp_to_big_rational(mut value: BigInt, mut exp: i32) -> BigRational {
269 if value == BigInt::from(0) {
270 return BigRational::from_integer(BigInt::from(0));
271 }
272
273 if exp < 0
275 && let Some(tz) = value.trailing_zeros()
276 {
277 let exp_abs = exp.unsigned_abs();
278 let reduce = tz.min(u64::from(exp_abs));
279 value >>= reduce;
280 let remaining_abs = u64::from(exp_abs) - reduce;
281 exp = negative_exponent_from_magnitude(remaining_abs);
282 }
283
284 if exp >= 0 {
285 BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
286 } else {
287 BigRational::new_raw(value, BigInt::from(1u32) << exp.unsigned_abs())
288 }
289}
290
291#[inline]
297fn negative_exponent_from_magnitude(magnitude: u64) -> i32 {
298 if magnitude == u64::from(i32::MIN.unsigned_abs()) {
299 return i32::MIN;
300 }
301
302 let Ok(value) = i32::try_from(magnitude) else {
303 cold_path();
304 unreachable!("negative exponent magnitude exceeds the i32 domain");
305 };
306 -value
307}
308
309fn exact_rational_to_finite_f64(exact: &BigRational, index: Option<usize>) -> Result<f64, LaError> {
323 if exact.denom().sign() == Sign::NoSign {
324 cold_path();
325 return Err(LaError::unrepresentable(
326 index,
327 UnrepresentableReason::NotFinite,
328 ));
329 }
330
331 if exact.numer().sign() == Sign::NoSign {
332 return Ok(0.0);
333 }
334
335 let denominator = exact.denom();
336 if denominator.sign() == Sign::Plus
337 && let Some(denominator_exp) = positive_power_of_two_exponent(denominator)
338 && let Ok(denominator_exp) = i32::try_from(denominator_exp)
339 {
340 return big_int_exp_ref_to_finite_f64(exact.numer(), -denominator_exp, index, || {
341 rounded_rational_unrepresentable_reason(exact)
342 });
343 }
344
345 let reduced = exact.reduced();
350 reduced_rational_to_finite_f64(&reduced, index)
351}
352
353fn positive_power_of_two_exponent(value: &BigInt) -> Option<u64> {
355 if value.sign() != Sign::Plus {
356 return None;
357 }
358
359 let exponent = value.trailing_zeros()?;
360 (value.bits().checked_sub(1) == Some(exponent)).then_some(exponent)
361}
362
363fn reduced_rational_to_finite_f64(
377 exact: &BigRational,
378 index: Option<usize>,
379) -> Result<f64, LaError> {
380 let Some(denominator_exp) = positive_power_of_two_exponent(exact.denom()) else {
381 cold_path();
382 return Err(LaError::unrepresentable(
383 index,
384 rounded_rational_unrepresentable_reason(exact),
385 ));
386 };
387 let Ok(denominator_exp) = i32::try_from(denominator_exp) else {
388 cold_path();
389 return Err(LaError::unrepresentable(
390 index,
391 rounded_rational_unrepresentable_reason(exact),
392 ));
393 };
394
395 big_int_exp_ref_to_finite_f64(exact.numer(), -denominator_exp, index, || {
396 rounded_rational_unrepresentable_reason(exact)
397 })
398}
399
400fn rounded_rational_unrepresentable_reason(exact: &BigRational) -> UnrepresentableReason {
406 match exact.to_f64() {
407 Some(value) if value.is_finite() => UnrepresentableReason::RequiresRounding,
408 _ => UnrepresentableReason::NotFinite,
409 }
410}
411
412fn exact_rational_to_rounded_f64(
415 exact: &BigRational,
416 index: Option<usize>,
417) -> Result<f64, LaError> {
418 if exact.denom().sign() == Sign::NoSign {
419 cold_path();
420 return Err(LaError::unrepresentable(
421 index,
422 UnrepresentableReason::NotFinite,
423 ));
424 }
425 if exact.numer().sign() == Sign::NoSign {
426 return Ok(0.0);
427 }
428
429 let Some(value) = exact.to_f64() else {
430 cold_path();
431 return Err(LaError::unrepresentable(
432 index,
433 UnrepresentableReason::NotFinite,
434 ));
435 };
436 if value.is_finite() {
437 Ok(value)
438 } else {
439 cold_path();
440 Err(LaError::unrepresentable(
441 index,
442 UnrepresentableReason::NotFinite,
443 ))
444 }
445}
446
447impl ExactF64Conversion for BigRational {
448 type Output = f64;
449
450 #[inline]
451 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
452 exact_rational_to_finite_f64(self, None)
453 }
454
455 #[inline]
456 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
457 exact_rational_to_rounded_f64(self, None)
458 }
459}
460
461impl<const D: usize> ExactF64Conversion for [BigRational; D] {
462 type Output = Vector<D>;
463
464 #[inline]
465 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
466 let mut result = [0.0; D];
467 for (index, value) in self.iter().enumerate() {
468 result[index] = exact_rational_to_finite_f64(value, Some(index))?;
469 }
470 Vector::try_new(result)
471 }
472
473 #[inline]
474 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
475 let mut result = [0.0; D];
476 for (index, value) in self.iter().enumerate() {
477 result[index] = exact_rational_to_rounded_f64(value, Some(index))?;
478 }
479 Vector::try_new(result)
480 }
481}
482
483impl<const D: usize> ExactF64Conversion for RationalVector<D> {
484 type Output = Vector<D>;
485
486 #[inline]
487 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
488 let mut result = [0.0; D];
489 for (index, value) in self.as_array().iter().enumerate() {
490 result[index] = reduced_rational_to_finite_f64(value, Some(index))?;
492 }
493 Vector::try_new(result)
494 }
495
496 #[inline]
497 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
498 self.as_array().to_rounded_f64()
499 }
500}
501
502fn shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option<u64> {
518 let word_bits = u64::from(u64::BITS);
519 let word_index = usize::try_from(shift / word_bits).ok()?;
520 let bit_shift = u32::try_from(shift % word_bits).ok()?;
521 let mut digits = value.iter_u64_digits().skip(word_index);
522 let low = digits.next()? >> bit_shift;
523 if bit_shift == 0 {
524 Some(low)
525 } else {
526 let high = digits.next().unwrap_or(0) << (u64::BITS - bit_shift);
527 Some(low | high)
528 }
529}
530
531fn magnitude_bit_is_set(value: &BigInt, bit: u64) -> bool {
533 let word_bits = u64::from(u64::BITS);
534 let Ok(word_index) = usize::try_from(bit / word_bits) else {
535 return false;
536 };
537 let bit_index = u32::try_from(bit % word_bits).unwrap_or(0);
538 value
539 .iter_u64_digits()
540 .nth(word_index)
541 .is_some_and(|word| word & (1_u64 << bit_index) != 0)
542}
543
544fn magnitude_has_lower_bits(value: &BigInt, exclusive_end: u64) -> bool {
546 let word_bits = u64::from(u64::BITS);
547 let Ok(full_words) = usize::try_from(exclusive_end / word_bits) else {
548 return value.sign() != Sign::NoSign;
549 };
550 let partial_bits = u32::try_from(exclusive_end % word_bits).unwrap_or(0);
551 let mut digits = value.iter_u64_digits();
552
553 for _ in 0..full_words {
554 if digits.next().unwrap_or(0) != 0 {
555 return true;
556 }
557 }
558
559 if partial_bits == 0 {
560 false
561 } else {
562 let mask = (1_u64 << partial_bits) - 1;
563 digits.next().is_some_and(|word| word & mask != 0)
564 }
565}
566
567fn rounded_shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option<u64> {
573 if shift > value.bits() {
574 return Some(0);
575 }
576 let retained = shifted_magnitude_to_u64(value, shift).unwrap_or(0);
577 if shift == 0 {
578 return Some(retained);
579 }
580
581 let guard_bit = shift - 1;
582 let increment = magnitude_bit_is_set(value, guard_bit)
583 && (magnitude_has_lower_bits(value, guard_bit) || retained & 1 != 0);
584 retained.checked_add(u64::from(increment))
585}
586
587#[inline]
590fn inexact_big_int_reason(
591 top_bit_exp: i64,
592 rounded_reason: impl FnOnce() -> UnrepresentableReason,
593) -> UnrepresentableReason {
594 if top_bit_exp < F64_MAX_BINARY_EXPONENT {
595 UnrepresentableReason::RequiresRounding
596 } else {
597 rounded_reason()
598 }
599}
600
601fn big_int_exp_ref_to_rounded_f64(
608 value: &BigInt,
609 exp: i32,
610 index: Option<usize>,
611) -> Result<f64, LaError> {
612 if value.sign() == Sign::NoSign {
613 return Ok(0.0);
614 }
615
616 let sign = if value.sign() == Sign::Minus {
617 1_u64 << 63
618 } else {
619 0
620 };
621 let Ok(bit_len) = i64::try_from(value.bits()) else {
622 cold_path();
623 return Err(LaError::unrepresentable(
624 index,
625 UnrepresentableReason::NotFinite,
626 ));
627 };
628 let Some(mut top_bit_exp) = i64::from(exp).checked_add(bit_len - 1) else {
629 cold_path();
630 return Err(LaError::unrepresentable(
631 index,
632 UnrepresentableReason::NotFinite,
633 ));
634 };
635 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
636 cold_path();
637 return Err(LaError::unrepresentable(
638 index,
639 UnrepresentableReason::NotFinite,
640 ));
641 }
642
643 if top_bit_exp >= F64_MIN_NORMAL_EXPONENT {
644 let mut significand = if bit_len <= F64_SIGNIFICAND_BITS {
645 let Some(magnitude) = shifted_magnitude_to_u64(value, 0) else {
646 cold_path();
647 unreachable!("nonzero integer must expose magnitude digits");
648 };
649 let shift = u32::try_from(F64_SIGNIFICAND_BITS - bit_len)
650 .unwrap_or_else(|_| unreachable!("normal significand shift must fit u32"));
651 magnitude
652 .checked_shl(shift)
653 .unwrap_or_else(|| unreachable!("normal significand must fit u64"))
654 } else {
655 let shift = u64::try_from(bit_len - F64_SIGNIFICAND_BITS)
656 .unwrap_or_else(|_| unreachable!("positive significand shift must fit u64"));
657 rounded_shifted_magnitude_to_u64(value, shift)
658 .unwrap_or_else(|| unreachable!("rounded binary64 significand must fit u64"))
659 };
660
661 if significand == 1_u64 << F64_SIGNIFICAND_BITS {
662 significand >>= 1;
663 top_bit_exp += 1;
664 }
665 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
666 cold_path();
667 return Err(LaError::unrepresentable(
668 index,
669 UnrepresentableReason::NotFinite,
670 ));
671 }
672
673 let biased_exp = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS)
674 .unwrap_or_else(|_| unreachable!("normal exponent must be positive"));
675 return Ok(f64::from_bits(
676 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
677 ));
678 }
679
680 let subnormal_shift = i64::from(exp) - F64_MIN_BINARY_EXPONENT;
681 let significand = if subnormal_shift >= 0 {
682 let Some(magnitude) = shifted_magnitude_to_u64(value, 0) else {
683 cold_path();
684 unreachable!("nonzero integer must expose magnitude digits");
685 };
686 let shift = u32::try_from(subnormal_shift)
687 .unwrap_or_else(|_| unreachable!("subnormal left shift must fit u32"));
688 magnitude
689 .checked_shl(shift)
690 .unwrap_or_else(|| unreachable!("subnormal significand must fit u64"))
691 } else {
692 let shift = u64::try_from(-subnormal_shift)
693 .unwrap_or_else(|_| unreachable!("subnormal right shift must fit u64"));
694 rounded_shifted_magnitude_to_u64(value, shift)
695 .unwrap_or_else(|| unreachable!("rounded subnormal significand must fit u64"))
696 };
697
698 if significand == 1_u64 << F64_FRACTION_BITS {
699 return Ok(f64::from_bits(sign | (1_u64 << F64_FRACTION_BITS)));
700 }
701 Ok(f64::from_bits(sign | significand))
702}
703
704fn big_int_exp_ref_to_finite_f64(
714 value: &BigInt,
715 exp: i32,
716 index: Option<usize>,
717 rounded_reason: impl FnOnce() -> UnrepresentableReason,
718) -> Result<f64, LaError> {
719 if value.sign() == Sign::NoSign {
720 return Ok(0.0);
721 }
722
723 let is_negative = value.sign() == Sign::Minus;
724 let mut exp = i64::from(exp);
725 let Some(trailing_zeros) = value.trailing_zeros() else {
726 cold_path();
727 unreachable!("nonzero integer must have a least-significant set bit");
728 };
729 let Ok(trailing_zeros_i64) = i64::try_from(trailing_zeros) else {
730 cold_path();
731 return Err(LaError::unrepresentable(
732 index,
733 UnrepresentableReason::NotFinite,
734 ));
735 };
736 let Some(updated_exp) = exp.checked_add(trailing_zeros_i64) else {
737 cold_path();
738 return Err(LaError::unrepresentable(
739 index,
740 UnrepresentableReason::NotFinite,
741 ));
742 };
743 exp = updated_exp;
744
745 let Some(bit_len) = value.bits().checked_sub(trailing_zeros) else {
746 cold_path();
747 unreachable!("trailing-zero count cannot exceed integer bit length");
748 };
749 let Ok(bit_len) = i64::try_from(bit_len) else {
750 cold_path();
751 return Err(LaError::unrepresentable(
752 index,
753 UnrepresentableReason::NotFinite,
754 ));
755 };
756 let Some(top_bit_exp) = exp.checked_add(bit_len - 1) else {
757 cold_path();
758 return Err(LaError::unrepresentable(
759 index,
760 UnrepresentableReason::NotFinite,
761 ));
762 };
763 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
764 cold_path();
765 return Err(LaError::unrepresentable(
766 index,
767 UnrepresentableReason::NotFinite,
768 ));
769 }
770 if exp < F64_MIN_BINARY_EXPONENT {
771 cold_path();
772 let reason = inexact_big_int_reason(top_bit_exp, rounded_reason);
776 return Err(LaError::unrepresentable(index, reason));
777 }
778 if bit_len > F64_SIGNIFICAND_BITS {
779 cold_path();
780 let reason = inexact_big_int_reason(top_bit_exp, rounded_reason);
784 return Err(LaError::unrepresentable(index, reason));
785 }
786
787 let Some(mantissa) = shifted_magnitude_to_u64(value, trailing_zeros) else {
788 cold_path();
789 return Err(LaError::unrepresentable(
790 index,
791 UnrepresentableReason::NotFinite,
792 ));
793 };
794 let sign = if is_negative { 1u64 << 63 } else { 0 };
795
796 if top_bit_exp < F64_MIN_NORMAL_EXPONENT {
797 let Ok(shift) = u32::try_from(exp - F64_MIN_BINARY_EXPONENT) else {
798 cold_path();
799 return Err(LaError::unrepresentable(
800 index,
801 UnrepresentableReason::RequiresRounding,
802 ));
803 };
804 Ok(f64::from_bits(sign | (mantissa << shift)))
805 } else {
806 let Ok(biased_exp) = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS) else {
807 cold_path();
808 return Err(LaError::unrepresentable(
809 index,
810 UnrepresentableReason::NotFinite,
811 ));
812 };
813 let Ok(shift) = u32::try_from(F64_FRACTION_BITS - (bit_len - 1)) else {
814 cold_path();
815 return Err(LaError::unrepresentable(
816 index,
817 UnrepresentableReason::RequiresRounding,
818 ));
819 };
820 let significand = mantissa << shift;
821 Ok(f64::from_bits(
822 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
823 ))
824 }
825}
826
827fn big_int_exp_to_finite_f64(
828 value: &BigInt,
829 exp: i32,
830 index: Option<usize>,
831) -> Result<f64, LaError> {
832 big_int_exp_ref_to_finite_f64(value, exp, index, || {
833 match big_int_exp_ref_to_rounded_f64(value, exp, index) {
834 Ok(_) => UnrepresentableReason::RequiresRounding,
835 Err(_) => UnrepresentableReason::NotFinite,
836 }
837 })
838}
839
840fn big_int_exp_to_rounded_f64(value: &BigInt, exp: i32) -> Result<f64, LaError> {
842 big_int_exp_ref_to_rounded_f64(value, exp, None)
843}
844
845#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
864enum Component {
865 #[default]
866 Zero,
867 NonZero {
868 mantissa: NonZeroU64,
869 exponent: i32,
870 is_negative: bool,
871 },
872}
873
874impl Component {
875 const fn exponent(self) -> Option<i32> {
877 match self {
878 Self::Zero => None,
879 Self::NonZero { exponent, .. } => Some(exponent),
880 }
881 }
882}
883
884mod decomposition {
885 use super::Component;
886
887 #[derive(Clone, Debug, Eq, PartialEq)]
893 pub(super) struct Decomposed<T> {
894 components: T,
895 min_exponent: Option<i32>,
896 }
897
898 impl<T> Decomposed<T> {
899 pub(super) const fn components(&self) -> &T {
901 &self.components
902 }
903
904 pub(super) const fn min_exponent(&self) -> Option<i32> {
906 self.min_exponent
907 }
908 }
909
910 impl<const D: usize> Decomposed<[Component; D]> {
911 pub(super) fn from_vector_components(components: [Component; D]) -> Self {
913 let min_exponent = components
914 .iter()
915 .filter_map(|component| component.exponent())
916 .min();
917 Self {
918 components,
919 min_exponent,
920 }
921 }
922 }
923
924 impl<const D: usize> Decomposed<[[Component; D]; D]> {
925 pub(super) fn from_matrix_components(components: [[Component; D]; D]) -> Self {
927 let min_exponent = components
928 .iter()
929 .flatten()
930 .filter_map(|component| component.exponent())
931 .min();
932 Self {
933 components,
934 min_exponent,
935 }
936 }
937 }
938
939 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
944 pub(super) struct ScaleExponent {
945 value: i32,
946 }
947
948 impl ScaleExponent {
949 pub(super) const ZERO: Self = Self { value: 0 };
951
952 pub(super) const fn for_decomposed<T>(decomposed: &Decomposed<T>) -> Self {
954 let value = match decomposed.min_exponent() {
955 Some(exponent) => exponent,
956 None => 0,
957 };
958 Self { value }
959 }
960
961 pub(super) const fn min(self, other: Self) -> Self {
963 if self.value < other.value {
964 self
965 } else {
966 other
967 }
968 }
969
970 pub(super) const fn get(self) -> i32 {
972 self.value
973 }
974
975 pub(super) fn shift_for(self, exponent: i32) -> u32 {
981 let Some(shift) = exponent.checked_sub(self.value) else {
982 unreachable!("finite f64 exponent difference cannot overflow");
983 };
984 let Ok(shift) = u32::try_from(shift) else {
985 unreachable!("scale exponent cannot exceed a component exponent");
986 };
987 shift
988 }
989 }
990}
991
992use decomposition::{Decomposed, ScaleExponent};
993
994fn decompose_proven_finite_matrix<const D: usize>(
996 m: &Matrix<D>,
997) -> Decomposed<[[Component; D]; D]> {
998 let components =
999 from_fn(|row| from_fn(|col| decompose_proven_finite_f64(m.as_rows()[row][col])));
1000 Decomposed::from_matrix_components(components)
1001}
1002
1003fn decompose_proven_finite_vector<const D: usize>(v: &Vector<D>) -> Decomposed<[Component; D]> {
1005 let components = from_fn(|index| decompose_proven_finite_f64(v.as_array()[index]));
1006 Decomposed::from_vector_components(components)
1007}
1008
1009#[inline]
1012fn component_to_big_int(component: Component, scale: ScaleExponent) -> BigInt {
1013 match component {
1014 Component::Zero => BigInt::from(0),
1015 Component::NonZero {
1016 mantissa,
1017 exponent,
1018 is_negative,
1019 } => {
1020 let value = BigInt::from(mantissa.get()) << scale.shift_for(exponent);
1021 if is_negative { -value } else { value }
1022 }
1023 }
1024}
1025
1026fn build_big_int_matrix<const D: usize>(
1028 components: &[[Component; D]; D],
1029 scale: ScaleExponent,
1030) -> [[BigInt; D]; D] {
1031 from_fn(|row| from_fn(|col| component_to_big_int(components[row][col], scale)))
1032}
1033
1034fn build_big_int_vec<const D: usize>(
1036 components: &[Component; D],
1037 scale: ScaleExponent,
1038) -> [BigInt; D] {
1039 from_fn(|index| component_to_big_int(components[index], scale))
1040}
1041
1042#[inline]
1044fn det2_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1045 &a[0][0] * &a[1][1] - &a[0][1] * &a[1][0]
1046}
1047
1048#[inline]
1054fn det3_big_int_entries(a: [[&BigInt; 3]; 3]) -> BigInt {
1055 let m00 = a[1][1] * a[2][2] - a[1][2] * a[2][1];
1056 let m01 = a[1][0] * a[2][2] - a[1][2] * a[2][0];
1057 let m02 = a[1][0] * a[2][1] - a[1][1] * a[2][0];
1058 a[0][0] * m00 - a[0][1] * m01 + a[0][2] * m02
1059}
1060
1061#[inline]
1063fn det3_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1064 det3_big_int_entries([
1065 [&a[0][0], &a[0][1], &a[0][2]],
1066 [&a[1][0], &a[1][1], &a[1][2]],
1067 [&a[2][0], &a[2][1], &a[2][2]],
1068 ])
1069}
1070
1071#[inline]
1079fn det4_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1080 if a[0][..4].iter().all(|value| value.sign() != Sign::NoSign) {
1081 let m01 = &a[2][0] * &a[3][1] - &a[2][1] * &a[3][0];
1084 let m02 = &a[2][0] * &a[3][2] - &a[2][2] * &a[3][0];
1085 let m03 = &a[2][0] * &a[3][3] - &a[2][3] * &a[3][0];
1086 let m12 = &a[2][1] * &a[3][2] - &a[2][2] * &a[3][1];
1087 let m13 = &a[2][1] * &a[3][3] - &a[2][3] * &a[3][1];
1088 let m23 = &a[2][2] * &a[3][3] - &a[2][3] * &a[3][2];
1089 let c00 = &a[1][1] * &m23 - &a[1][2] * &m13 + &a[1][3] * &m12;
1090 let mut det = &a[0][0] * c00;
1091 let c01 = &a[1][0] * m23 - &a[1][2] * &m03 + &a[1][3] * &m02;
1092 det -= &a[0][1] * c01;
1093 let c02 = &a[1][0] * m13 - &a[1][1] * m03 + &a[1][3] * &m01;
1094 det += &a[0][2] * c02;
1095 let c03 = &a[1][0] * m12 - &a[1][1] * m02 + &a[1][2] * m01;
1096 return det - &a[0][3] * c03;
1097 }
1098
1099 let mut det = BigInt::from(0);
1100
1101 if a[0][0].sign() != Sign::NoSign {
1102 let c00 = det3_big_int_entries([
1103 [&a[1][1], &a[1][2], &a[1][3]],
1104 [&a[2][1], &a[2][2], &a[2][3]],
1105 [&a[3][1], &a[3][2], &a[3][3]],
1106 ]);
1107 det += &a[0][0] * c00;
1108 }
1109 if a[0][1].sign() != Sign::NoSign {
1110 let c01 = det3_big_int_entries([
1111 [&a[1][0], &a[1][2], &a[1][3]],
1112 [&a[2][0], &a[2][2], &a[2][3]],
1113 [&a[3][0], &a[3][2], &a[3][3]],
1114 ]);
1115 det -= &a[0][1] * c01;
1116 }
1117 if a[0][2].sign() != Sign::NoSign {
1118 let c02 = det3_big_int_entries([
1119 [&a[1][0], &a[1][1], &a[1][3]],
1120 [&a[2][0], &a[2][1], &a[2][3]],
1121 [&a[3][0], &a[3][1], &a[3][3]],
1122 ]);
1123 det += &a[0][2] * c02;
1124 }
1125 if a[0][3].sign() != Sign::NoSign {
1126 let c03 = det3_big_int_entries([
1127 [&a[1][0], &a[1][1], &a[1][2]],
1128 [&a[2][0], &a[2][1], &a[2][2]],
1129 [&a[3][0], &a[3][1], &a[3][2]],
1130 ]);
1131 det -= &a[0][3] * c03;
1132 }
1133
1134 det
1135}
1136
1137pub(crate) fn det_big_int<const D: usize>(mut a: [[BigInt; D]; D]) -> BigInt {
1140 if D == 0 {
1141 return BigInt::from(1);
1142 }
1143
1144 match D {
1145 1 => take(&mut a[0][0]),
1146 2 => det2_big_int(&a),
1147 3 => det3_big_int(&a),
1148 4 => det4_big_int(&a),
1149 _ => {
1150 let odd_swaps = match bareiss_forward_eliminate(&mut a, None) {
1151 BareissResult::Upper { odd_swaps } => odd_swaps,
1152 BareissResult::Singular { .. } => {
1153 cold_path();
1154 return BigInt::from(0);
1155 }
1156 };
1157
1158 let determinant = take(&mut a[D - 1][D - 1]);
1159 if odd_swaps { -determinant } else { determinant }
1160 }
1161 }
1162}
1163
1164#[derive(Debug)]
1166enum BareissResult {
1167 Upper { odd_swaps: bool },
1170 Singular { pivot_col: usize },
1172}
1173
1174fn bareiss_forward_eliminate<const D: usize>(
1188 a: &mut [[BigInt; D]; D],
1189 mut rhs: Option<&mut [BigInt; D]>,
1190) -> BareissResult {
1191 let zero = BigInt::from(0);
1192 let mut prev_pivot = BigInt::from(1);
1193 let mut odd_swaps = false;
1194
1195 for k in 0..D {
1196 if a[k][k] == zero {
1198 let mut found = false;
1199 for i in (k + 1)..D {
1200 if a[i][k] != zero {
1201 a.swap(k, i);
1202 if let Some(r) = &mut rhs {
1203 r.swap(k, i);
1204 }
1205 odd_swaps = !odd_swaps;
1206 found = true;
1207 break;
1208 }
1209 }
1210 if !found {
1211 cold_path();
1212 return BareissResult::Singular { pivot_col: k };
1213 }
1214 }
1215
1216 if k + 1 == D {
1219 break;
1220 }
1221
1222 for i in (k + 1)..D {
1226 for j in (k + 1)..D {
1227 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
1228 }
1229 if let Some(r) = &mut rhs {
1230 r[i] = (&a[k][k] * &r[i] - &a[i][k] * &r[k]) / &prev_pivot;
1231 }
1232 a[i][k].clone_from(&zero);
1233 }
1234
1235 prev_pivot.clone_from(&a[k][k]);
1236 }
1237
1238 #[cfg(debug_assertions)]
1242 for (k, row) in a.iter().enumerate() {
1243 assert_ne!(row[k], zero, "pivot at ({k}, {k}) must be non-zero");
1244 for (i, lower_row) in a.iter().enumerate().skip(k + 1) {
1245 assert_eq!(
1246 lower_row[k], zero,
1247 "sub-diagonal at ({i}, {k}) must be zero"
1248 );
1249 }
1250 }
1251
1252 BareissResult::Upper { odd_swaps }
1253}
1254
1255fn determinant_scale_exp<const D: usize>(e_min: i32) -> Result<i32, LaError> {
1265 let Ok(d_i32) = i32::try_from(D) else {
1266 cold_path();
1267 return Err(LaError::determinant_scale_overflow(D, e_min));
1268 };
1269 let Some(total_exp) = e_min.checked_mul(d_i32) else {
1270 cold_path();
1271 return Err(LaError::determinant_scale_overflow(D, e_min));
1272 };
1273 Ok(total_exp)
1274}
1275
1276fn scaled_det_int_finite<const D: usize>(m: &Matrix<D>) -> (BigInt, ScaleExponent) {
1289 let decomposed = decompose_proven_finite_matrix(m);
1290 scaled_det_int_decomposed(&decomposed)
1291}
1292
1293fn scaled_det_int_decomposed<const D: usize>(
1295 decomposed: &Decomposed<[[Component; D]; D]>,
1296) -> (BigInt, ScaleExponent) {
1297 if D == 0 {
1300 return (BigInt::from(1), ScaleExponent::ZERO);
1301 }
1302
1303 if decomposed.min_exponent().is_none() {
1304 return (BigInt::from(0), ScaleExponent::ZERO);
1305 }
1306 let scale = ScaleExponent::for_decomposed(decomposed);
1307 let a = build_big_int_matrix(decomposed.components(), scale);
1308 let det_int = det_big_int(a);
1309
1310 (det_int, scale)
1311}
1312
1313fn exact_det_int_finite<const D: usize>(m: &Matrix<D>) -> Result<(BigInt, i32), LaError> {
1319 let (det_int, scale) = scaled_det_int_finite(m);
1320 if det_int.sign() == Sign::NoSign {
1321 return Ok((det_int, 0));
1322 }
1323 let total_exp = determinant_scale_exp::<D>(scale.get())?;
1324 Ok((det_int, total_exp))
1325}
1326
1327fn exact_det_finite<const D: usize>(m: &Matrix<D>) -> Result<BigRational, LaError> {
1331 let (det_int, total_exp) = exact_det_int_finite(m)?;
1332 Ok(big_int_exp_to_big_rational(det_int, total_exp))
1333}
1334
1335fn bareiss_solve_finite<const D: usize>(
1344 m: &Matrix<D>,
1345 b: &Vector<D>,
1346) -> Result<[BigRational; D], LaError> {
1347 let matrix = decompose_proven_finite_matrix(m);
1348 let rhs = decompose_proven_finite_vector(b);
1349 bareiss_solve_components(&matrix, &rhs)
1350}
1351
1352fn bareiss_solve_components<const D: usize>(
1368 matrix: &Decomposed<[[Component; D]; D]>,
1369 rhs: &Decomposed<[Component; D]>,
1370) -> Result<[BigRational; D], LaError> {
1371 const MAX_SHARED_SCALE_GAP_BITS: u32 = 64;
1372
1373 let independent_matrix_scale = ScaleExponent::for_decomposed(matrix);
1374 let independent_rhs_scale = ScaleExponent::for_decomposed(rhs);
1375 let scale_gap = independent_matrix_scale
1376 .get()
1377 .abs_diff(independent_rhs_scale.get());
1378 let (matrix_scale, rhs_scale) = if scale_gap <= MAX_SHARED_SCALE_GAP_BITS {
1379 let shared = independent_matrix_scale.min(independent_rhs_scale);
1380 (shared, shared)
1381 } else {
1382 (independent_matrix_scale, independent_rhs_scale)
1383 };
1384 let a = build_big_int_matrix(matrix.components(), matrix_scale);
1385 let rhs = build_big_int_vec(rhs.components(), rhs_scale);
1386 let mut x = solve_big_int(a, rhs)?;
1387
1388 let solution_scale_exp = rhs_scale
1389 .get()
1390 .checked_sub(matrix_scale.get())
1391 .unwrap_or_else(|| unreachable!("finite f64 scale difference cannot overflow i32"));
1392 if solution_scale_exp != 0 {
1393 let solution_scale = big_int_exp_to_big_rational(BigInt::from(1_u8), solution_scale_exp);
1394 for component in &mut x {
1395 *component *= &solution_scale;
1396 }
1397 }
1398
1399 Ok(x)
1400}
1401
1402pub(crate) fn solve_big_int<const D: usize>(
1409 mut a: [[BigInt; D]; D],
1410 mut rhs: [BigInt; D],
1411) -> Result<[BigRational; D], LaError> {
1412 match bareiss_forward_eliminate(&mut a, Some(&mut rhs)) {
1413 BareissResult::Upper { .. } => {}
1414 BareissResult::Singular { pivot_col } => {
1415 cold_path();
1416 return Err(LaError::singular_exact(pivot_col));
1417 }
1418 }
1419
1420 let mut x: [BigRational; D] = from_fn(|_| BigRational::from_integer(BigInt::from(0)));
1421 for i in (0..D).rev() {
1422 let mut sum = BigRational::from_integer(take(&mut rhs[i]));
1423 for j in (i + 1)..D {
1424 let a_ij = BigRational::from_integer(take(&mut a[i][j]));
1425 sum -= &a_ij * &x[j];
1426 }
1427 let a_ii = BigRational::from_integer(take(&mut a[i][i]));
1428 x[i] = sum / &a_ii;
1429 }
1430
1431 Ok(x)
1432}
1433
1434#[inline]
1449fn det_exact_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
1450 let (det_int, total_exp) = exact_det_int_finite(m)?;
1451 big_int_exp_to_finite_f64(&det_int, total_exp, None)
1452}
1453
1454#[inline]
1466fn det_exact_rounded_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
1467 let (det_int, total_exp) = exact_det_int_finite(m)?;
1468 big_int_exp_to_rounded_f64(&det_int, total_exp)
1469}
1470
1471#[inline]
1478fn det_sign_exact_finite<const D: usize>(m: &Matrix<D>) -> DeterminantSign {
1479 if let Ok(Some(estimate)) = m.det_direct_with_errbound() {
1480 let det_f64 = estimate.determinant();
1481 let error_bound = estimate.absolute_error_bound();
1482 if det_f64 > error_bound {
1483 return DeterminantSign::Positive;
1484 }
1485 if det_f64 < -error_bound {
1486 return DeterminantSign::Negative;
1487 }
1488 }
1489
1490 cold_path();
1491 let decomposed = decompose_proven_finite_matrix(m);
1492 let (det_int, _) = scaled_det_int_decomposed(&decomposed);
1493 match det_int.sign() {
1494 Sign::Plus => DeterminantSign::Positive,
1495 Sign::Minus => DeterminantSign::Negative,
1496 Sign::NoSign => DeterminantSign::Zero,
1497 }
1498}
1499
1500impl<const D: usize> Matrix<D> {
1501 #[inline]
1535 pub fn det_exact(&self) -> Result<BigRational, LaError> {
1536 exact_det_finite(self)
1537 }
1538
1539 #[inline]
1573 pub fn det_exact_f64(&self) -> Result<f64, LaError> {
1574 det_exact_f64_finite(self)
1575 }
1576
1577 #[inline]
1617 pub fn det_exact_rounded_f64(&self) -> Result<f64, LaError> {
1618 det_exact_rounded_f64_finite(self)
1619 }
1620
1621 #[inline]
1671 pub fn solve_exact(&self, b: Vector<D>) -> Result<RationalVector<D>, LaError> {
1672 bareiss_solve_finite(self, &b).map(RationalVector::from_canonical_array)
1673 }
1674
1675 #[inline]
1708 pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1709 self.solve_exact(b)?.try_to_f64()
1710 }
1711
1712 #[inline]
1749 pub fn solve_exact_rounded_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1750 self.solve_exact(b)?.to_rounded_f64()
1751 }
1752
1753 #[inline]
1793 pub fn det_sign_exact(&self) -> DeterminantSign {
1794 det_sign_exact_finite(self)
1795 }
1796}
1797
1798#[cfg(test)]
1799mod tests {
1800 use core::assert_matches;
1801 use std::array::from_fn;
1802
1803 use num_traits::{FromPrimitive, Signed};
1804 use pastey::paste;
1805 use proptest::prelude::*;
1806
1807 use super::*;
1808 use crate::{
1809 ArithmeticOperation, DEFAULT_SINGULAR_TOL, NonFiniteLocation, NonFiniteOrigin,
1810 SingularityReason,
1811 };
1812
1813 #[test]
1816 fn det4_matches_bareiss_across_sparse_wide_and_singular_inputs() {
1817 let coefficients = [
1818 [11_i32, 2, -3, 4],
1819 [2, 13, 5, -1],
1820 [3, -2, 17, 6],
1821 [-1, 4, 2, 19],
1822 ];
1823 for shift in [0_u32, 80, 256, 1024] {
1824 for mask in 0..16_u8 {
1825 let mut rows: [[BigInt; 4]; 4] = from_fn(|i| {
1826 from_fn(|j| {
1827 if i == 0 && mask & (1 << j) == 0 {
1828 BigInt::from(0)
1829 } else {
1830 (BigInt::from(coefficients[i][j]) << shift) + BigInt::from(i + j)
1831 }
1832 })
1833 });
1834 for variant in 0..3 {
1835 if variant == 1 {
1836 rows.swap(0, 2);
1837 } else if variant == 2 {
1838 rows[1] = rows[0].clone();
1839 }
1840 let mut eliminated = rows.clone();
1841 let expected = match bareiss_forward_eliminate(&mut eliminated, None) {
1842 BareissResult::Upper { odd_swaps } => {
1843 let det = take(&mut eliminated[3][3]);
1844 if odd_swaps { -det } else { det }
1845 }
1846 BareissResult::Singular { .. } => BigInt::from(0),
1847 };
1848 assert_eq!(
1849 det4_big_int(&rows),
1850 expected,
1851 "shift={shift}, mask={mask}, variant={variant}"
1852 );
1853 }
1854 }
1855 }
1856 }
1857
1858 fn f64_to_big_rational(x: f64) -> BigRational {
1870 BigRational::from_f64(x).expect("test oracle requires finite f64 input")
1871 }
1872
1873 fn assert_unrepresentable<T>(
1874 result: &Result<T, LaError>,
1875 expected_index: Option<usize>,
1876 expected_reason: UnrepresentableReason,
1877 ) {
1878 let Err(error) = result else {
1879 panic!("expected an exact-to-f64 conversion error");
1880 };
1881 assert!(matches!(
1882 *error,
1883 LaError::Unrepresentable { index, reason, .. }
1884 if index == expected_index && reason == expected_reason
1885 ));
1886 }
1887
1888 macro_rules! gen_exact_identity_tests {
1893 ($d:literal) => {
1894 paste! {
1895 #[test]
1896 fn [<exact_identity_paths_ $d d>]() {
1897 let matrix = Matrix::<$d>::identity();
1898 let one = BigRational::from_integer(BigInt::from(1));
1899
1900 assert_eq!(matrix.det_exact().unwrap(), one);
1901 assert_eq!(matrix.det_exact_f64().unwrap().to_bits(), 1.0_f64.to_bits());
1902 assert_eq!(
1903 matrix.det_exact_rounded_f64().unwrap().to_bits(),
1904 1.0_f64.to_bits()
1905 );
1906 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Positive);
1907 }
1908 }
1909 };
1910 }
1911
1912 gen_exact_identity_tests!(2);
1913 gen_exact_identity_tests!(3);
1914 gen_exact_identity_tests!(4);
1915 gen_exact_identity_tests!(5);
1916
1917 macro_rules! gen_det_exact_f64_agrees_with_det_direct {
1920 ($d:literal) => {
1921 paste! {
1922 #[test]
1923 fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
1924 let mut rows = [[0.0f64; $d]; $d];
1927 let mut value = 2.0;
1928 for (i, row) in rows.iter_mut().enumerate() {
1929 row[i] = value;
1930 value *= 2.0;
1931 }
1932 let m = Matrix::<$d>::try_from_rows(rows).unwrap();
1933 let exact = m.det_exact_f64().unwrap();
1934 let direct = m.det_direct().unwrap().unwrap();
1935 assert_eq!(exact.to_bits(), direct.to_bits());
1936 }
1937 }
1938 };
1939 }
1940
1941 gen_det_exact_f64_agrees_with_det_direct!(2);
1942 gen_det_exact_f64_agrees_with_det_direct!(3);
1943 gen_det_exact_f64_agrees_with_det_direct!(4);
1944
1945 #[test]
1946 fn det_sign_exact_d0_is_positive() {
1947 assert_eq!(
1948 Matrix::<0>::zero().det_sign_exact(),
1949 DeterminantSign::Positive
1950 );
1951 }
1952
1953 #[test]
1954 fn det_sign_exact_d1_positive() {
1955 let m = Matrix::<1>::try_from_rows([[42.0]]).unwrap();
1956 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
1957 }
1958
1959 #[test]
1960 fn det_sign_exact_d1_negative() {
1961 let m = Matrix::<1>::try_from_rows([[-3.5]]).unwrap();
1962 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1963 }
1964
1965 #[test]
1966 fn det_sign_exact_d1_zero() {
1967 let m = Matrix::<1>::try_from_rows([[0.0]]).unwrap();
1968 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1969 }
1970
1971 #[test]
1972 fn det_sign_exact_singular_duplicate_rows() {
1973 let m = Matrix::<3>::try_from_rows([
1974 [1.0, 2.0, 3.0],
1975 [4.0, 5.0, 6.0],
1976 [1.0, 2.0, 3.0], ])
1978 .unwrap();
1979 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1980 }
1981
1982 #[test]
1983 fn det_sign_exact_singular_linear_combination() {
1984 let m = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]])
1986 .unwrap();
1987 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1988 }
1989
1990 #[test]
1991 fn det_sign_exact_negative_det_row_swap() {
1992 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
1994 .unwrap();
1995 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1996 }
1997
1998 #[test]
1999 fn det_sign_exact_negative_det_known() {
2000 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2002 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2003 }
2004
2005 #[test]
2006 fn det_sign_exact_agrees_with_det_for_spd() {
2007 let m = Matrix::<3>::try_from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]])
2009 .unwrap();
2010 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2011 assert!(m.det().unwrap() > 0.0);
2012 }
2013
2014 #[test]
2021 fn det_sign_exact_near_singular_perturbation() {
2022 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
2024 [1.0 + perturbation, 2.0, 3.0],
2025 [4.0, 5.0, 6.0],
2026 [7.0, 8.0, 9.0],
2027 ])
2028 .unwrap();
2029 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2031 }
2032
2033 #[test]
2037 fn det_sign_exact_fast_filter_positive_4x4() {
2038 let m = Matrix::<4>::try_from_rows([
2039 [2.0, 1.0, 0.0, 0.0],
2040 [1.0, 3.0, 1.0, 0.0],
2041 [0.0, 1.0, 4.0, 1.0],
2042 [0.0, 0.0, 1.0, 5.0],
2043 ])
2044 .unwrap();
2045 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2047 }
2048
2049 #[test]
2050 fn det_sign_exact_fast_filter_negative_4x4() {
2051 let m = Matrix::<4>::try_from_rows([
2053 [1.0, 3.0, 1.0, 0.0],
2054 [2.0, 1.0, 0.0, 0.0],
2055 [0.0, 1.0, 4.0, 1.0],
2056 [0.0, 0.0, 1.0, 5.0],
2057 ])
2058 .unwrap();
2059 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2060 }
2061
2062 #[test]
2063 fn det_sign_exact_subnormal_entries() {
2064 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
2067
2068 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
2069 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2071 }
2072
2073 #[test]
2074 fn det_sign_exact_falls_back_when_subnormal_rounding_reverses_direct_sign() {
2075 let scale = 2.0_f64.powi(-360);
2076 let matrix = Matrix::<3>::try_from_rows([
2077 [-5.0 * scale, 3.0 * scale, 6.0 * scale],
2078 [0.0, -7.0 * scale, -7.0 * scale],
2079 [2.0 * scale, -3.0 * scale, -4.0 * scale],
2080 ])
2081 .unwrap();
2082
2083 assert_eq!(
2084 matrix.det_direct().unwrap().unwrap().to_bits(),
2085 (-f64::from_bits(1)).to_bits()
2086 );
2087 assert_eq!(matrix.det_errbound(), Ok(None));
2088 assert!(matrix.det_exact().unwrap().is_positive());
2089 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Positive);
2090 }
2091
2092 #[test]
2093 fn det_sign_exact_falls_back_for_bit_exact_underflow_counterexample() {
2094 let matrix = Matrix::<3>::try_from_rows([
2095 [
2096 f64::from_bits(9_218_868_437_227_405_311),
2097 f64::from_bits(13_830_554_455_654_793_216),
2098 0.0,
2099 ],
2100 [
2101 f64::from_bits(6_790_500_848_393_242_208),
2102 f64::from_bits(2_184_621_143_747_520_227),
2103 f64::from_bits(2_187_555_472_467_513_745),
2104 ],
2105 [
2106 0.0,
2107 f64::from_bits(2_184_859_204_554_904_434),
2108 f64::from_bits(2_184_762_736_385_916_910),
2109 ],
2110 ])
2111 .unwrap();
2112
2113 assert!(matrix.det_direct().unwrap().unwrap().is_sign_positive());
2114 assert_eq!(matrix.det_errbound(), Ok(None));
2115 assert!(matrix.det_exact().unwrap().is_negative());
2116 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Negative);
2117 }
2118
2119 #[test]
2120 fn det_sign_exact_pivot_needed_5x5() {
2121 let m = Matrix::<5>::try_from_rows([
2124 [0.0, 1.0, 0.0, 0.0, 0.0],
2125 [1.0, 0.0, 0.0, 0.0, 0.0],
2126 [0.0, 0.0, 1.0, 0.0, 0.0],
2127 [0.0, 0.0, 0.0, 1.0, 0.0],
2128 [0.0, 0.0, 0.0, 0.0, 1.0],
2129 ])
2130 .unwrap();
2131 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2132 }
2133
2134 #[test]
2135 fn det_sign_exact_5x5_known() {
2136 let m = Matrix::<5>::try_from_rows([
2138 [0.0, 1.0, 0.0, 0.0, 0.0],
2139 [1.0, 0.0, 0.0, 0.0, 0.0],
2140 [0.0, 0.0, 0.0, 1.0, 0.0],
2141 [0.0, 0.0, 1.0, 0.0, 0.0],
2142 [0.0, 0.0, 0.0, 0.0, 1.0],
2143 ])
2144 .unwrap();
2145 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2147 }
2148
2149 #[test]
2155 fn decompose_f64_zero() {
2156 assert_eq!(decompose_proven_finite_f64(0.0), Component::Zero);
2157 assert_eq!(decompose_proven_finite_f64(-0.0), Component::Zero);
2158 }
2159
2160 #[test]
2161 fn decompose_f64_one() {
2162 assert_eq!(
2163 decompose_proven_finite_f64(1.0),
2164 Component::NonZero {
2165 mantissa: NonZeroU64::new(1).unwrap(),
2166 exponent: 0,
2167 is_negative: false,
2168 }
2169 );
2170 }
2171
2172 #[test]
2173 fn decompose_f64_negative() {
2174 assert_eq!(
2175 decompose_proven_finite_f64(-3.5),
2176 Component::NonZero {
2177 mantissa: NonZeroU64::new(7).unwrap(),
2178 exponent: -1,
2179 is_negative: true,
2180 }
2181 );
2182 }
2183
2184 #[test]
2185 fn decompose_f64_subnormal() {
2186 let tiny = f64::from_bits(1);
2187 assert!(tiny.is_subnormal());
2188 assert_eq!(
2189 decompose_proven_finite_f64(tiny),
2190 Component::NonZero {
2191 mantissa: NonZeroU64::new(1).unwrap(),
2192 exponent: -1074,
2193 is_negative: false,
2194 }
2195 );
2196 }
2197
2198 #[test]
2199 fn decompose_f64_normalizes_mixed_subnormal_mantissa() {
2200 let value = f64::from_bits(0x000C_0000_0000_0000);
2201 assert!(value.is_subnormal());
2202 assert_eq!(
2203 decompose_proven_finite_f64(value),
2204 Component::NonZero {
2205 mantissa: NonZeroU64::new(3).unwrap(),
2206 exponent: -1024,
2207 is_negative: false,
2208 }
2209 );
2210 }
2211
2212 #[test]
2213 fn decompose_f64_power_of_two() {
2214 assert_eq!(
2215 decompose_proven_finite_f64(1024.0),
2216 Component::NonZero {
2217 mantissa: NonZeroU64::new(1).unwrap(),
2218 exponent: 10,
2219 is_negative: false,
2220 }
2221 );
2222 }
2223
2224 proptest! {
2225 #[test]
2226 fn finite_f64_round_trips_through_exact_decomposition(bits in any::<u64>()) {
2227 let value = f64::from_bits(bits);
2228 prop_assume!(value.is_finite());
2229
2230 let exact = f64_to_big_rational(value);
2231 let decomposed = match decompose_proven_finite_f64(value) {
2232 Component::Zero => BigRational::from_integer(BigInt::from(0)),
2233 Component::NonZero { mantissa, exponent, is_negative } => {
2234 prop_assert_eq!(mantissa.get() & 1, 1);
2235 prop_assert!((-1074..=1023).contains(&exponent));
2236 let numerator = if is_negative {
2237 -BigInt::from(mantissa.get())
2238 } else {
2239 BigInt::from(mantissa.get())
2240 };
2241 if exponent >= 0 {
2242 BigRational::from_integer(numerator << exponent.cast_unsigned())
2243 } else {
2244 BigRational::new(numerator, BigInt::from(1) << exponent.unsigned_abs())
2245 }
2246 }
2247 };
2248 prop_assert_eq!(&decomposed, &exact);
2249 let reconstructed = exact_rational_to_finite_f64(&exact, None);
2250
2251 prop_assert_eq!(reconstructed, Ok(value));
2252 }
2253 }
2254
2255 #[test]
2256 fn wide_low_exponent_value_reports_non_finite_rounded_result() {
2257 let value = (BigInt::from(1_u8) << 2099_u32) - BigInt::from(1_u8);
2259 let result = big_int_exp_to_finite_f64(&value, -1075, None);
2260
2261 assert!(!result.as_ref().unwrap_err().requires_rounding());
2262 assert_unrepresentable(&result, None, UnrepresentableReason::NotFinite);
2263 }
2264
2265 #[test]
2266 fn direct_big_int_rounding_handles_extreme_negative_exponent_without_large_denominator() {
2267 let positive = big_int_exp_ref_to_rounded_f64(&BigInt::from(1_u8), i32::MIN, None).unwrap();
2268 let negative =
2269 big_int_exp_ref_to_rounded_f64(&BigInt::from(-1_i8), i32::MIN, None).unwrap();
2270
2271 assert_eq!(positive.to_bits(), 0.0_f64.to_bits());
2272 assert_eq!(negative.to_bits(), (-0.0_f64).to_bits());
2273 }
2274
2275 proptest! {
2276 #[test]
2277 fn direct_big_int_rounding_matches_rational_oracle(
2278 value in any::<i128>(),
2279 exp in -1200_i32..=1200_i32,
2280 ) {
2281 let value = BigInt::from(value);
2282 let direct = big_int_exp_ref_to_rounded_f64(&value, exp, None);
2283 let exact = big_int_exp_to_big_rational(value, exp);
2284 let oracle = exact_rational_to_rounded_f64(&exact, None);
2285
2286 prop_assert_eq!(direct.map(f64::to_bits), oracle.map(f64::to_bits));
2287 }
2288 }
2289
2290 #[test]
2291 fn component_to_big_int_distinguishes_zero_from_nonzero_mantissa() {
2292 let baseline = Component::NonZero {
2293 mantissa: NonZeroU64::new(1).unwrap(),
2294 exponent: 1,
2295 is_negative: false,
2296 };
2297 let positive = Component::NonZero {
2298 mantissa: NonZeroU64::new(3).unwrap(),
2299 exponent: 4,
2300 is_negative: false,
2301 };
2302 let negative = Component::NonZero {
2303 mantissa: NonZeroU64::new(5).unwrap(),
2304 exponent: 3,
2305 is_negative: true,
2306 };
2307
2308 let decomposed =
2309 Decomposed::from_vector_components([Component::Zero, baseline, positive, negative]);
2310 let scale = ScaleExponent::for_decomposed(&decomposed);
2311
2312 assert_eq!(
2313 component_to_big_int(Component::Zero, scale),
2314 BigInt::from(0)
2315 );
2316 assert_eq!(component_to_big_int(positive, scale), BigInt::from(24));
2317 assert_eq!(component_to_big_int(negative, scale), BigInt::from(-20));
2318 }
2319
2320 #[test]
2321 fn decomposed_all_zero_uses_no_sentinel_exponent() {
2322 let decomposed = decompose_proven_finite_matrix(&Matrix::<2>::zero());
2323 assert_eq!(decomposed.min_exponent(), None);
2324
2325 let scale = ScaleExponent::for_decomposed(&decomposed);
2326 assert_eq!(scale, ScaleExponent::ZERO);
2327 assert_eq!(scale.get(), 0);
2328 assert_eq!(
2329 build_big_int_matrix(decomposed.components(), scale),
2330 [
2331 [BigInt::from(0), BigInt::from(0)],
2332 [BigInt::from(0), BigInt::from(0)]
2333 ]
2334 );
2335 }
2336
2337 #[test]
2338 fn matrix_and_rhs_scales_are_derived_independently() {
2339 let tiny = f64::from_bits(1);
2340 let matrix = Matrix::<2>::try_from_rows([[f64::MAX, 0.0], [0.0, 1.0]]).unwrap();
2341 let rhs = Vector::<2>::try_new([tiny, 0.0]).unwrap();
2342 let matrix = decompose_proven_finite_matrix(&matrix);
2343 let rhs = decompose_proven_finite_vector(&rhs);
2344
2345 assert_eq!(matrix.min_exponent(), Some(0));
2346 assert_eq!(rhs.min_exponent(), Some(-1074));
2347
2348 let matrix_scale = ScaleExponent::for_decomposed(&matrix);
2349 let rhs_scale = ScaleExponent::for_decomposed(&rhs);
2350 assert_eq!(matrix_scale.get(), 0);
2351 assert_eq!(matrix_scale.shift_for(0), 0);
2352 assert_eq!(rhs_scale.get(), -1074);
2353 assert_eq!(rhs_scale.shift_for(-1074), 0);
2354 }
2355
2356 proptest! {
2357 #[test]
2358 fn derived_scale_yields_nonnegative_shifts(bits in any::<[u64; 4]>()) {
2359 let values = bits.map(f64::from_bits);
2360 prop_assume!(values.iter().all(|value| value.is_finite()));
2361 let matrix = Matrix::<2>::try_from_rows([
2362 [values[0], values[1]],
2363 [values[2], values[3]],
2364 ]).unwrap();
2365 let decomposed = decompose_proven_finite_matrix(&matrix);
2366 let scale = ScaleExponent::for_decomposed(&decomposed);
2367
2368 for component in decomposed.components().iter().flatten() {
2369 if let Some(exponent) = component.exponent() {
2370 prop_assert!(exponent >= scale.get());
2371 prop_assert_eq!(
2372 scale.shift_for(exponent),
2373 u32::try_from(exponent - scale.get()).unwrap(),
2374 );
2375 }
2376 }
2377 }
2378 }
2379
2380 #[test]
2381 fn determinant_scale_exp_multiplies_dimension_and_min_exponent() {
2382 assert_eq!(determinant_scale_exp::<4>(-1074), Ok(-4296));
2383 }
2384
2385 #[test]
2386 fn determinant_scale_exp_rejects_dimension_too_large_for_i32() {
2387 assert_eq!(
2388 determinant_scale_exp::<{ i32::MAX as usize + 1 }>(-1074),
2389 Err(LaError::DeterminantScaleOverflow {
2390 dim: i32::MAX as usize + 1,
2391 min_exponent: -1074,
2392 })
2393 );
2394 }
2395
2396 #[test]
2397 fn determinant_scale_exp_rejects_exponent_product_overflow() {
2398 assert_eq!(
2399 determinant_scale_exp::<3_000_000>(-1074),
2400 Err(LaError::DeterminantScaleOverflow {
2401 dim: 3_000_000,
2402 min_exponent: -1074,
2403 })
2404 );
2405 }
2406
2407 #[test]
2408 fn negative_exponent_from_magnitude_covers_i32_domain_boundaries() {
2409 assert_eq!(negative_exponent_from_magnitude(0), 0);
2410 assert_eq!(negative_exponent_from_magnitude(1), -1);
2411 assert_eq!(
2412 negative_exponent_from_magnitude(i32::MAX.cast_unsigned().into()),
2413 -i32::MAX
2414 );
2415 assert_eq!(
2416 negative_exponent_from_magnitude(i32::MIN.unsigned_abs().into()),
2417 i32::MIN
2418 );
2419 }
2420
2421 #[test]
2422 #[should_panic(expected = "negative exponent magnitude exceeds the i32 domain")]
2423 fn negative_exponent_from_magnitude_rejects_values_above_i32_domain() {
2424 let _ = negative_exponent_from_magnitude(u64::from(i32::MIN.unsigned_abs()) + 1);
2425 }
2426
2427 #[test]
2432 fn exact_det_int_d0() {
2433 let m = Matrix::<0>::zero();
2434 let (det, exp) = exact_det_int_finite(&m).unwrap();
2435 assert_eq!(det, BigInt::from(1));
2436 assert_eq!(exp, 0);
2437 }
2438
2439 #[test]
2445 fn exact_det_int_d1_cases() {
2446 let cases: &[(f64, i64, i32)] = &[
2447 (7.0, 7, 0), (0.0, 0, 0), (-3.5, -7, -1), (0.5, 1, -1), ];
2453 for &(input, expected_det_int, expected_exp) in cases {
2454 let m = Matrix::<1>::try_from_rows([[input]]).unwrap();
2455 let (det, exp) = exact_det_int_finite(&m).unwrap();
2456 assert_eq!(
2457 det,
2458 BigInt::from(expected_det_int),
2459 "det_int for input={input}"
2460 );
2461 assert_eq!(exp, expected_exp, "exp for input={input}");
2462 }
2463 }
2464
2465 #[test]
2466 fn exact_det_int_d2_known() {
2467 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2469 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2470 let det = big_int_exp_to_big_rational(det_int, total_exp);
2472 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2473 }
2474
2475 #[test]
2476 fn exact_det_int_all_zeros() {
2477 let m = Matrix::<3>::zero();
2478 let (det, _) = exact_det_int_finite(&m).unwrap();
2479 assert_eq!(det, BigInt::from(0));
2480 }
2481
2482 #[test]
2483 fn exact_det_int_fractional_entries() {
2484 let m = Matrix::<2>::try_from_rows([[0.5, 0.25], [1.0, 1.0]]).unwrap();
2487 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2488 let det = big_int_exp_to_big_rational(det_int, total_exp);
2489 assert_eq!(det, BigRational::new(BigInt::from(1), BigInt::from(4)));
2490 }
2491
2492 #[test]
2493 fn exact_det_int_d3_direct_expansion_handles_zero_diagonal() {
2494 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
2496 .unwrap();
2497 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2498 let det = big_int_exp_to_big_rational(det_int, total_exp);
2499 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2500 }
2501
2502 #[test]
2507 fn big_int_exp_to_big_rational_zero() {
2508 let r = big_int_exp_to_big_rational(BigInt::from(0), -50);
2509 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
2510 }
2511
2512 #[test]
2513 fn big_int_exp_to_big_rational_positive_exp() {
2514 let r = big_int_exp_to_big_rational(BigInt::from(3), 2);
2516 assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
2517 }
2518
2519 #[test]
2520 fn big_int_exp_to_big_rational_negative_exp_reduced() {
2521 let r = big_int_exp_to_big_rational(BigInt::from(6), -2);
2523 assert_eq!(*r.numer(), BigInt::from(3));
2524 assert_eq!(*r.denom(), BigInt::from(2));
2525 }
2526
2527 #[test]
2528 fn big_int_exp_to_big_rational_negative_exp_reduces_to_integer() {
2529 let r = big_int_exp_to_big_rational(BigInt::from(8), -3);
2531 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
2532 }
2533
2534 #[test]
2535 fn big_int_exp_to_big_rational_negative_exp_already_odd() {
2536 let r = big_int_exp_to_big_rational(BigInt::from(3), -2);
2538 assert_eq!(*r.numer(), BigInt::from(3));
2539 assert_eq!(*r.denom(), BigInt::from(4));
2540 }
2541
2542 #[test]
2543 fn big_int_exp_to_big_rational_negative_value() {
2544 let r = big_int_exp_to_big_rational(BigInt::from(-5), 1);
2546 assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
2547 }
2548
2549 #[test]
2550 fn big_int_exp_to_big_rational_negative_value_with_denominator() {
2551 let r = big_int_exp_to_big_rational(BigInt::from(-3), -2);
2553 assert_eq!(*r.numer(), BigInt::from(-3));
2554 assert_eq!(*r.denom(), BigInt::from(4));
2555 }
2556
2557 #[test]
2562 fn det_exact_d1_returns_entry() {
2563 let det = Matrix::<1>::try_from_rows([[7.0]])
2564 .unwrap()
2565 .det_exact()
2566 .unwrap();
2567 assert_eq!(det, f64_to_big_rational(7.0));
2568 }
2569
2570 #[test]
2571 fn det_exact_d3_direct_expansion_handles_zero_diagonal() {
2572 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
2574 .unwrap();
2575 let det = m.det_exact().unwrap();
2576 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2578 }
2579
2580 #[test]
2581 fn det_exact_d3_singular_zero_column_returns_zero() {
2582 let m = Matrix::<3>::try_from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
2584 .unwrap();
2585 let det = m.det_exact().unwrap();
2586 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2587 }
2588
2589 #[test]
2590 fn det_sign_exact_overflow_determinant_finite_entries() {
2591 let big = f64::MAX / 2.0;
2595 assert!(big.is_finite());
2596 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2597 .unwrap();
2598 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2600 }
2601
2602 #[test]
2607 fn det_exact_d0_is_one() {
2608 let det = Matrix::<0>::zero().det_exact().unwrap();
2609 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
2610 }
2611
2612 #[test]
2613 fn det_exact_known_2x2() {
2614 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2616 let det = m.det_exact().unwrap();
2617 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2618 }
2619
2620 #[test]
2621 fn det_exact_known_dense_4x4() {
2622 let m = Matrix::<4>::try_from_rows([
2623 [4.0, 1.0, 3.0, 2.0],
2624 [0.0, 5.0, 2.0, 1.0],
2625 [7.0, 2.0, 6.0, 3.0],
2626 [1.0, 8.0, 4.0, 9.0],
2627 ])
2628 .unwrap();
2629
2630 assert_eq!(
2631 m.det_exact(),
2632 Ok(BigRational::from_integer(BigInt::from(92)))
2633 );
2634 }
2635
2636 #[test]
2637 fn det_exact_singular_returns_zero() {
2638 let m = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
2640 .unwrap();
2641 let det = m.det_exact().unwrap();
2642 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2643 }
2644
2645 #[test]
2646 fn det_exact_near_singular_perturbation() {
2647 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
2650 [1.0 + perturbation, 2.0, 3.0],
2651 [4.0, 5.0, 6.0],
2652 [7.0, 8.0, 9.0],
2653 ])
2654 .unwrap();
2655 let det = m.det_exact().unwrap();
2656 let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
2658 assert_eq!(det, expected);
2659 }
2660
2661 #[test]
2662 fn det_exact_5x5_permutation() {
2663 let m = Matrix::<5>::try_from_rows([
2665 [0.0, 1.0, 0.0, 0.0, 0.0],
2666 [1.0, 0.0, 0.0, 0.0, 0.0],
2667 [0.0, 0.0, 1.0, 0.0, 0.0],
2668 [0.0, 0.0, 0.0, 1.0, 0.0],
2669 [0.0, 0.0, 0.0, 0.0, 1.0],
2670 ])
2671 .unwrap();
2672 let det = m.det_exact().unwrap();
2673 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2674 }
2675
2676 #[test]
2681 fn det_exact_f64_known_2x2() {
2682 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2683 let det = m.det_exact_f64().unwrap();
2684 assert!((det - (-2.0)).abs() <= f64::EPSILON);
2685 }
2686
2687 #[test]
2688 fn det_exact_f64_overflow_returns_err() {
2689 let big = f64::MAX / 2.0;
2691 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2692 .unwrap();
2693 assert_unrepresentable(&m.det_exact_f64(), None, UnrepresentableReason::NotFinite);
2695 }
2696
2697 #[test]
2698 fn det_exact_rounded_f64_overflow_returns_err() {
2699 let big = f64::MAX / 2.0;
2700 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2701 .unwrap();
2702
2703 assert_unrepresentable(
2704 &m.det_exact_rounded_f64(),
2705 None,
2706 UnrepresentableReason::NotFinite,
2707 );
2708 }
2709
2710 #[test]
2711 fn det_exact_f64_underflow_returns_err_for_nonzero_exact_result() {
2712 let tiny = f64::from_bits(1);
2713 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
2714
2715 assert!(m.det_exact().unwrap().is_positive());
2716 assert_unrepresentable(
2717 &m.det_exact_f64(),
2718 None,
2719 UnrepresentableReason::RequiresRounding,
2720 );
2721 }
2722
2723 #[test]
2724 fn det_exact_f64_rejects_inexact_rounding() {
2725 let m = Matrix::<2>::try_from_rows([[1.0 + f64::EPSILON, 0.0], [0.0, 1.0 - f64::EPSILON]])
2726 .unwrap();
2727
2728 assert_eq!(
2729 m.det_exact(),
2730 Ok(BigRational::new(
2731 (BigInt::from(1_u128) << 104_u32) - BigInt::from(1),
2732 BigInt::from(1_u128 << 104),
2733 ))
2734 );
2735 assert_unrepresentable(
2736 &m.det_exact_f64(),
2737 None,
2738 UnrepresentableReason::RequiresRounding,
2739 );
2740 }
2741
2742 #[test]
2743 fn det_exact_f64_accepts_max_finite_binary64() {
2744 let m = Matrix::<1>::try_from_rows([[f64::MAX]]).unwrap();
2745
2746 assert_eq!(m.det_exact_f64().unwrap().to_bits(), f64::MAX.to_bits());
2747 }
2748
2749 fn arbitrary_rhs<const D: usize>() -> Vector<D> {
2755 let values = [1.0, -2.5, 3.0, 0.25, -4.0];
2756 let mut arr = [0.0f64; D];
2757 for (dst, src) in arr.iter_mut().zip(values.iter()) {
2758 *dst = *src;
2759 }
2760 Vector::<D>::new(arr)
2761 }
2762
2763 macro_rules! gen_solve_exact_tests {
2764 ($d:literal) => {
2765 paste! {
2766 #[test]
2767 fn [<solve_exact_identity_paths_ $d d>]() {
2768 let a = Matrix::<$d>::identity();
2769 let b = arbitrary_rhs::<$d>();
2770 let exact = a.solve_exact(b).unwrap();
2771 let strict_f64 = a.solve_exact_f64(b).unwrap().into_array();
2772
2773 for i in 0..$d {
2774 assert_eq!(exact.as_array()[i], f64_to_big_rational(b.as_array()[i]));
2775 assert_eq!(strict_f64[i].to_bits(), b.as_array()[i].to_bits());
2776 }
2777 }
2778
2779 #[test]
2780 fn [<solve_exact_singular_ $d d>]() {
2781 let a = Matrix::<$d>::zero();
2783 let b = arbitrary_rhs::<$d>();
2784 assert_matches!(
2785 a.solve_exact(b),
2786 Err(LaError::Singular {
2787 pivot_col: 0,
2788 reason: SingularityReason::Exact,
2789 ..
2790 })
2791 );
2792 }
2793 }
2794 };
2795 }
2796
2797 gen_solve_exact_tests!(2);
2798 gen_solve_exact_tests!(3);
2799 gen_solve_exact_tests!(4);
2800 gen_solve_exact_tests!(5);
2801
2802 macro_rules! gen_solve_exact_f64_agrees_with_lu {
2805 ($d:literal) => {
2806 paste! {
2807 #[test]
2808 fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
2809 let mut rows = [[0.0f64; $d]; $d];
2813 for r in 0..$d {
2814 for c in 0..$d {
2815 rows[r][c] = if r == c {
2816 f64::from($d) + 1.0
2817 } else {
2818 1.0
2819 };
2820 }
2821 }
2822 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2823 let x_true = {
2824 let mut arr = [0.0f64; $d];
2825 for (dst, src) in arr.iter_mut().zip([1.0, -2.0, 3.0, -4.0, 5.0]) {
2826 *dst = src;
2827 }
2828 arr
2829 };
2830 let mut b_arr = [0.0f64; $d];
2831 for i in 0..$d {
2832 let mut sum = 0.0;
2833 for j in 0..$d {
2834 sum = rows[i][j].mul_add(x_true[j], sum);
2835 }
2836 b_arr[i] = sum;
2837 }
2838 let b = Vector::<$d>::new(b_arr);
2839 let exact = a.solve_exact_f64(b).unwrap().into_array();
2840 let lu_sol = a.lu(DEFAULT_SINGULAR_TOL).unwrap()
2841 .solve(b).unwrap().into_array();
2842 for i in 0..$d {
2843 assert_eq!(exact[i].to_bits(), x_true[i].to_bits());
2844 let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
2845 assert!((exact[i] - lu_sol[i]).abs() <= eps);
2846 }
2847 }
2848 }
2849 };
2850 }
2851
2852 gen_solve_exact_f64_agrees_with_lu!(2);
2853 gen_solve_exact_f64_agrees_with_lu!(3);
2854 gen_solve_exact_f64_agrees_with_lu!(4);
2855 gen_solve_exact_f64_agrees_with_lu!(5);
2856
2857 macro_rules! gen_solve_exact_roundtrip_tests {
2863 ($d:literal) => {
2864 paste! {
2865 #[test]
2866 #[expect(
2867 clippy::cast_precision_loss,
2868 reason = "dimensions and indices are at most five and exactly representable as f64"
2869 )]
2870 fn [<solve_exact_roundtrip_ $d d>]() {
2871 let mut rows = [[0.0f64; $d]; $d];
2874 for r in 0..$d {
2875 for c in 0..$d {
2876 rows[r][c] = if r == c {
2877 f64::from($d) + 1.0
2878 } else {
2879 1.0
2880 };
2881 }
2882 }
2883 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2884
2885 let mut x0 = [0.0f64; $d];
2887 for i in 0..$d {
2888 x0[i] = (i + 1) as f64;
2889 }
2890
2891 let mut b_arr = [0.0f64; $d];
2894 for r in 0..$d {
2895 let mut sum = 0.0_f64;
2896 for c in 0..$d {
2897 sum = rows[r][c].mul_add(x0[c], sum);
2898 }
2899 b_arr[r] = sum;
2900 }
2901 let b = Vector::<$d>::new(b_arr);
2902
2903 let x = a.solve_exact(b).unwrap();
2904 for i in 0..$d {
2905 assert_eq!(x.as_array()[i], f64_to_big_rational(x0[i]));
2906 }
2907 }
2908 }
2909 };
2910 }
2911
2912 gen_solve_exact_roundtrip_tests!(2);
2913 gen_solve_exact_roundtrip_tests!(3);
2914 gen_solve_exact_roundtrip_tests!(4);
2915 gen_solve_exact_roundtrip_tests!(5);
2916
2917 #[test]
2922 fn solve_exact_d0_returns_empty() {
2923 let a = Matrix::<0>::zero();
2924 let b = Vector::<0>::zero();
2925 let x = a.solve_exact(b).unwrap();
2926 assert!(x.as_array().is_empty());
2927 }
2928
2929 #[test]
2930 fn solve_exact_known_2x2() {
2931 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2933 let b = Vector::<2>::new([5.0, 11.0]);
2934 let x = a.solve_exact(b).unwrap();
2935 assert_eq!(x.as_array()[0], BigRational::from_integer(BigInt::from(1)));
2936 assert_eq!(x.as_array()[1], BigRational::from_integer(BigInt::from(2)));
2937 }
2938
2939 #[test]
2940 fn solve_exact_pivoting_needed() {
2941 let a = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
2943 .unwrap();
2944 let b = Vector::<3>::new([2.0, 3.0, 4.0]);
2945 let x = a.solve_exact(b).unwrap();
2946 assert_eq!(x.as_array()[0], f64_to_big_rational(3.0));
2948 assert_eq!(x.as_array()[1], f64_to_big_rational(2.0));
2949 assert_eq!(x.as_array()[2], f64_to_big_rational(4.0));
2950 }
2951
2952 #[test]
2953 fn solve_exact_fractional_result() {
2954 let a = Matrix::<2>::try_from_rows([[2.0, 1.0], [1.0, 3.0]]).unwrap();
2956 let b = Vector::<2>::new([1.0, 1.0]);
2957 let x = a.solve_exact(b).unwrap();
2958 assert_eq!(
2959 x.as_array()[0],
2960 BigRational::new(BigInt::from(2), BigInt::from(5))
2961 );
2962 assert_eq!(
2963 x.as_array()[1],
2964 BigRational::new(BigInt::from(1), BigInt::from(5))
2965 );
2966 }
2967
2968 #[test]
2969 fn solve_exact_singular_duplicate_rows() {
2970 let a = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [1.0, 2.0, 3.0]])
2971 .unwrap();
2972 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
2973 assert_matches!(
2974 a.solve_exact(b),
2975 Err(LaError::Singular {
2976 reason: SingularityReason::Exact,
2977 ..
2978 })
2979 );
2980 }
2981
2982 #[test]
2983 fn solve_exact_5x5_permutation() {
2984 let a = Matrix::<5>::try_from_rows([
2986 [0.0, 1.0, 0.0, 0.0, 0.0],
2987 [1.0, 0.0, 0.0, 0.0, 0.0],
2988 [0.0, 0.0, 1.0, 0.0, 0.0],
2989 [0.0, 0.0, 0.0, 1.0, 0.0],
2990 [0.0, 0.0, 0.0, 0.0, 1.0],
2991 ])
2992 .unwrap();
2993 let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
2994 let x = a.solve_exact(b).unwrap();
2995 assert_eq!(x.as_array()[0], f64_to_big_rational(20.0));
2996 assert_eq!(x.as_array()[1], f64_to_big_rational(10.0));
2997 assert_eq!(x.as_array()[2], f64_to_big_rational(30.0));
2998 assert_eq!(x.as_array()[3], f64_to_big_rational(40.0));
2999 assert_eq!(x.as_array()[4], f64_to_big_rational(50.0));
3000 }
3001
3002 macro_rules! gen_solve_exact_large_finite_entries_tests {
3008 ($d:literal) => {
3009 paste! {
3010 #[test]
3011 fn [<solve_exact_large_finite_entries_ $d d>]() {
3012 let big = f64::MAX / 2.0;
3013 assert!(big.is_finite());
3014 let mut rows = [[0.0f64; $d]; $d];
3016 for i in 0..$d {
3017 rows[i][i] = big;
3018 }
3019 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3020 let mut b_arr = [big; $d];
3022 b_arr[$d - 1] = 0.0;
3023 let b = Vector::<$d>::new(b_arr);
3024 let x = a.solve_exact(b).unwrap();
3025 for i in 0..($d - 1) {
3026 assert_eq!(x.as_array()[i], BigRational::from_integer(BigInt::from(1)));
3027 }
3028 assert_eq!(x.as_array()[$d - 1], BigRational::from_integer(BigInt::from(0)));
3029 }
3030 }
3031 };
3032 }
3033
3034 gen_solve_exact_large_finite_entries_tests!(2);
3035 gen_solve_exact_large_finite_entries_tests!(3);
3036 gen_solve_exact_large_finite_entries_tests!(4);
3037 gen_solve_exact_large_finite_entries_tests!(5);
3038
3039 macro_rules! gen_solve_exact_mixed_magnitude_entries_tests {
3046 ($d:literal) => {
3047 paste! {
3048 #[test]
3049 fn [<solve_exact_mixed_magnitude_entries_ $d d>]() {
3050 let tiny = f64::MIN_POSITIVE; let huge = 1.0e100_f64;
3052 let mut rows = [[0.0f64; $d]; $d];
3054 let mut b_arr = [0.0f64; $d];
3055 for i in 0..$d {
3056 let val = if i % 2 == 0 { huge } else { tiny };
3057 rows[i][i] = val;
3058 b_arr[i] = val;
3059 }
3060 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3061 let b = Vector::<$d>::new(b_arr);
3062 let x = a.solve_exact(b).unwrap();
3063 for i in 0..$d {
3064 assert_eq!(x.as_array()[i], BigRational::from_integer(BigInt::from(1)));
3065 }
3066 }
3067 }
3068 };
3069 }
3070
3071 gen_solve_exact_mixed_magnitude_entries_tests!(2);
3072 gen_solve_exact_mixed_magnitude_entries_tests!(3);
3073 gen_solve_exact_mixed_magnitude_entries_tests!(4);
3074 gen_solve_exact_mixed_magnitude_entries_tests!(5);
3075
3076 #[test]
3077 fn solve_exact_restores_independent_matrix_and_rhs_scales() {
3078 let large = 2.0_f64.powi(500);
3079 let tiny = 2.0_f64.powi(-1000);
3080
3081 let large_matrix = Matrix::<2>::try_from_rows([[large, 0.0], [0.0, large]]).unwrap();
3082 let tiny_rhs = Vector::<2>::new([tiny, -2.0 * tiny]);
3083 let small_solution = large_matrix.solve_exact(tiny_rhs).unwrap();
3084 assert_eq!(
3085 small_solution.as_array()[0],
3086 BigRational::new(BigInt::from(1_u8), BigInt::from(1_u8) << 1500_u32)
3087 );
3088 assert_eq!(
3089 small_solution.as_array()[1],
3090 BigRational::new(BigInt::from(-1_i8), BigInt::from(1_u8) << 1499_u32)
3091 );
3092
3093 let tiny_matrix = Matrix::<1>::try_from_rows([[tiny]]).unwrap();
3094 let large_rhs = Vector::<1>::new([large]);
3095 let large_solution = tiny_matrix.solve_exact(large_rhs).unwrap();
3096 assert_eq!(
3097 large_solution.as_array()[0],
3098 BigRational::from_integer(BigInt::from(1_u8) << 1500_u32)
3099 );
3100 }
3101
3102 macro_rules! gen_solve_exact_subnormal_rhs_tests {
3108 ($d:literal) => {
3109 paste! {
3110 #[test]
3111 #[expect(
3112 clippy::cast_precision_loss,
3113 reason = "indices are at most five and exactly representable as f64"
3114 )]
3115 fn [<solve_exact_subnormal_rhs_ $d d>]() {
3116 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3118 let a = Matrix::<$d>::identity();
3119 let mut b_arr = [0.0f64; $d];
3121 for i in 0..$d {
3122 b_arr[i] = (i + 1) as f64 * tiny;
3123 assert!(b_arr[i].is_subnormal());
3124 }
3125 let b = Vector::<$d>::new(b_arr);
3126 let x = a.solve_exact(b).unwrap();
3127 for i in 0..$d {
3128 assert_eq!(x.as_array()[i], f64_to_big_rational((i + 1) as f64 * tiny));
3129 }
3130 }
3131 }
3132 };
3133 }
3134
3135 gen_solve_exact_subnormal_rhs_tests!(2);
3136 gen_solve_exact_subnormal_rhs_tests!(3);
3137 gen_solve_exact_subnormal_rhs_tests!(4);
3138 gen_solve_exact_subnormal_rhs_tests!(5);
3139
3140 macro_rules! gen_solve_exact_pivot_swap_fractional_tests {
3150 ($d:literal) => {
3151 paste! {
3152 #[test]
3153 #[expect(
3154 clippy::cast_precision_loss,
3155 reason = "indices and test offsets are small integers exactly representable as f64"
3156 )]
3157 fn [<solve_exact_pivot_swap_with_fractional_result_ $d d>]() {
3158 let mut rows = [[0.0f64; $d]; $d];
3161 rows[0][1] = 1.0;
3162 rows[1][0] = 2.0;
3163 rows[1][1] = 1.0;
3164 for (i, row) in rows.iter_mut().enumerate().skip(2) {
3166 row[i] = 1.0;
3167 }
3168 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3169 let mut b_arr = [0.0f64; $d];
3172 b_arr[0] = 3.0;
3173 b_arr[1] = 4.0;
3174 for (i, value) in b_arr.iter_mut().enumerate().skip(2) {
3175 *value = (i + 10) as f64;
3176 }
3177 let b = Vector::<$d>::new(b_arr);
3178 let x = a.solve_exact(b).unwrap();
3179 assert_eq!(x.as_array()[0], BigRational::new(BigInt::from(1), BigInt::from(2)));
3180 assert_eq!(x.as_array()[1], BigRational::from_integer(BigInt::from(3)));
3181 for (i, value) in x.as_array().iter().enumerate().skip(2) {
3182 assert_eq!(value, &f64_to_big_rational((i + 10) as f64));
3183 }
3184 }
3185 }
3186 };
3187 }
3188
3189 gen_solve_exact_pivot_swap_fractional_tests!(2);
3190 gen_solve_exact_pivot_swap_fractional_tests!(3);
3191 gen_solve_exact_pivot_swap_fractional_tests!(4);
3192 gen_solve_exact_pivot_swap_fractional_tests!(5);
3193
3194 macro_rules! gen_solve_exact_mid_pivot_swap_tests {
3203 ($d:literal) => {
3204 paste! {
3205 #[test]
3206 #[expect(
3207 clippy::cast_precision_loss,
3208 reason = "indices and test offsets are small integers exactly representable as f64"
3209 )]
3210 fn [<solve_exact_mid_pivot_swap_ $d d>]() {
3211 let mut rows = [[0.0f64; $d]; $d];
3212 rows[0][0] = 1.0; rows[0][1] = 2.0; rows[0][2] = 3.0;
3213 rows[1][2] = 4.0;
3215 rows[2][1] = 5.0; rows[2][2] = 6.0;
3216 for (i, row) in rows.iter_mut().enumerate().skip(3) {
3218 row[i] = 1.0;
3219 }
3220 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3221 let mut b_arr = [0.0f64; $d];
3222 b_arr[0] = 6.0;
3223 b_arr[1] = 7.0;
3224 b_arr[2] = 8.0;
3225 for (i, value) in b_arr.iter_mut().enumerate().skip(3) {
3226 *value = (i + 10) as f64;
3227 }
3228 let b = Vector::<$d>::new(b_arr);
3229 let x = a.solve_exact(b).unwrap();
3230 assert_eq!(x.as_array()[0], BigRational::new(BigInt::from(7), BigInt::from(4)));
3232 assert_eq!(x.as_array()[1], BigRational::new(BigInt::from(-1), BigInt::from(2)));
3233 assert_eq!(x.as_array()[2], BigRational::new(BigInt::from(7), BigInt::from(4)));
3234 for (i, value) in x.as_array().iter().enumerate().skip(3) {
3235 assert_eq!(value, &f64_to_big_rational((i + 10) as f64));
3236 }
3237 }
3238 }
3239 };
3240 }
3241
3242 gen_solve_exact_mid_pivot_swap_tests!(3);
3243 gen_solve_exact_mid_pivot_swap_tests!(4);
3244 gen_solve_exact_mid_pivot_swap_tests!(5);
3245
3246 macro_rules! gen_solve_exact_singular_rank_deficient_tests {
3254 ($d:literal) => {
3255 paste! {
3256 #[test]
3257 fn [<solve_exact_singular_rank_deficient_ $d d>]() {
3258 let mut rows = [[0.0f64; $d]; $d];
3259 for i in 0..($d - 1) {
3260 rows[i][i] = 1.0;
3261 rows[$d - 1][i] = 1.0;
3262 }
3263 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3265 let b = Vector::<$d>::new([1.0; $d]);
3266 assert_matches!(
3267 a.solve_exact(b),
3268 Err(LaError::Singular {
3269 pivot_col,
3270 reason: SingularityReason::Exact,
3271 ..
3272 }) if pivot_col == $d - 1
3273 );
3274 }
3275 }
3276 };
3277 }
3278
3279 gen_solve_exact_singular_rank_deficient_tests!(2);
3280 gen_solve_exact_singular_rank_deficient_tests!(3);
3281 gen_solve_exact_singular_rank_deficient_tests!(4);
3282 gen_solve_exact_singular_rank_deficient_tests!(5);
3283
3284 macro_rules! gen_solve_exact_zero_rhs_tests {
3290 ($d:literal) => {
3291 paste! {
3292 #[test]
3293 fn [<solve_exact_zero_rhs_ $d d>]() {
3294 let mut rows = [[1.0f64; $d]; $d];
3296 for i in 0..$d {
3297 rows[i][i] = f64::from($d) + 1.0;
3298 }
3299 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3300 let b = Vector::<$d>::zero();
3301 let x = a.solve_exact(b).unwrap();
3302 for xi in x.as_array() {
3303 assert_eq!(*xi, BigRational::from_integer(BigInt::from(0)));
3304 }
3305 }
3306 }
3307 };
3308 }
3309
3310 gen_solve_exact_zero_rhs_tests!(2);
3311 gen_solve_exact_zero_rhs_tests!(3);
3312 gen_solve_exact_zero_rhs_tests!(4);
3313 gen_solve_exact_zero_rhs_tests!(5);
3314
3315 fn big_rational_matvec<const D: usize>(
3328 a: &Matrix<D>,
3329 x: &[BigRational; D],
3330 ) -> [BigRational; D] {
3331 from_fn(|i| {
3332 let mut sum = BigRational::from_integer(BigInt::from(0));
3333 for (aij, xj) in a.as_rows()[i].iter().zip(x.iter()) {
3334 sum += f64_to_big_rational(*aij) * xj;
3335 }
3336 sum
3337 })
3338 }
3339
3340 fn hilbert<const D: usize>() -> Matrix<D> {
3341 let rows = from_fn(|r| from_fn(|c| 1.0 / f64::from(u32::try_from(r + c + 1).unwrap())));
3342 Matrix::<D>::try_from_rows(rows).unwrap()
3343 }
3344
3345 #[test]
3353 fn solve_exact_near_singular_3x3_integer_x0() {
3354 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::try_from_rows([
3356 [1.0 + perturbation, 2.0, 3.0],
3357 [4.0, 5.0, 6.0],
3358 [7.0, 8.0, 9.0],
3359 ])
3360 .unwrap();
3361 let b = Vector::<3>::new([6.0 + perturbation, 15.0, 24.0]);
3362 let x = a.solve_exact(b).unwrap();
3363 let one = BigRational::from_integer(BigInt::from(1));
3364 assert_eq!(x.as_array()[0], one);
3365 assert_eq!(x.as_array()[1], one);
3366 assert_eq!(x.as_array()[2], one);
3367 }
3368
3369 #[test]
3376 fn solve_exact_large_entries_3x3_unit_vector() {
3377 let big = f64::MAX / 2.0;
3378 assert!(big.is_finite());
3379 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
3380 .unwrap();
3381 let b = Vector::<3>::new([big, 1.0, 1.0]);
3382 let x = a.solve_exact(b).unwrap();
3383 let zero = BigRational::from_integer(BigInt::from(0));
3384 let one = BigRational::from_integer(BigInt::from(1));
3385 assert_eq!(x.as_array()[0], one);
3386 assert_eq!(x.as_array()[1], zero);
3387 assert_eq!(x.as_array()[2], zero);
3388 }
3389
3390 #[test]
3396 fn det_sign_exact_large_entries_3x3_positive() {
3397 let big = f64::MAX / 2.0;
3398 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
3399 .unwrap();
3400 assert_matches!(
3403 a.det_direct(),
3404 Err(LaError::NonFinite {
3405 location: NonFiniteLocation::Scalar,
3406 origin: NonFiniteOrigin::Computation {
3407 operation: ArithmeticOperation::Determinant,
3408 ..
3409 },
3410 ..
3411 })
3412 );
3413 assert_eq!(a.det_sign_exact(), DeterminantSign::Positive);
3414 assert!(a.det_exact().unwrap().is_positive());
3418 assert_unrepresentable(&a.det_exact_f64(), None, UnrepresentableReason::NotFinite);
3419 }
3420
3421 macro_rules! gen_det_sign_exact_hilbert_positive_tests {
3430 ($d:literal) => {
3431 paste! {
3432 #[test]
3433 fn [<det_sign_exact_hilbert_positive_ $d d>]() {
3434 let h = hilbert::<$d>();
3435 assert_eq!(h.det_sign_exact(), DeterminantSign::Positive);
3436 }
3437 }
3438 };
3439 }
3440
3441 gen_det_sign_exact_hilbert_positive_tests!(2);
3442 gen_det_sign_exact_hilbert_positive_tests!(3);
3443 gen_det_sign_exact_hilbert_positive_tests!(4);
3444 gen_det_sign_exact_hilbert_positive_tests!(5);
3445
3446 macro_rules! gen_solve_exact_hilbert_residual_tests {
3453 ($d:literal) => {
3454 paste! {
3455 #[test]
3456 fn [<solve_exact_hilbert_residual_ $d d>]() {
3457 let h = hilbert::<$d>();
3458 let mut b_arr = [0.0f64; $d];
3461 for i in 0usize..$d {
3462 let sign = if i.is_multiple_of(2) { 1.0 } else { -1.0 };
3463 b_arr[i] = sign * f64::from(u32::try_from(i + 1).unwrap());
3464 }
3465 let b = Vector::<$d>::new(b_arr);
3466 let x = h.solve_exact(b).unwrap();
3467 let ax = big_rational_matvec(&h, x.as_array());
3468 for i in 0..$d {
3469 assert_eq!(ax[i], f64_to_big_rational(b_arr[i]));
3470 }
3471 }
3472 }
3473 };
3474 }
3475
3476 gen_solve_exact_hilbert_residual_tests!(2);
3477 gen_solve_exact_hilbert_residual_tests!(3);
3478 gen_solve_exact_hilbert_residual_tests!(4);
3479 gen_solve_exact_hilbert_residual_tests!(5);
3480
3481 #[test]
3486 fn solve_exact_f64_known_2x2() {
3487 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
3488 let b = Vector::<2>::new([5.0, 11.0]);
3489 let x = a.solve_exact_f64(b).unwrap().into_array();
3490 assert!((x[0] - 1.0).abs() <= f64::EPSILON);
3491 assert!((x[1] - 2.0).abs() <= f64::EPSILON);
3492 }
3493
3494 #[test]
3495 fn solve_exact_f64_overflow_returns_err() {
3496 let big = f64::MAX / 2.0;
3499 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
3500 let b = Vector::<2>::new([big, big]);
3501 assert_unrepresentable(
3502 &a.solve_exact_f64(b),
3503 Some(0),
3504 UnrepresentableReason::NotFinite,
3505 );
3506 }
3507
3508 #[test]
3509 fn solve_exact_f64_huge_non_dyadic_component_returns_not_finite() {
3510 let a = Matrix::<1>::try_from_rows([[3.0 * f64::MIN_POSITIVE]]).unwrap();
3511 let b = Vector::<1>::new([f64::MAX]);
3512
3513 assert_unrepresentable(
3514 &a.solve_exact_f64(b),
3515 Some(0),
3516 UnrepresentableReason::NotFinite,
3517 );
3518 }
3519
3520 #[test]
3521 fn solve_exact_rounded_f64_overflow_returns_err() {
3522 let big = f64::MAX / 2.0;
3523 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
3524 let b = Vector::<2>::new([big, big]);
3525
3526 assert_unrepresentable(
3527 &a.solve_exact_rounded_f64(b),
3528 Some(0),
3529 UnrepresentableReason::NotFinite,
3530 );
3531 }
3532
3533 #[test]
3534 fn solve_exact_f64_underflow_returns_err_for_nonzero_exact_component() {
3535 let tiny = f64::from_bits(1);
3536 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3537 let b = Vector::<1>::new([tiny]);
3538
3539 assert_unrepresentable(
3540 &a.solve_exact_f64(b),
3541 Some(0),
3542 UnrepresentableReason::RequiresRounding,
3543 );
3544 }
3545
3546 #[test]
3547 fn solve_exact_f64_accepts_smallest_subnormal_result() {
3548 let tiny = f64::from_bits(1);
3549 let a = Matrix::<1>::identity();
3550 let b = Vector::<1>::new([tiny]);
3551
3552 assert_eq!(
3553 a.solve_exact_f64(b).unwrap().into_array()[0].to_bits(),
3554 tiny.to_bits()
3555 );
3556 }
3557
3558 #[test]
3563 fn bareiss_solve_d1() {
3564 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3565 let b = Vector::<1>::new([6.0]);
3566 let x = a.solve_exact(b).unwrap();
3567 assert_eq!(x.as_array()[0], f64_to_big_rational(3.0));
3568 }
3569
3570 #[test]
3571 fn bareiss_solve_singular_column_all_zero() {
3572 let a = Matrix::<3>::try_from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
3573 .unwrap();
3574 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
3575 assert_matches!(
3576 a.solve_exact(b),
3577 Err(LaError::Singular {
3578 pivot_col: 1,
3579 reason: SingularityReason::Exact,
3580 ..
3581 })
3582 );
3583 }
3584
3585 #[test]
3590 fn f64_to_big_rational_scalar_cases() {
3591 let cases = [
3592 ("positive zero", 0.0, 0, 1),
3593 ("negative zero", -0.0, 0, 1),
3594 ("one", 1.0, 1, 1),
3595 ("negative one", -1.0, -1, 1),
3596 ("half", 0.5, 1, 2),
3597 ("quarter", 0.25, 1, 4),
3598 ("negative three and a half", -3.5, -7, 2),
3599 ("integer", 42.0, 42, 1),
3600 ("power of two", 1024.0, 1024, 1),
3601 ];
3602
3603 for (label, value, numerator, denominator) in cases {
3604 assert_eq!(
3605 f64_to_big_rational(value),
3606 BigRational::new(BigInt::from(numerator), BigInt::from(denominator)),
3607 "{label}"
3608 );
3609 }
3610 }
3611
3612 #[test]
3613 fn f64_to_big_rational_subnormal() {
3614 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3616 let r = f64_to_big_rational(tiny);
3617 assert_eq!(
3619 r,
3620 BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
3621 );
3622 }
3623
3624 #[test]
3625 fn f64_to_big_rational_already_lowest_terms() {
3626 let r = f64_to_big_rational(0.5);
3628 assert_eq!(*r.numer(), BigInt::from(1));
3629 assert_eq!(*r.denom(), BigInt::from(2));
3630 }
3631
3632 #[test]
3633 fn f64_to_big_rational_round_trip() {
3634 let values = [
3637 0.0,
3638 1.0,
3639 -1.0,
3640 0.5,
3641 0.25,
3642 0.1,
3643 42.0,
3644 -3.5,
3645 1e10,
3646 1e-10,
3647 f64::MAX / 2.0,
3648 f64::MIN_POSITIVE,
3649 5e-324,
3650 ];
3651 for &v in &values {
3652 let r = f64_to_big_rational(v);
3653 let back = r.to_f64().expect("round-trip to_f64 failed");
3654 assert_eq!(
3655 v.to_bits(),
3656 back.to_bits(),
3657 "round-trip failed for {v}: got {back}"
3658 );
3659 }
3660 }
3661}