1#![forbid(unsafe_code)]
2
3use core::hint::cold_path;
99use core::mem::take;
100use core::num::NonZeroU64;
101use std::array::from_fn;
102
103use num_bigint::{BigInt, Sign};
104use num_rational::BigRational;
105use num_traits::ToPrimitive;
106
107use crate::matrix::Matrix;
108use crate::rational::RationalVector;
109use crate::vector::Vector;
110use crate::{LaError, UnrepresentableReason};
111
112#[must_use]
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum DeterminantSign {
130 Negative,
132 Zero,
134 Positive,
136}
137
138impl DeterminantSign {
139 #[inline]
141 #[must_use]
142 pub const fn as_i8(self) -> i8 {
143 match self {
144 Self::Negative => -1,
145 Self::Zero => 0,
146 Self::Positive => 1,
147 }
148 }
149}
150
151pub trait ExactF64Conversion {
187 type Output;
189
190 fn try_to_f64(&self) -> Result<Self::Output, LaError>;
204
205 fn to_rounded_f64(&self) -> Result<Self::Output, LaError>;
214}
215
216const F64_SIGNIFICAND_BITS: i64 = 53;
217const F64_FRACTION_BITS: i64 = 52;
218const F64_MIN_BINARY_EXPONENT: i64 = -1074;
219const F64_MIN_NORMAL_EXPONENT: i64 = -1022;
220const F64_MAX_BINARY_EXPONENT: i64 = 1023;
221const F64_EXPONENT_BIAS: i64 = 1023;
222const F64_FRACTION_MASK: u64 = (1u64 << 52) - 1;
223
224const fn decompose_proven_finite_f64(x: f64) -> Component {
231 let bits = x.to_bits();
232 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
233 let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
234
235 if biased_exp == 0 && fraction == 0 {
237 return Component::Zero;
238 }
239
240 let (mantissa, raw_exp) = if biased_exp == 0 {
241 (fraction, -1074_i32)
244 } else {
245 ((1u64 << 52) | fraction, biased_exp - 1075)
248 };
249
250 let tz = mantissa.trailing_zeros();
253 let Some(mantissa) = NonZeroU64::new(mantissa >> tz) else {
254 return Component::Zero;
255 };
256
257 Component::NonZero {
258 mantissa,
259 exponent: raw_exp + tz.cast_signed(),
260 is_negative: bits >> 63 != 0,
261 }
262}
263
264fn big_int_exp_to_big_rational(mut value: BigInt, mut exp: i32) -> BigRational {
270 if value == BigInt::from(0) {
271 return BigRational::from_integer(BigInt::from(0));
272 }
273
274 if exp < 0
276 && let Some(tz) = value.trailing_zeros()
277 {
278 let exp_abs = exp.unsigned_abs();
279 let reduce = tz.min(u64::from(exp_abs));
280 value >>= reduce;
281 let remaining_abs = u64::from(exp_abs) - reduce;
282 exp = negative_exponent_from_magnitude(remaining_abs);
283 }
284
285 if exp >= 0 {
286 BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
287 } else {
288 BigRational::new_raw(value, BigInt::from(1u32) << exp.unsigned_abs())
289 }
290}
291
292#[inline]
298fn negative_exponent_from_magnitude(magnitude: u64) -> i32 {
299 if magnitude == u64::from(i32::MIN.unsigned_abs()) {
300 return i32::MIN;
301 }
302
303 let Ok(value) = i32::try_from(magnitude) else {
304 cold_path();
305 unreachable!("negative exponent magnitude exceeds the i32 domain");
306 };
307 -value
308}
309
310fn exact_rational_to_finite_f64(exact: &BigRational, index: Option<usize>) -> Result<f64, LaError> {
324 if exact.denom().sign() == Sign::NoSign {
325 cold_path();
326 return Err(LaError::unrepresentable(
327 index,
328 UnrepresentableReason::NotFinite,
329 ));
330 }
331
332 if exact.numer().sign() == Sign::NoSign {
333 return Ok(0.0);
334 }
335
336 let denominator = exact.denom();
337 if denominator.sign() == Sign::Plus
338 && let Some(denominator_exp) = positive_power_of_two_exponent(denominator)
339 && let Ok(denominator_exp) = i32::try_from(denominator_exp)
340 {
341 return big_int_exp_ref_to_finite_f64(exact.numer(), -denominator_exp, index, || {
342 rounded_rational_unrepresentable_reason(exact)
343 });
344 }
345
346 let reduced = exact.reduced();
351 reduced_rational_to_finite_f64(&reduced, index)
352}
353
354fn positive_power_of_two_exponent(value: &BigInt) -> Option<u64> {
356 if value.sign() != Sign::Plus {
357 return None;
358 }
359
360 let exponent = value.trailing_zeros()?;
361 (value.bits().checked_sub(1) == Some(exponent)).then_some(exponent)
362}
363
364fn reduced_rational_to_finite_f64(
378 exact: &BigRational,
379 index: Option<usize>,
380) -> Result<f64, LaError> {
381 let Some(denominator_exp) = positive_power_of_two_exponent(exact.denom()) else {
382 cold_path();
383 return Err(LaError::unrepresentable(
384 index,
385 rounded_rational_unrepresentable_reason(exact),
386 ));
387 };
388 let Ok(denominator_exp) = i32::try_from(denominator_exp) else {
389 cold_path();
390 return Err(LaError::unrepresentable(
391 index,
392 rounded_rational_unrepresentable_reason(exact),
393 ));
394 };
395
396 big_int_exp_ref_to_finite_f64(exact.numer(), -denominator_exp, index, || {
397 rounded_rational_unrepresentable_reason(exact)
398 })
399}
400
401fn rounded_rational_unrepresentable_reason(exact: &BigRational) -> UnrepresentableReason {
407 match exact.to_f64() {
408 Some(value) if value.is_finite() => UnrepresentableReason::RequiresRounding,
409 _ => UnrepresentableReason::NotFinite,
410 }
411}
412
413fn exact_rational_to_rounded_f64(
416 exact: &BigRational,
417 index: Option<usize>,
418) -> Result<f64, LaError> {
419 if exact.denom().sign() == Sign::NoSign {
420 cold_path();
421 return Err(LaError::unrepresentable(
422 index,
423 UnrepresentableReason::NotFinite,
424 ));
425 }
426 if exact.numer().sign() == Sign::NoSign {
427 return Ok(0.0);
428 }
429
430 let Some(value) = exact.to_f64() else {
431 cold_path();
432 return Err(LaError::unrepresentable(
433 index,
434 UnrepresentableReason::NotFinite,
435 ));
436 };
437 if value.is_finite() {
438 Ok(value)
439 } else {
440 cold_path();
441 Err(LaError::unrepresentable(
442 index,
443 UnrepresentableReason::NotFinite,
444 ))
445 }
446}
447
448impl ExactF64Conversion for BigRational {
449 type Output = f64;
450
451 #[inline]
452 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
453 exact_rational_to_finite_f64(self, None)
454 }
455
456 #[inline]
457 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
458 exact_rational_to_rounded_f64(self, None)
459 }
460}
461
462impl<const D: usize> ExactF64Conversion for [BigRational; D] {
463 type Output = Vector<D>;
464
465 #[inline]
466 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
467 let mut result = [0.0; D];
468 for (index, value) in self.iter().enumerate() {
469 result[index] = exact_rational_to_finite_f64(value, Some(index))?;
470 }
471 Vector::try_new(result)
472 }
473
474 #[inline]
475 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
476 let mut result = [0.0; D];
477 for (index, value) in self.iter().enumerate() {
478 result[index] = exact_rational_to_rounded_f64(value, Some(index))?;
479 }
480 Vector::try_new(result)
481 }
482}
483
484impl<const D: usize> ExactF64Conversion for RationalVector<D> {
485 type Output = Vector<D>;
486
487 #[inline]
488 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
489 let mut result = [0.0; D];
490 for (index, value) in self.as_array().iter().enumerate() {
491 result[index] = reduced_rational_to_finite_f64(value, Some(index))?;
493 }
494 Vector::try_new(result)
495 }
496
497 #[inline]
498 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
499 self.as_array().to_rounded_f64()
500 }
501}
502
503fn shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option<u64> {
509 let word_bits = u64::from(u64::BITS);
510 let word_index = usize::try_from(shift / word_bits).ok()?;
511 let bit_shift = u32::try_from(shift % word_bits).ok()?;
512 let mut digits = value.iter_u64_digits().skip(word_index);
513 let low = digits.next()? >> bit_shift;
514 if bit_shift == 0 {
515 Some(low)
516 } else {
517 let high = digits.next().unwrap_or(0) << (u64::BITS - bit_shift);
518 Some(low | high)
519 }
520}
521
522fn magnitude_bit_is_set(value: &BigInt, bit: u64) -> bool {
524 let word_bits = u64::from(u64::BITS);
525 let Ok(word_index) = usize::try_from(bit / word_bits) else {
526 return false;
527 };
528 let bit_index = u32::try_from(bit % word_bits).unwrap_or(0);
529 value
530 .iter_u64_digits()
531 .nth(word_index)
532 .is_some_and(|word| word & (1_u64 << bit_index) != 0)
533}
534
535fn magnitude_has_lower_bits(value: &BigInt, exclusive_end: u64) -> bool {
537 let word_bits = u64::from(u64::BITS);
538 let Ok(full_words) = usize::try_from(exclusive_end / word_bits) else {
539 return value.sign() != Sign::NoSign;
540 };
541 let partial_bits = u32::try_from(exclusive_end % word_bits).unwrap_or(0);
542 let mut digits = value.iter_u64_digits();
543
544 for _ in 0..full_words {
545 if digits.next().unwrap_or(0) != 0 {
546 return true;
547 }
548 }
549
550 if partial_bits == 0 {
551 false
552 } else {
553 let mask = (1_u64 << partial_bits) - 1;
554 digits.next().is_some_and(|word| word & mask != 0)
555 }
556}
557
558fn rounded_shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option<u64> {
564 if shift > value.bits() {
565 return Some(0);
566 }
567 let retained = shifted_magnitude_to_u64(value, shift).unwrap_or(0);
568 if shift == 0 {
569 return Some(retained);
570 }
571
572 let guard_bit = shift - 1;
573 let increment = magnitude_bit_is_set(value, guard_bit)
574 && (magnitude_has_lower_bits(value, guard_bit) || retained & 1 != 0);
575 retained.checked_add(u64::from(increment))
576}
577
578#[inline]
581fn inexact_big_int_reason(
582 top_bit_exp: i64,
583 rounded_reason: impl FnOnce() -> UnrepresentableReason,
584) -> UnrepresentableReason {
585 if top_bit_exp < F64_MAX_BINARY_EXPONENT {
586 UnrepresentableReason::RequiresRounding
587 } else {
588 rounded_reason()
589 }
590}
591
592fn big_int_exp_ref_to_rounded_f64(
599 value: &BigInt,
600 exp: i32,
601 index: Option<usize>,
602) -> Result<f64, LaError> {
603 if value.sign() == Sign::NoSign {
604 return Ok(0.0);
605 }
606
607 let sign = if value.sign() == Sign::Minus {
608 1_u64 << 63
609 } else {
610 0
611 };
612 let Ok(bit_len) = i64::try_from(value.bits()) else {
613 cold_path();
614 return Err(LaError::unrepresentable(
615 index,
616 UnrepresentableReason::NotFinite,
617 ));
618 };
619 let Some(mut top_bit_exp) = i64::from(exp).checked_add(bit_len - 1) else {
620 cold_path();
621 return Err(LaError::unrepresentable(
622 index,
623 UnrepresentableReason::NotFinite,
624 ));
625 };
626 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
627 cold_path();
628 return Err(LaError::unrepresentable(
629 index,
630 UnrepresentableReason::NotFinite,
631 ));
632 }
633
634 if top_bit_exp >= F64_MIN_NORMAL_EXPONENT {
635 let mut significand = if bit_len <= F64_SIGNIFICAND_BITS {
636 let Some(magnitude) = shifted_magnitude_to_u64(value, 0) else {
637 cold_path();
638 unreachable!("nonzero integer must expose magnitude digits");
639 };
640 let shift = u32::try_from(F64_SIGNIFICAND_BITS - bit_len)
641 .unwrap_or_else(|_| unreachable!("normal significand shift must fit u32"));
642 magnitude
643 .checked_shl(shift)
644 .unwrap_or_else(|| unreachable!("normal significand must fit u64"))
645 } else {
646 let shift = u64::try_from(bit_len - F64_SIGNIFICAND_BITS)
647 .unwrap_or_else(|_| unreachable!("positive significand shift must fit u64"));
648 rounded_shifted_magnitude_to_u64(value, shift)
649 .unwrap_or_else(|| unreachable!("rounded binary64 significand must fit u64"))
650 };
651
652 if significand == 1_u64 << F64_SIGNIFICAND_BITS {
653 significand >>= 1;
654 top_bit_exp += 1;
655 }
656 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
657 cold_path();
658 return Err(LaError::unrepresentable(
659 index,
660 UnrepresentableReason::NotFinite,
661 ));
662 }
663
664 let biased_exp = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS)
665 .unwrap_or_else(|_| unreachable!("normal exponent must be positive"));
666 return Ok(f64::from_bits(
667 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
668 ));
669 }
670
671 let subnormal_shift = i64::from(exp) - F64_MIN_BINARY_EXPONENT;
672 let significand = if subnormal_shift >= 0 {
673 let Some(magnitude) = shifted_magnitude_to_u64(value, 0) else {
674 cold_path();
675 unreachable!("nonzero integer must expose magnitude digits");
676 };
677 let shift = u32::try_from(subnormal_shift)
678 .unwrap_or_else(|_| unreachable!("subnormal left shift must fit u32"));
679 magnitude
680 .checked_shl(shift)
681 .unwrap_or_else(|| unreachable!("subnormal significand must fit u64"))
682 } else {
683 let shift = u64::try_from(-subnormal_shift)
684 .unwrap_or_else(|_| unreachable!("subnormal right shift must fit u64"));
685 rounded_shifted_magnitude_to_u64(value, shift)
686 .unwrap_or_else(|| unreachable!("rounded subnormal significand must fit u64"))
687 };
688
689 Ok(f64::from_bits(sign | significand))
691}
692
693fn big_int_exp_ref_to_finite_f64(
703 value: &BigInt,
704 exp: i32,
705 index: Option<usize>,
706 rounded_reason: impl FnOnce() -> UnrepresentableReason,
707) -> Result<f64, LaError> {
708 if value.sign() == Sign::NoSign {
709 return Ok(0.0);
710 }
711
712 let is_negative = value.sign() == Sign::Minus;
713 let mut exp = i64::from(exp);
714 let Some(trailing_zeros) = value.trailing_zeros() else {
715 cold_path();
716 unreachable!("nonzero integer must have a least-significant set bit");
717 };
718 let Ok(trailing_zeros_i64) = i64::try_from(trailing_zeros) else {
719 cold_path();
720 return Err(LaError::unrepresentable(
721 index,
722 UnrepresentableReason::NotFinite,
723 ));
724 };
725 let Some(updated_exp) = exp.checked_add(trailing_zeros_i64) else {
726 cold_path();
727 return Err(LaError::unrepresentable(
728 index,
729 UnrepresentableReason::NotFinite,
730 ));
731 };
732 exp = updated_exp;
733
734 let Some(bit_len) = value.bits().checked_sub(trailing_zeros) else {
735 cold_path();
736 unreachable!("trailing-zero count cannot exceed integer bit length");
737 };
738 let Ok(bit_len) = i64::try_from(bit_len) else {
739 cold_path();
740 return Err(LaError::unrepresentable(
741 index,
742 UnrepresentableReason::NotFinite,
743 ));
744 };
745 let Some(top_bit_exp) = exp.checked_add(bit_len - 1) else {
746 cold_path();
747 return Err(LaError::unrepresentable(
748 index,
749 UnrepresentableReason::NotFinite,
750 ));
751 };
752 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
753 cold_path();
754 return Err(LaError::unrepresentable(
755 index,
756 UnrepresentableReason::NotFinite,
757 ));
758 }
759 if exp < F64_MIN_BINARY_EXPONENT {
760 cold_path();
761 let reason = inexact_big_int_reason(top_bit_exp, rounded_reason);
765 return Err(LaError::unrepresentable(index, reason));
766 }
767 if bit_len > F64_SIGNIFICAND_BITS {
768 cold_path();
769 let reason = inexact_big_int_reason(top_bit_exp, rounded_reason);
773 return Err(LaError::unrepresentable(index, reason));
774 }
775
776 let Some(mantissa) = shifted_magnitude_to_u64(value, trailing_zeros) else {
777 cold_path();
778 return Err(LaError::unrepresentable(
779 index,
780 UnrepresentableReason::NotFinite,
781 ));
782 };
783 let sign = if is_negative { 1u64 << 63 } else { 0 };
784
785 if top_bit_exp < F64_MIN_NORMAL_EXPONENT {
786 let Ok(shift) = u32::try_from(exp - F64_MIN_BINARY_EXPONENT) else {
787 cold_path();
788 return Err(LaError::unrepresentable(
789 index,
790 UnrepresentableReason::RequiresRounding,
791 ));
792 };
793 Ok(f64::from_bits(sign | (mantissa << shift)))
794 } else {
795 let Ok(biased_exp) = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS) else {
796 cold_path();
797 return Err(LaError::unrepresentable(
798 index,
799 UnrepresentableReason::NotFinite,
800 ));
801 };
802 let Ok(shift) = u32::try_from(F64_FRACTION_BITS - (bit_len - 1)) else {
803 cold_path();
804 return Err(LaError::unrepresentable(
805 index,
806 UnrepresentableReason::RequiresRounding,
807 ));
808 };
809 let significand = mantissa << shift;
810 Ok(f64::from_bits(
811 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
812 ))
813 }
814}
815
816fn big_int_exp_to_finite_f64(
817 value: &BigInt,
818 exp: i32,
819 index: Option<usize>,
820) -> Result<f64, LaError> {
821 big_int_exp_ref_to_finite_f64(value, exp, index, || {
822 match big_int_exp_ref_to_rounded_f64(value, exp, index) {
823 Ok(_) => UnrepresentableReason::RequiresRounding,
824 Err(_) => UnrepresentableReason::NotFinite,
825 }
826 })
827}
828
829fn big_int_exp_to_rounded_f64(value: &BigInt, exp: i32) -> Result<f64, LaError> {
831 big_int_exp_ref_to_rounded_f64(value, exp, None)
832}
833
834#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
854enum Component {
855 #[default]
856 Zero,
857 NonZero {
858 mantissa: NonZeroU64,
859 exponent: i32,
860 is_negative: bool,
861 },
862}
863
864impl Component {
865 const fn exponent(self) -> Option<i32> {
867 match self {
868 Self::Zero => None,
869 Self::NonZero { exponent, .. } => Some(exponent),
870 }
871 }
872}
873
874mod decomposition {
875 use super::Component;
876
877 #[derive(Clone, Debug, Eq, PartialEq)]
883 pub(super) struct Decomposed<T> {
884 components: T,
885 min_exponent: Option<i32>,
886 }
887
888 impl<T> Decomposed<T> {
889 pub(super) const fn components(&self) -> &T {
891 &self.components
892 }
893
894 pub(super) const fn min_exponent(&self) -> Option<i32> {
896 self.min_exponent
897 }
898 }
899
900 impl<const D: usize> Decomposed<[Component; D]> {
901 pub(super) fn from_vector_components(components: [Component; D]) -> Self {
903 let min_exponent = components
904 .iter()
905 .filter_map(|component| component.exponent())
906 .min();
907 Self {
908 components,
909 min_exponent,
910 }
911 }
912 }
913
914 impl<const D: usize> Decomposed<[[Component; D]; D]> {
915 pub(super) fn from_matrix_components(components: [[Component; D]; D]) -> Self {
917 let min_exponent = components
918 .iter()
919 .flatten()
920 .filter_map(|component| component.exponent())
921 .min();
922 Self {
923 components,
924 min_exponent,
925 }
926 }
927 }
928
929 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
934 pub(super) struct ScaleExponent {
935 value: i32,
936 }
937
938 impl ScaleExponent {
939 pub(super) const ZERO: Self = Self { value: 0 };
941
942 pub(super) const fn for_decomposed<T>(decomposed: &Decomposed<T>) -> Self {
944 let value = match decomposed.min_exponent() {
945 Some(exponent) => exponent,
946 None => 0,
947 };
948 Self { value }
949 }
950
951 pub(super) const fn min(self, other: Self) -> Self {
953 if self.value < other.value {
954 self
955 } else {
956 other
957 }
958 }
959
960 pub(super) const fn get(self) -> i32 {
962 self.value
963 }
964
965 pub(super) fn shift_for(self, exponent: i32) -> u32 {
971 let Some(shift) = exponent.checked_sub(self.value) else {
972 unreachable!("finite f64 exponent difference cannot overflow");
973 };
974 let Ok(shift) = u32::try_from(shift) else {
975 unreachable!("scale exponent cannot exceed a component exponent");
976 };
977 shift
978 }
979 }
980}
981
982use decomposition::{Decomposed, ScaleExponent};
983
984fn decompose_proven_finite_matrix<const D: usize>(
986 m: &Matrix<D>,
987) -> Decomposed<[[Component; D]; D]> {
988 let components =
989 from_fn(|row| from_fn(|col| decompose_proven_finite_f64(m.as_rows()[row][col])));
990 Decomposed::from_matrix_components(components)
991}
992
993fn decompose_proven_finite_vector<const D: usize>(v: &Vector<D>) -> Decomposed<[Component; D]> {
995 let components = from_fn(|index| decompose_proven_finite_f64(v.as_array()[index]));
996 Decomposed::from_vector_components(components)
997}
998
999#[inline]
1002fn component_to_big_int(component: Component, scale: ScaleExponent) -> BigInt {
1003 match component {
1004 Component::Zero => BigInt::from(0),
1005 Component::NonZero {
1006 mantissa,
1007 exponent,
1008 is_negative,
1009 } => {
1010 let value = BigInt::from(mantissa.get()) << scale.shift_for(exponent);
1011 if is_negative { -value } else { value }
1012 }
1013 }
1014}
1015
1016fn build_big_int_matrix<const D: usize>(
1018 components: &[[Component; D]; D],
1019 scale: ScaleExponent,
1020) -> [[BigInt; D]; D] {
1021 from_fn(|row| from_fn(|col| component_to_big_int(components[row][col], scale)))
1022}
1023
1024fn build_big_int_vec<const D: usize>(
1026 components: &[Component; D],
1027 scale: ScaleExponent,
1028) -> [BigInt; D] {
1029 from_fn(|index| component_to_big_int(components[index], scale))
1030}
1031
1032#[inline]
1034fn det2_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1035 &a[0][0] * &a[1][1] - &a[0][1] * &a[1][0]
1036}
1037
1038#[inline]
1044fn det3_big_int_entries(a: [[&BigInt; 3]; 3]) -> BigInt {
1045 let m00 = a[1][1] * a[2][2] - a[1][2] * a[2][1];
1046 let m01 = a[1][0] * a[2][2] - a[1][2] * a[2][0];
1047 let m02 = a[1][0] * a[2][1] - a[1][1] * a[2][0];
1048 a[0][0] * m00 - a[0][1] * m01 + a[0][2] * m02
1049}
1050
1051#[inline]
1053fn det3_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1054 det3_big_int_entries([
1055 [&a[0][0], &a[0][1], &a[0][2]],
1056 [&a[1][0], &a[1][1], &a[1][2]],
1057 [&a[2][0], &a[2][1], &a[2][2]],
1058 ])
1059}
1060
1061#[inline]
1069fn det4_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1070 if a[0][..4].iter().all(|value| value.sign() != Sign::NoSign) {
1071 let m01 = &a[2][0] * &a[3][1] - &a[2][1] * &a[3][0];
1074 let m02 = &a[2][0] * &a[3][2] - &a[2][2] * &a[3][0];
1075 let m03 = &a[2][0] * &a[3][3] - &a[2][3] * &a[3][0];
1076 let m12 = &a[2][1] * &a[3][2] - &a[2][2] * &a[3][1];
1077 let m13 = &a[2][1] * &a[3][3] - &a[2][3] * &a[3][1];
1078 let m23 = &a[2][2] * &a[3][3] - &a[2][3] * &a[3][2];
1079 let c00 = &a[1][1] * &m23 - &a[1][2] * &m13 + &a[1][3] * &m12;
1080 let mut det = &a[0][0] * c00;
1081 let c01 = &a[1][0] * m23 - &a[1][2] * &m03 + &a[1][3] * &m02;
1082 det -= &a[0][1] * c01;
1083 let c02 = &a[1][0] * m13 - &a[1][1] * m03 + &a[1][3] * &m01;
1084 det += &a[0][2] * c02;
1085 let c03 = &a[1][0] * m12 - &a[1][1] * m02 + &a[1][2] * m01;
1086 return det - &a[0][3] * c03;
1087 }
1088
1089 let mut det = BigInt::from(0);
1090
1091 if a[0][0].sign() != Sign::NoSign {
1092 let c00 = det3_big_int_entries([
1093 [&a[1][1], &a[1][2], &a[1][3]],
1094 [&a[2][1], &a[2][2], &a[2][3]],
1095 [&a[3][1], &a[3][2], &a[3][3]],
1096 ]);
1097 det += &a[0][0] * c00;
1098 }
1099 if a[0][1].sign() != Sign::NoSign {
1100 let c01 = det3_big_int_entries([
1101 [&a[1][0], &a[1][2], &a[1][3]],
1102 [&a[2][0], &a[2][2], &a[2][3]],
1103 [&a[3][0], &a[3][2], &a[3][3]],
1104 ]);
1105 det -= &a[0][1] * c01;
1106 }
1107 if a[0][2].sign() != Sign::NoSign {
1108 let c02 = det3_big_int_entries([
1109 [&a[1][0], &a[1][1], &a[1][3]],
1110 [&a[2][0], &a[2][1], &a[2][3]],
1111 [&a[3][0], &a[3][1], &a[3][3]],
1112 ]);
1113 det += &a[0][2] * c02;
1114 }
1115 if a[0][3].sign() != Sign::NoSign {
1116 let c03 = det3_big_int_entries([
1117 [&a[1][0], &a[1][1], &a[1][2]],
1118 [&a[2][0], &a[2][1], &a[2][2]],
1119 [&a[3][0], &a[3][1], &a[3][2]],
1120 ]);
1121 det -= &a[0][3] * c03;
1122 }
1123
1124 det
1125}
1126
1127pub(crate) fn det_big_int<const D: usize>(mut a: [[BigInt; D]; D]) -> BigInt {
1130 if D == 0 {
1131 return BigInt::from(1);
1132 }
1133
1134 match D {
1135 1 => take(&mut a[0][0]),
1136 2 => det2_big_int(&a),
1137 3 => det3_big_int(&a),
1138 4 => det4_big_int(&a),
1139 _ => {
1140 let odd_swaps = match bareiss_forward_eliminate(&mut a, None) {
1141 BareissResult::Upper { odd_swaps } => odd_swaps,
1142 BareissResult::Singular { .. } => {
1143 cold_path();
1144 return BigInt::from(0);
1145 }
1146 };
1147
1148 let determinant = take(&mut a[D - 1][D - 1]);
1149 if odd_swaps { -determinant } else { determinant }
1150 }
1151 }
1152}
1153
1154#[derive(Debug)]
1156enum BareissResult {
1157 Upper { odd_swaps: bool },
1160 Singular { pivot_col: usize },
1162}
1163
1164fn bareiss_forward_eliminate<const D: usize>(
1178 a: &mut [[BigInt; D]; D],
1179 mut rhs: Option<&mut [BigInt; D]>,
1180) -> BareissResult {
1181 let zero = BigInt::from(0);
1182 let mut prev_pivot = BigInt::from(1);
1183 let mut odd_swaps = false;
1184
1185 for k in 0..D {
1186 if a[k][k] == zero {
1188 let mut found = false;
1189 for i in (k + 1)..D {
1190 if a[i][k] != zero {
1191 a.swap(k, i);
1192 if let Some(r) = &mut rhs {
1193 r.swap(k, i);
1194 }
1195 odd_swaps = !odd_swaps;
1196 found = true;
1197 break;
1198 }
1199 }
1200 if !found {
1201 cold_path();
1202 return BareissResult::Singular { pivot_col: k };
1203 }
1204 }
1205
1206 if k + 1 == D {
1209 break;
1210 }
1211
1212 for i in (k + 1)..D {
1216 for j in (k + 1)..D {
1217 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
1218 }
1219 if let Some(r) = &mut rhs {
1220 r[i] = (&a[k][k] * &r[i] - &a[i][k] * &r[k]) / &prev_pivot;
1221 }
1222 a[i][k].clone_from(&zero);
1223 }
1224
1225 prev_pivot.clone_from(&a[k][k]);
1226 }
1227
1228 #[cfg(debug_assertions)]
1232 for (k, row) in a.iter().enumerate() {
1233 assert_ne!(row[k], zero, "pivot at ({k}, {k}) must be non-zero");
1234 for (i, lower_row) in a.iter().enumerate().skip(k + 1) {
1235 assert_eq!(
1236 lower_row[k], zero,
1237 "sub-diagonal at ({i}, {k}) must be zero"
1238 );
1239 }
1240 }
1241
1242 BareissResult::Upper { odd_swaps }
1243}
1244
1245fn determinant_scale_exp<const D: usize>(total: i64, e_min: i32) -> Result<i32, LaError> {
1256 let Ok(total_exp) = i32::try_from(total) else {
1257 cold_path();
1258 return Err(LaError::determinant_scale_overflow(D, e_min));
1259 };
1260 Ok(total_exp)
1261}
1262
1263#[expect(
1276 clippy::inline_always,
1277 reason = "keep the measured row-scaling prototype's fixed-dimension code generation"
1278)]
1279#[inline(always)]
1280fn scaled_det_int_finite<const D: usize>(m: &Matrix<D>) -> (BigInt, [ScaleExponent; D]) {
1281 let mut scales = [ScaleExponent::ZERO; D];
1282 let mut has_zero_row = false;
1283 let a: [[BigInt; D]; D] = from_fn(|row| {
1284 let decomposed = Decomposed::from_vector_components(from_fn(|column| {
1285 decompose_proven_finite_f64(m.as_rows()[row][column])
1286 }));
1287 has_zero_row |= decomposed.min_exponent().is_none();
1288 let scale = ScaleExponent::for_decomposed(&decomposed);
1289 scales[row] = scale;
1290 build_big_int_vec(decomposed.components(), scale)
1291 });
1292 if has_zero_row {
1293 return (BigInt::from(0), scales);
1294 }
1295 (det_big_int(a), scales)
1296}
1297
1298fn exact_det_int_finite<const D: usize>(m: &Matrix<D>) -> Result<(BigInt, i32), LaError> {
1304 let (det_int, scales) = scaled_det_int_finite(m);
1305 if det_int.sign() == Sign::NoSign {
1306 return Ok((det_int, 0));
1307 }
1308 let total = scales.iter().map(|scale| i64::from(scale.get())).sum();
1313 let minimum = scales.iter().map(|scale| scale.get()).min().unwrap_or(0);
1314 let total_exp = determinant_scale_exp::<D>(total, minimum)?;
1315 Ok((det_int, total_exp))
1316}
1317
1318fn exact_det_finite<const D: usize>(m: &Matrix<D>) -> Result<BigRational, LaError> {
1322 let (det_int, total_exp) = exact_det_int_finite(m)?;
1323 Ok(big_int_exp_to_big_rational(det_int, total_exp))
1324}
1325
1326fn bareiss_solve_finite<const D: usize>(
1335 m: &Matrix<D>,
1336 b: &Vector<D>,
1337) -> Result<[BigRational; D], LaError> {
1338 let matrix = decompose_proven_finite_matrix(m);
1339 let rhs = decompose_proven_finite_vector(b);
1340 bareiss_solve_components(&matrix, &rhs)
1341}
1342
1343fn bareiss_solve_components<const D: usize>(
1359 matrix: &Decomposed<[[Component; D]; D]>,
1360 rhs: &Decomposed<[Component; D]>,
1361) -> Result<[BigRational; D], LaError> {
1362 const MAX_SHARED_SCALE_GAP_BITS: u32 = 64;
1363
1364 let independent_matrix_scale = ScaleExponent::for_decomposed(matrix);
1365 let independent_rhs_scale = ScaleExponent::for_decomposed(rhs);
1366 let scale_gap = independent_matrix_scale
1367 .get()
1368 .abs_diff(independent_rhs_scale.get());
1369 let (matrix_scale, rhs_scale) = if scale_gap <= MAX_SHARED_SCALE_GAP_BITS {
1370 let shared = independent_matrix_scale.min(independent_rhs_scale);
1371 (shared, shared)
1372 } else {
1373 (independent_matrix_scale, independent_rhs_scale)
1374 };
1375 let a = build_big_int_matrix(matrix.components(), matrix_scale);
1376 let rhs = build_big_int_vec(rhs.components(), rhs_scale);
1377 let mut x = solve_big_int(a, rhs)?;
1378
1379 let solution_scale_exp = rhs_scale
1380 .get()
1381 .checked_sub(matrix_scale.get())
1382 .unwrap_or_else(|| unreachable!("finite f64 scale difference cannot overflow i32"));
1383 if solution_scale_exp != 0 {
1384 let solution_scale = big_int_exp_to_big_rational(BigInt::from(1_u8), solution_scale_exp);
1385 for component in &mut x {
1386 *component *= &solution_scale;
1387 }
1388 }
1389
1390 Ok(x)
1391}
1392
1393pub(crate) fn solve_big_int<const D: usize>(
1410 mut a: [[BigInt; D]; D],
1411 mut rhs: [BigInt; D],
1412) -> Result<[BigRational; D], LaError> {
1413 match bareiss_forward_eliminate(&mut a, Some(&mut rhs)) {
1414 BareissResult::Upper { .. } => {}
1415 BareissResult::Singular { pivot_col } => {
1416 cold_path();
1417 return Err(LaError::singular_exact(pivot_col));
1418 }
1419 }
1420
1421 let mut denominator = BigInt::from(1);
1422 for i in (0..D).rev() {
1423 let mut sum = &rhs[i] * &denominator;
1424 for j in (i + 1)..D {
1425 sum -= &a[i][j] * &rhs[j];
1426 }
1427 let (numerator, factor) = BigRational::new(sum, take(&mut a[i][i])).into_raw();
1428 if factor != BigInt::from(1) {
1429 denominator *= &factor;
1430 for solved in &mut rhs[i + 1..] {
1431 *solved *= &factor;
1432 }
1433 }
1434 rhs[i] = numerator;
1435 }
1436
1437 Ok(rhs.map(|numerator| BigRational::new(numerator, denominator.clone())))
1438}
1439
1440#[inline]
1455fn det_exact_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
1456 let (det_int, total_exp) = exact_det_int_finite(m)?;
1457 big_int_exp_to_finite_f64(&det_int, total_exp, None)
1458}
1459
1460#[inline]
1472fn det_exact_rounded_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
1473 let (det_int, total_exp) = exact_det_int_finite(m)?;
1474 big_int_exp_to_rounded_f64(&det_int, total_exp)
1475}
1476
1477#[inline]
1484fn det_sign_exact_finite<const D: usize>(m: &Matrix<D>) -> DeterminantSign {
1485 if let Ok(Some(estimate)) = m.det_direct_with_errbound() {
1486 let det_f64 = estimate.determinant();
1487 let error_bound = estimate.absolute_error_bound();
1488 if det_f64 > error_bound {
1489 return DeterminantSign::Positive;
1490 }
1491 if det_f64 < -error_bound {
1492 return DeterminantSign::Negative;
1493 }
1494 }
1495
1496 cold_path();
1497 let (det_int, _) = scaled_det_int_finite(m);
1498 match det_int.sign() {
1499 Sign::Plus => DeterminantSign::Positive,
1500 Sign::Minus => DeterminantSign::Negative,
1501 Sign::NoSign => DeterminantSign::Zero,
1502 }
1503}
1504
1505impl<const D: usize> Matrix<D> {
1506 #[inline]
1540 pub fn det_exact(&self) -> Result<BigRational, LaError> {
1541 exact_det_finite(self)
1542 }
1543
1544 #[inline]
1578 pub fn det_exact_f64(&self) -> Result<f64, LaError> {
1579 det_exact_f64_finite(self)
1580 }
1581
1582 #[inline]
1622 pub fn det_exact_rounded_f64(&self) -> Result<f64, LaError> {
1623 det_exact_rounded_f64_finite(self)
1624 }
1625
1626 #[inline]
1679 pub fn solve_exact(&self, b: Vector<D>) -> Result<RationalVector<D>, LaError> {
1680 bareiss_solve_finite(self, &b).map(RationalVector::from_canonical_array)
1681 }
1682
1683 #[inline]
1716 pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1717 self.solve_exact(b)?.try_to_f64()
1718 }
1719
1720 #[inline]
1757 pub fn solve_exact_rounded_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1758 self.solve_exact(b)?.to_rounded_f64()
1759 }
1760
1761 #[inline]
1801 pub fn det_sign_exact(&self) -> DeterminantSign {
1802 det_sign_exact_finite(self)
1803 }
1804}
1805
1806#[cfg(test)]
1807mod tests {
1808 use core::assert_matches;
1809 use std::array::from_fn;
1810
1811 use num_traits::{FromPrimitive, Signed};
1812 use pastey::paste;
1813 use proptest::prelude::*;
1814
1815 use super::*;
1816 use crate::{
1817 ArithmeticOperation, DEFAULT_SINGULAR_TOL, NonFiniteLocation, NonFiniteOrigin,
1818 SingularityReason,
1819 };
1820
1821 #[test]
1824 fn det4_matches_bareiss_across_sparse_wide_and_singular_inputs() {
1825 let coefficients = [
1826 [11_i32, 2, -3, 4],
1827 [2, 13, 5, -1],
1828 [3, -2, 17, 6],
1829 [-1, 4, 2, 19],
1830 ];
1831 for shift in [0_u32, 80, 256, 1024] {
1832 for mask in 0..16_u8 {
1833 let mut rows: [[BigInt; 4]; 4] = from_fn(|i| {
1834 from_fn(|j| {
1835 if i == 0 && mask & (1 << j) == 0 {
1836 BigInt::from(0)
1837 } else {
1838 (BigInt::from(coefficients[i][j]) << shift) + BigInt::from(i + j)
1839 }
1840 })
1841 });
1842 for variant in 0..3 {
1843 if variant == 1 {
1844 rows.swap(0, 2);
1845 } else if variant == 2 {
1846 rows[1] = rows[0].clone();
1847 }
1848 let mut eliminated = rows.clone();
1849 let expected = match bareiss_forward_eliminate(&mut eliminated, None) {
1850 BareissResult::Upper { odd_swaps } => {
1851 let det = take(&mut eliminated[3][3]);
1852 if odd_swaps { -det } else { det }
1853 }
1854 BareissResult::Singular { .. } => BigInt::from(0),
1855 };
1856 assert_eq!(
1857 det4_big_int(&rows),
1858 expected,
1859 "shift={shift}, mask={mask}, variant={variant}"
1860 );
1861 }
1862 }
1863 }
1864 }
1865
1866 fn f64_to_big_rational(x: f64) -> BigRational {
1878 BigRational::from_f64(x).expect("test oracle requires finite f64 input")
1879 }
1880
1881 fn assert_unrepresentable<T>(
1882 result: &Result<T, LaError>,
1883 expected_index: Option<usize>,
1884 expected_reason: UnrepresentableReason,
1885 ) {
1886 let Err(error) = result else {
1887 panic!("expected an exact-to-f64 conversion error");
1888 };
1889 assert!(matches!(
1890 *error,
1891 LaError::Unrepresentable { index, reason, .. }
1892 if index == expected_index && reason == expected_reason
1893 ));
1894 }
1895
1896 macro_rules! gen_exact_identity_tests {
1901 ($d:literal) => {
1902 paste! {
1903 #[test]
1904 fn [<exact_identity_paths_ $d d>]() {
1905 let matrix = Matrix::<$d>::identity();
1906 let one = BigRational::from_integer(BigInt::from(1));
1907
1908 assert_eq!(matrix.det_exact().unwrap(), one);
1909 assert_eq!(matrix.det_exact_f64().unwrap().to_bits(), 1.0_f64.to_bits());
1910 assert_eq!(
1911 matrix.det_exact_rounded_f64().unwrap().to_bits(),
1912 1.0_f64.to_bits()
1913 );
1914 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Positive);
1915 }
1916 }
1917 };
1918 }
1919
1920 gen_exact_identity_tests!(2);
1921 gen_exact_identity_tests!(3);
1922 gen_exact_identity_tests!(4);
1923 gen_exact_identity_tests!(5);
1924
1925 macro_rules! gen_det_exact_f64_agrees_with_det_direct {
1928 ($d:literal) => {
1929 paste! {
1930 #[test]
1931 fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
1932 let mut rows = [[0.0f64; $d]; $d];
1935 let mut value = 2.0;
1936 for (i, row) in rows.iter_mut().enumerate() {
1937 row[i] = value;
1938 value *= 2.0;
1939 }
1940 let m = Matrix::<$d>::try_from_rows(rows).unwrap();
1941 let exact = m.det_exact_f64().unwrap();
1942 let direct = m.det_direct().unwrap().unwrap();
1943 assert_eq!(exact.to_bits(), direct.to_bits());
1944 }
1945 }
1946 };
1947 }
1948
1949 gen_det_exact_f64_agrees_with_det_direct!(2);
1950 gen_det_exact_f64_agrees_with_det_direct!(3);
1951 gen_det_exact_f64_agrees_with_det_direct!(4);
1952
1953 #[test]
1954 fn det_sign_exact_d0_is_positive() {
1955 assert_eq!(
1956 Matrix::<0>::zero().det_sign_exact(),
1957 DeterminantSign::Positive
1958 );
1959 }
1960
1961 #[test]
1962 fn det_sign_exact_d1_positive() {
1963 let m = Matrix::<1>::try_from_rows([[42.0]]).unwrap();
1964 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
1965 }
1966
1967 #[test]
1968 fn det_sign_exact_d1_negative() {
1969 let m = Matrix::<1>::try_from_rows([[-3.5]]).unwrap();
1970 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1971 }
1972
1973 #[test]
1974 fn det_sign_exact_d1_zero() {
1975 let m = Matrix::<1>::try_from_rows([[0.0]]).unwrap();
1976 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1977 }
1978
1979 #[test]
1980 fn det_sign_exact_singular_duplicate_rows() {
1981 let m = Matrix::<3>::try_from_rows([
1982 [1.0, 2.0, 3.0],
1983 [4.0, 5.0, 6.0],
1984 [1.0, 2.0, 3.0], ])
1986 .unwrap();
1987 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1988 }
1989
1990 #[test]
1991 fn det_sign_exact_singular_linear_combination() {
1992 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]])
1994 .unwrap();
1995 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1996 }
1997
1998 #[test]
1999 fn det_sign_exact_negative_det_row_swap() {
2000 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]])
2002 .unwrap();
2003 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2004 }
2005
2006 #[test]
2007 fn det_sign_exact_negative_det_known() {
2008 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2010 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2011 }
2012
2013 #[test]
2014 fn det_sign_exact_agrees_with_det_for_spd() {
2015 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]])
2017 .unwrap();
2018 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2019 assert!(m.det().unwrap() > 0.0);
2020 }
2021
2022 #[test]
2029 fn det_sign_exact_near_singular_perturbation() {
2030 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
2032 [1.0 + perturbation, 2.0, 3.0],
2033 [4.0, 5.0, 6.0],
2034 [7.0, 8.0, 9.0],
2035 ])
2036 .unwrap();
2037 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2039 }
2040
2041 #[test]
2045 fn det_sign_exact_fast_filter_positive_4x4() {
2046 let m = Matrix::<4>::try_from_rows([
2047 [2.0, 1.0, 0.0, 0.0],
2048 [1.0, 3.0, 1.0, 0.0],
2049 [0.0, 1.0, 4.0, 1.0],
2050 [0.0, 0.0, 1.0, 5.0],
2051 ])
2052 .unwrap();
2053 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2055 }
2056
2057 #[test]
2058 fn det_sign_exact_fast_filter_negative_4x4() {
2059 let m = Matrix::<4>::try_from_rows([
2061 [1.0, 3.0, 1.0, 0.0],
2062 [2.0, 1.0, 0.0, 0.0],
2063 [0.0, 1.0, 4.0, 1.0],
2064 [0.0, 0.0, 1.0, 5.0],
2065 ])
2066 .unwrap();
2067 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2068 }
2069
2070 #[test]
2071 fn det_sign_exact_subnormal_entries() {
2072 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
2075
2076 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
2077 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2079 }
2080
2081 #[test]
2082 fn det_sign_exact_falls_back_when_subnormal_rounding_reverses_direct_sign() {
2083 let scale = 2.0_f64.powi(-360);
2084 let matrix = Matrix::<3>::try_from_rows([
2085 [-5.0 * scale, 3.0 * scale, 6.0 * scale],
2086 [0.0, -7.0 * scale, -7.0 * scale],
2087 [2.0 * scale, -3.0 * scale, -4.0 * scale],
2088 ])
2089 .unwrap();
2090
2091 assert_eq!(
2092 matrix.det_direct().unwrap().unwrap().to_bits(),
2093 (-f64::from_bits(1)).to_bits()
2094 );
2095 assert_eq!(matrix.det_errbound(), Ok(None));
2096 assert!(matrix.det_exact().unwrap().is_positive());
2097 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Positive);
2098 }
2099
2100 #[test]
2101 fn det_sign_exact_falls_back_for_bit_exact_underflow_counterexample() {
2102 let matrix = Matrix::<3>::try_from_rows([
2103 [
2104 f64::from_bits(9_218_868_437_227_405_311),
2105 f64::from_bits(13_830_554_455_654_793_216),
2106 0.0,
2107 ],
2108 [
2109 f64::from_bits(6_790_500_848_393_242_208),
2110 f64::from_bits(2_184_621_143_747_520_227),
2111 f64::from_bits(2_187_555_472_467_513_745),
2112 ],
2113 [
2114 0.0,
2115 f64::from_bits(2_184_859_204_554_904_434),
2116 f64::from_bits(2_184_762_736_385_916_910),
2117 ],
2118 ])
2119 .unwrap();
2120
2121 assert!(matrix.det_direct().unwrap().unwrap().is_sign_positive());
2122 assert_eq!(matrix.det_errbound(), Ok(None));
2123 assert!(matrix.det_exact().unwrap().is_negative());
2124 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Negative);
2125 }
2126
2127 #[test]
2128 fn det_sign_exact_pivot_needed_5x5() {
2129 let m = Matrix::<5>::try_from_rows([
2132 [0.0, 1.0, 0.0, 0.0, 0.0],
2133 [1.0, 0.0, 0.0, 0.0, 0.0],
2134 [0.0, 0.0, 1.0, 0.0, 0.0],
2135 [0.0, 0.0, 0.0, 1.0, 0.0],
2136 [0.0, 0.0, 0.0, 0.0, 1.0],
2137 ])
2138 .unwrap();
2139 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2140 }
2141
2142 #[test]
2143 fn det_sign_exact_5x5_known() {
2144 let m = Matrix::<5>::try_from_rows([
2146 [0.0, 1.0, 0.0, 0.0, 0.0],
2147 [1.0, 0.0, 0.0, 0.0, 0.0],
2148 [0.0, 0.0, 0.0, 1.0, 0.0],
2149 [0.0, 0.0, 1.0, 0.0, 0.0],
2150 [0.0, 0.0, 0.0, 0.0, 1.0],
2151 ])
2152 .unwrap();
2153 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2155 }
2156
2157 #[test]
2163 fn decompose_f64_zero() {
2164 assert_eq!(decompose_proven_finite_f64(0.0), Component::Zero);
2165 assert_eq!(decompose_proven_finite_f64(-0.0), Component::Zero);
2166 }
2167
2168 #[test]
2169 fn decompose_f64_one() {
2170 assert_eq!(
2171 decompose_proven_finite_f64(1.0),
2172 Component::NonZero {
2173 mantissa: NonZeroU64::new(1).unwrap(),
2174 exponent: 0,
2175 is_negative: false,
2176 }
2177 );
2178 }
2179
2180 #[test]
2181 fn decompose_f64_negative() {
2182 assert_eq!(
2183 decompose_proven_finite_f64(-3.5),
2184 Component::NonZero {
2185 mantissa: NonZeroU64::new(7).unwrap(),
2186 exponent: -1,
2187 is_negative: true,
2188 }
2189 );
2190 }
2191
2192 #[test]
2193 fn decompose_f64_subnormal() {
2194 let tiny = f64::from_bits(1);
2195 assert!(tiny.is_subnormal());
2196 assert_eq!(
2197 decompose_proven_finite_f64(tiny),
2198 Component::NonZero {
2199 mantissa: NonZeroU64::new(1).unwrap(),
2200 exponent: -1074,
2201 is_negative: false,
2202 }
2203 );
2204 }
2205
2206 #[test]
2207 fn decompose_f64_normalizes_mixed_subnormal_mantissa() {
2208 let value = f64::from_bits(0x000C_0000_0000_0000);
2209 assert!(value.is_subnormal());
2210 assert_eq!(
2211 decompose_proven_finite_f64(value),
2212 Component::NonZero {
2213 mantissa: NonZeroU64::new(3).unwrap(),
2214 exponent: -1024,
2215 is_negative: false,
2216 }
2217 );
2218 }
2219
2220 #[test]
2221 fn decompose_f64_power_of_two() {
2222 assert_eq!(
2223 decompose_proven_finite_f64(1024.0),
2224 Component::NonZero {
2225 mantissa: NonZeroU64::new(1).unwrap(),
2226 exponent: 10,
2227 is_negative: false,
2228 }
2229 );
2230 }
2231
2232 proptest! {
2233 #[test]
2234 fn finite_f64_round_trips_through_exact_decomposition(bits in any::<u64>()) {
2235 let value = f64::from_bits(bits);
2236 prop_assume!(value.is_finite());
2237
2238 let exact = f64_to_big_rational(value);
2239 let decomposed = match decompose_proven_finite_f64(value) {
2240 Component::Zero => BigRational::from_integer(BigInt::from(0)),
2241 Component::NonZero { mantissa, exponent, is_negative } => {
2242 prop_assert_eq!(mantissa.get() & 1, 1);
2243 prop_assert!((-1074..=1023).contains(&exponent));
2244 let numerator = if is_negative {
2245 -BigInt::from(mantissa.get())
2246 } else {
2247 BigInt::from(mantissa.get())
2248 };
2249 if exponent >= 0 {
2250 BigRational::from_integer(numerator << exponent.cast_unsigned())
2251 } else {
2252 BigRational::new(numerator, BigInt::from(1) << exponent.unsigned_abs())
2253 }
2254 }
2255 };
2256 prop_assert_eq!(&decomposed, &exact);
2257 let reconstructed = exact_rational_to_finite_f64(&exact, None);
2258
2259 prop_assert_eq!(reconstructed, Ok(value));
2260 }
2261 }
2262
2263 #[test]
2264 fn wide_low_exponent_value_reports_non_finite_rounded_result() {
2265 let value = (BigInt::from(1_u8) << 2099_u32) - BigInt::from(1_u8);
2267 let result = big_int_exp_to_finite_f64(&value, -1075, None);
2268
2269 assert!(!result.as_ref().unwrap_err().requires_rounding());
2270 assert_unrepresentable(&result, None, UnrepresentableReason::NotFinite);
2271 }
2272
2273 #[test]
2274 fn direct_big_int_rounding_handles_extreme_negative_exponent_without_large_denominator() {
2275 let positive = big_int_exp_ref_to_rounded_f64(&BigInt::from(1_u8), i32::MIN, None).unwrap();
2276 let negative =
2277 big_int_exp_ref_to_rounded_f64(&BigInt::from(-1_i8), i32::MIN, None).unwrap();
2278
2279 assert_eq!(positive.to_bits(), 0.0_f64.to_bits());
2280 assert_eq!(negative.to_bits(), (-0.0_f64).to_bits());
2281 }
2282
2283 proptest! {
2284 #[test]
2285 fn direct_big_int_rounding_matches_rational_oracle(
2286 value in any::<i128>(),
2287 exp in -1200_i32..=1200_i32,
2288 ) {
2289 let value = BigInt::from(value);
2290 let direct = big_int_exp_ref_to_rounded_f64(&value, exp, None);
2291 let exact = big_int_exp_to_big_rational(value, exp);
2292 let oracle = exact_rational_to_rounded_f64(&exact, None);
2293
2294 prop_assert_eq!(direct.map(f64::to_bits), oracle.map(f64::to_bits));
2295 }
2296 }
2297
2298 #[test]
2299 fn component_to_big_int_distinguishes_zero_from_nonzero_mantissa() {
2300 let baseline = Component::NonZero {
2301 mantissa: NonZeroU64::new(1).unwrap(),
2302 exponent: 1,
2303 is_negative: false,
2304 };
2305 let positive = Component::NonZero {
2306 mantissa: NonZeroU64::new(3).unwrap(),
2307 exponent: 4,
2308 is_negative: false,
2309 };
2310 let negative = Component::NonZero {
2311 mantissa: NonZeroU64::new(5).unwrap(),
2312 exponent: 3,
2313 is_negative: true,
2314 };
2315
2316 let decomposed =
2317 Decomposed::from_vector_components([Component::Zero, baseline, positive, negative]);
2318 let scale = ScaleExponent::for_decomposed(&decomposed);
2319
2320 assert_eq!(
2321 component_to_big_int(Component::Zero, scale),
2322 BigInt::from(0)
2323 );
2324 assert_eq!(component_to_big_int(positive, scale), BigInt::from(24));
2325 assert_eq!(component_to_big_int(negative, scale), BigInt::from(-20));
2326 }
2327
2328 #[test]
2329 fn decomposed_all_zero_uses_no_sentinel_exponent() {
2330 let decomposed = decompose_proven_finite_matrix(&Matrix::<2>::zero());
2331 assert_eq!(decomposed.min_exponent(), None);
2332
2333 let scale = ScaleExponent::for_decomposed(&decomposed);
2334 assert_eq!(scale, ScaleExponent::ZERO);
2335 assert_eq!(scale.get(), 0);
2336 assert_eq!(
2337 build_big_int_matrix(decomposed.components(), scale),
2338 [
2339 [BigInt::from(0), BigInt::from(0)],
2340 [BigInt::from(0), BigInt::from(0)]
2341 ]
2342 );
2343 }
2344
2345 #[test]
2346 fn matrix_and_rhs_scales_are_derived_independently() {
2347 let tiny = f64::from_bits(1);
2348 let matrix = Matrix::<2>::try_from_rows([[f64::MAX, 0.0], [0.0, 1.0]]).unwrap();
2349 let rhs = Vector::<2>::try_new([tiny, 0.0]).unwrap();
2350 let matrix = decompose_proven_finite_matrix(&matrix);
2351 let rhs = decompose_proven_finite_vector(&rhs);
2352
2353 assert_eq!(matrix.min_exponent(), Some(0));
2354 assert_eq!(rhs.min_exponent(), Some(-1074));
2355
2356 let matrix_scale = ScaleExponent::for_decomposed(&matrix);
2357 let rhs_scale = ScaleExponent::for_decomposed(&rhs);
2358 assert_eq!(matrix_scale.get(), 0);
2359 assert_eq!(matrix_scale.shift_for(0), 0);
2360 assert_eq!(rhs_scale.get(), -1074);
2361 assert_eq!(rhs_scale.shift_for(-1074), 0);
2362 }
2363
2364 proptest! {
2365 #[test]
2366 fn derived_scale_yields_nonnegative_shifts(bits in any::<[u64; 4]>()) {
2367 let values = bits.map(f64::from_bits);
2368 prop_assume!(values.iter().all(|value| value.is_finite()));
2369 let matrix = Matrix::<2>::try_from_rows([
2370 [values[0], values[1]],
2371 [values[2], values[3]],
2372 ]).unwrap();
2373 let decomposed = decompose_proven_finite_matrix(&matrix);
2374 let scale = ScaleExponent::for_decomposed(&decomposed);
2375
2376 for component in decomposed.components().iter().flatten() {
2377 if let Some(exponent) = component.exponent() {
2378 prop_assert!(exponent >= scale.get());
2379 prop_assert_eq!(
2380 scale.shift_for(exponent),
2381 u32::try_from(exponent - scale.get()).unwrap(),
2382 );
2383 }
2384 }
2385 }
2386 }
2387
2388 #[test]
2389 fn determinant_scale_exp_accepts_reconstructed_row_sum() {
2390 assert_eq!(determinant_scale_exp::<4>(-4296, -1074), Ok(-4296));
2391 assert_eq!(determinant_scale_exp::<4>(0, -1074), Ok(0));
2392 assert_eq!(determinant_scale_exp::<0>(0, 0), Ok(0));
2393 }
2394
2395 #[test]
2396 fn determinant_scale_exp_rejects_positive_overflow() {
2397 assert_eq!(
2398 determinant_scale_exp::<3_000_000>(i64::from(i32::MAX) + 1, 1023),
2399 Err(LaError::DeterminantScaleOverflow {
2400 dim: 3_000_000,
2401 min_exponent: 1023,
2402 })
2403 );
2404 }
2405
2406 #[test]
2407 fn determinant_scale_exp_rejects_negative_overflow() {
2408 assert_eq!(
2409 determinant_scale_exp::<3_000_000>(-1074 * 3_000_000_i64, -1074),
2410 Err(LaError::DeterminantScaleOverflow {
2411 dim: 3_000_000,
2412 min_exponent: -1074,
2413 })
2414 );
2415 }
2416
2417 #[test]
2418 fn negative_exponent_from_magnitude_covers_i32_domain_boundaries() {
2419 assert_eq!(negative_exponent_from_magnitude(0), 0);
2420 assert_eq!(negative_exponent_from_magnitude(1), -1);
2421 assert_eq!(
2422 negative_exponent_from_magnitude(i32::MAX.cast_unsigned().into()),
2423 -i32::MAX
2424 );
2425 assert_eq!(
2426 negative_exponent_from_magnitude(i32::MIN.unsigned_abs().into()),
2427 i32::MIN
2428 );
2429 }
2430
2431 #[test]
2432 #[should_panic(expected = "negative exponent magnitude exceeds the i32 domain")]
2433 fn negative_exponent_from_magnitude_rejects_values_above_i32_domain() {
2434 let _ = negative_exponent_from_magnitude(u64::from(i32::MIN.unsigned_abs()) + 1);
2435 }
2436
2437 #[test]
2442 fn exact_det_int_d0() {
2443 let m = Matrix::<0>::zero();
2444 let (det, exp) = exact_det_int_finite(&m).unwrap();
2445 assert_eq!(det, BigInt::from(1));
2446 assert_eq!(exp, 0);
2447 }
2448
2449 #[test]
2455 fn exact_det_int_d1_cases() {
2456 let cases: &[(f64, i64, i32)] = &[
2457 (7.0, 7, 0), (0.0, 0, 0), (-3.5, -7, -1), (0.5, 1, -1), ];
2463 for &(input, expected_det_int, expected_exp) in cases {
2464 let m = Matrix::<1>::try_from_rows([[input]]).unwrap();
2465 let (det, exp) = exact_det_int_finite(&m).unwrap();
2466 assert_eq!(
2467 det,
2468 BigInt::from(expected_det_int),
2469 "det_int for input={input}"
2470 );
2471 assert_eq!(exp, expected_exp, "exp for input={input}");
2472 }
2473 }
2474
2475 #[test]
2476 fn exact_det_int_d2_known() {
2477 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2479 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2480 let det = big_int_exp_to_big_rational(det_int, total_exp);
2482 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2483 }
2484
2485 #[test]
2486 fn exact_det_int_all_zeros() {
2487 let m = Matrix::<3>::zero();
2488 let (det, _) = exact_det_int_finite(&m).unwrap();
2489 assert_eq!(det, BigInt::from(0));
2490 }
2491
2492 #[test]
2493 fn exact_det_int_fractional_entries() {
2494 let m = Matrix::<2>::try_from_rows([[0.5, 0.25], [1.0, 1.0]]).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::new(BigInt::from(1), BigInt::from(4)));
2500 }
2501
2502 #[test]
2503 fn exact_det_int_d3_direct_expansion_handles_zero_diagonal() {
2504 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]])
2506 .unwrap();
2507 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2508 let det = big_int_exp_to_big_rational(det_int, total_exp);
2509 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2510 }
2511
2512 #[test]
2517 fn big_int_exp_to_big_rational_zero() {
2518 let r = big_int_exp_to_big_rational(BigInt::from(0), -50);
2519 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
2520 }
2521
2522 #[test]
2523 fn big_int_exp_to_big_rational_positive_exp() {
2524 let r = big_int_exp_to_big_rational(BigInt::from(3), 2);
2526 assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
2527 }
2528
2529 #[test]
2530 fn big_int_exp_to_big_rational_negative_exp_reduced() {
2531 let r = big_int_exp_to_big_rational(BigInt::from(6), -2);
2533 assert_eq!(*r.numer(), BigInt::from(3));
2534 assert_eq!(*r.denom(), BigInt::from(2));
2535 }
2536
2537 #[test]
2538 fn big_int_exp_to_big_rational_negative_exp_reduces_to_integer() {
2539 let r = big_int_exp_to_big_rational(BigInt::from(8), -3);
2541 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
2542 }
2543
2544 #[test]
2545 fn big_int_exp_to_big_rational_negative_exp_already_odd() {
2546 let r = big_int_exp_to_big_rational(BigInt::from(3), -2);
2548 assert_eq!(*r.numer(), BigInt::from(3));
2549 assert_eq!(*r.denom(), BigInt::from(4));
2550 }
2551
2552 #[test]
2553 fn big_int_exp_to_big_rational_negative_value() {
2554 let r = big_int_exp_to_big_rational(BigInt::from(-5), 1);
2556 assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
2557 }
2558
2559 #[test]
2560 fn big_int_exp_to_big_rational_negative_value_with_denominator() {
2561 let r = big_int_exp_to_big_rational(BigInt::from(-3), -2);
2563 assert_eq!(*r.numer(), BigInt::from(-3));
2564 assert_eq!(*r.denom(), BigInt::from(4));
2565 }
2566
2567 #[test]
2572 fn det_exact_d1_returns_entry() {
2573 let det = Matrix::<1>::try_from_rows([[7.0]])
2574 .unwrap()
2575 .det_exact()
2576 .unwrap();
2577 assert_eq!(det, f64_to_big_rational(7.0));
2578 }
2579
2580 #[test]
2581 fn det_exact_d3_direct_expansion_handles_zero_diagonal() {
2582 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]])
2584 .unwrap();
2585 let det = m.det_exact().unwrap();
2586 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2588 }
2589
2590 #[test]
2591 fn det_exact_d3_singular_zero_column_returns_zero() {
2592 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]])
2594 .unwrap();
2595 let det = m.det_exact().unwrap();
2596 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2597 }
2598
2599 #[test]
2600 fn det_sign_exact_overflow_determinant_finite_entries() {
2601 let big = f64::MAX / 2.0;
2605 assert!(big.is_finite());
2606 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2607 .unwrap();
2608 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2610 }
2611
2612 #[test]
2617 fn det_exact_d0_is_one() {
2618 let det = Matrix::<0>::zero().det_exact().unwrap();
2619 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
2620 }
2621
2622 #[test]
2623 fn det_exact_known_2x2() {
2624 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2626 let det = m.det_exact().unwrap();
2627 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2628 }
2629
2630 #[test]
2631 fn det_exact_known_dense_4x4() {
2632 let m = Matrix::<4>::try_from_rows([
2633 [4.0, 1.0, 3.0, 2.0],
2634 [0.0, 5.0, 2.0, 1.0],
2635 [7.0, 2.0, 6.0, 3.0],
2636 [1.0, 8.0, 4.0, 9.0],
2637 ])
2638 .unwrap();
2639
2640 assert_eq!(
2641 m.det_exact(),
2642 Ok(BigRational::from_integer(BigInt::from(92)))
2643 );
2644 }
2645
2646 #[test]
2647 fn det_exact_singular_returns_zero() {
2648 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]])
2650 .unwrap();
2651 let det = m.det_exact().unwrap();
2652 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2653 }
2654
2655 #[test]
2656 fn det_exact_near_singular_perturbation() {
2657 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
2660 [1.0 + perturbation, 2.0, 3.0],
2661 [4.0, 5.0, 6.0],
2662 [7.0, 8.0, 9.0],
2663 ])
2664 .unwrap();
2665 let det = m.det_exact().unwrap();
2666 let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
2668 assert_eq!(det, expected);
2669 }
2670
2671 #[test]
2672 fn det_exact_5x5_permutation() {
2673 let m = Matrix::<5>::try_from_rows([
2675 [0.0, 1.0, 0.0, 0.0, 0.0],
2676 [1.0, 0.0, 0.0, 0.0, 0.0],
2677 [0.0, 0.0, 1.0, 0.0, 0.0],
2678 [0.0, 0.0, 0.0, 1.0, 0.0],
2679 [0.0, 0.0, 0.0, 0.0, 1.0],
2680 ])
2681 .unwrap();
2682 let det = m.det_exact().unwrap();
2683 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2684 }
2685
2686 #[test]
2691 fn det_exact_f64_known_2x2() {
2692 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2693 let det = m.det_exact_f64().unwrap();
2694 assert!((det - (-2.0)).abs() <= f64::EPSILON);
2695 }
2696
2697 #[test]
2698 fn det_exact_f64_overflow_returns_err() {
2699 let big = f64::MAX / 2.0;
2701 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2702 .unwrap();
2703 assert_unrepresentable(&m.det_exact_f64(), None, UnrepresentableReason::NotFinite);
2705 }
2706
2707 #[test]
2708 fn det_exact_rounded_f64_overflow_returns_err() {
2709 let big = f64::MAX / 2.0;
2710 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2711 .unwrap();
2712
2713 assert_unrepresentable(
2714 &m.det_exact_rounded_f64(),
2715 None,
2716 UnrepresentableReason::NotFinite,
2717 );
2718 }
2719
2720 #[test]
2721 fn det_exact_f64_underflow_returns_err_for_nonzero_exact_result() {
2722 let tiny = f64::from_bits(1);
2723 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
2724
2725 assert!(m.det_exact().unwrap().is_positive());
2726 assert_unrepresentable(
2727 &m.det_exact_f64(),
2728 None,
2729 UnrepresentableReason::RequiresRounding,
2730 );
2731 }
2732
2733 #[test]
2734 fn det_exact_f64_rejects_inexact_rounding() {
2735 let m = Matrix::<2>::try_from_rows([[1.0 + f64::EPSILON, 0.0], [0.0, 1.0 - f64::EPSILON]])
2736 .unwrap();
2737
2738 assert_eq!(
2739 m.det_exact(),
2740 Ok(BigRational::new(
2741 (BigInt::from(1_u128) << 104_u32) - BigInt::from(1),
2742 BigInt::from(1_u128 << 104),
2743 ))
2744 );
2745 assert_unrepresentable(
2746 &m.det_exact_f64(),
2747 None,
2748 UnrepresentableReason::RequiresRounding,
2749 );
2750 }
2751
2752 #[test]
2753 fn det_exact_f64_accepts_max_finite_binary64() {
2754 let m = Matrix::<1>::try_from_rows([[f64::MAX]]).unwrap();
2755
2756 assert_eq!(m.det_exact_f64().unwrap().to_bits(), f64::MAX.to_bits());
2757 }
2758
2759 fn arbitrary_rhs<const D: usize>() -> Vector<D> {
2765 let values = [1.0, -2.5, 3.0, 0.25, -4.0];
2766 let mut arr = [0.0f64; D];
2767 for (dst, src) in arr.iter_mut().zip(values.iter()) {
2768 *dst = *src;
2769 }
2770 Vector::<D>::new(arr)
2771 }
2772
2773 macro_rules! gen_solve_exact_tests {
2774 ($d:literal) => {
2775 paste! {
2776 #[test]
2777 fn [<solve_exact_identity_paths_ $d d>]() {
2778 let a = Matrix::<$d>::identity();
2779 let b = arbitrary_rhs::<$d>();
2780 let exact = a.solve_exact(b).unwrap();
2781 let strict_f64 = a.solve_exact_f64(b).unwrap().into_array();
2782
2783 for i in 0..$d {
2784 assert_eq!(exact.as_array()[i], f64_to_big_rational(b.as_array()[i]));
2785 assert_eq!(strict_f64[i].to_bits(), b.as_array()[i].to_bits());
2786 }
2787 }
2788
2789 #[test]
2790 fn [<solve_exact_singular_ $d d>]() {
2791 let a = Matrix::<$d>::zero();
2793 let b = arbitrary_rhs::<$d>();
2794 assert_matches!(
2795 a.solve_exact(b),
2796 Err(LaError::Singular {
2797 pivot_col: 0,
2798 reason: SingularityReason::Exact,
2799 ..
2800 })
2801 );
2802 }
2803 }
2804 };
2805 }
2806
2807 gen_solve_exact_tests!(2);
2808 gen_solve_exact_tests!(3);
2809 gen_solve_exact_tests!(4);
2810 gen_solve_exact_tests!(5);
2811
2812 fn check_integer_solve_canonical_components<const D: usize>() {
2816 let denominators = [2, 3, 5, 7, 11, 13, 17, 19];
2817 let numerators: [BigInt; D] = from_fn(|col| {
2818 let value = BigInt::from(col);
2819 if col.is_multiple_of(2) { value } else { -value }
2820 });
2821 let expected: [BigRational; D] =
2822 from_fn(|col| BigRational::new(numerators[col].clone(), denominators[col].into()));
2823 for bits in [0, 137] {
2824 let row_scales: [BigInt; D] = from_fn(|row| {
2825 let magnitude = if bits == 0 {
2826 BigInt::from(1)
2827 } else {
2828 (BigInt::from(1) << (bits + 17 * row)) + 1
2829 };
2830 if row.is_multiple_of(2) {
2831 -magnitude
2832 } else {
2833 magnitude
2834 }
2835 });
2836 let coefficients: [[BigInt; D]; D] =
2839 from_fn(|row| from_fn(|col| BigInt::from(if row == col { D + 1 } else { 1 })));
2840 let mut rows: [[BigInt; D]; D] = from_fn(|row| {
2841 from_fn(|col| &row_scales[row] * &coefficients[row][col] * denominators[col])
2842 });
2843 let mut rhs: [BigInt; D] = from_fn(|row| {
2844 &row_scales[row]
2845 * coefficients[row]
2846 .iter()
2847 .zip(&numerators)
2848 .map(|(a, n)| a * n)
2849 .sum::<BigInt>()
2850 });
2851 for _ in 0..D.max(1) {
2852 let actual = solve_big_int(rows.clone(), rhs.clone()).unwrap();
2853 for (actual, expected) in actual.iter().zip(&expected) {
2854 assert_eq!(actual.numer(), expected.numer());
2855 assert_eq!(actual.denom(), expected.denom());
2856 }
2857 rows.rotate_left(usize::from(D > 0));
2858 rhs.rotate_left(usize::from(D > 0));
2859 }
2860 }
2861 }
2862
2863 macro_rules! gen_integer_solve_canonical_tests {
2864 ($d:literal) => {
2865 paste! {
2866 #[test]
2867 fn [<integer_solve_canonical_components_ $d d>]() {
2868 check_integer_solve_canonical_components::<$d>();
2869 }
2870 }
2871 };
2872 }
2873
2874 gen_integer_solve_canonical_tests!(0);
2875 gen_integer_solve_canonical_tests!(1);
2876 gen_integer_solve_canonical_tests!(2);
2877 gen_integer_solve_canonical_tests!(3);
2878 gen_integer_solve_canonical_tests!(4);
2879 gen_integer_solve_canonical_tests!(5);
2880 gen_integer_solve_canonical_tests!(6);
2881 gen_integer_solve_canonical_tests!(7);
2882 gen_integer_solve_canonical_tests!(8);
2883
2884 macro_rules! gen_solve_exact_f64_agrees_with_lu {
2887 ($d:literal) => {
2888 paste! {
2889 #[test]
2890 fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
2891 let mut rows = [[0.0f64; $d]; $d];
2895 for r in 0..$d {
2896 for c in 0..$d {
2897 rows[r][c] = if r == c {
2898 f64::from($d) + 1.0
2899 } else {
2900 1.0
2901 };
2902 }
2903 }
2904 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2905 let x_true = {
2906 let mut arr = [0.0f64; $d];
2907 for (dst, src) in arr.iter_mut().zip([1.0, -2.0, 3.0, -4.0, 5.0]) {
2908 *dst = src;
2909 }
2910 arr
2911 };
2912 let mut b_arr = [0.0f64; $d];
2913 for i in 0..$d {
2914 let mut sum = 0.0;
2915 for j in 0..$d {
2916 sum = rows[i][j].mul_add(x_true[j], sum);
2917 }
2918 b_arr[i] = sum;
2919 }
2920 let b = Vector::<$d>::new(b_arr);
2921 let exact = a.solve_exact_f64(b).unwrap().into_array();
2922 let lu_sol = a.lu(DEFAULT_SINGULAR_TOL).unwrap()
2923 .solve(b).unwrap().into_array();
2924 for i in 0..$d {
2925 assert_eq!(exact[i].to_bits(), x_true[i].to_bits());
2926 let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
2927 assert!((exact[i] - lu_sol[i]).abs() <= eps);
2928 }
2929 }
2930 }
2931 };
2932 }
2933
2934 gen_solve_exact_f64_agrees_with_lu!(2);
2935 gen_solve_exact_f64_agrees_with_lu!(3);
2936 gen_solve_exact_f64_agrees_with_lu!(4);
2937 gen_solve_exact_f64_agrees_with_lu!(5);
2938
2939 macro_rules! gen_solve_exact_roundtrip_tests {
2945 ($d:literal) => {
2946 paste! {
2947 #[test]
2948 #[expect(
2949 clippy::cast_precision_loss,
2950 reason = "dimensions and indices are at most five and exactly representable as f64"
2951 )]
2952 fn [<solve_exact_roundtrip_ $d d>]() {
2953 let mut rows = [[0.0f64; $d]; $d];
2956 for r in 0..$d {
2957 for c in 0..$d {
2958 rows[r][c] = if r == c {
2959 f64::from($d) + 1.0
2960 } else {
2961 1.0
2962 };
2963 }
2964 }
2965 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2966
2967 let mut x0 = [0.0f64; $d];
2969 for i in 0..$d {
2970 x0[i] = (i + 1) as f64;
2971 }
2972
2973 let mut b_arr = [0.0f64; $d];
2976 for r in 0..$d {
2977 let mut sum = 0.0_f64;
2978 for c in 0..$d {
2979 sum = rows[r][c].mul_add(x0[c], sum);
2980 }
2981 b_arr[r] = sum;
2982 }
2983 let b = Vector::<$d>::new(b_arr);
2984
2985 let x = a.solve_exact(b).unwrap();
2986 for i in 0..$d {
2987 assert_eq!(x.as_array()[i], f64_to_big_rational(x0[i]));
2988 }
2989 }
2990 }
2991 };
2992 }
2993
2994 gen_solve_exact_roundtrip_tests!(2);
2995 gen_solve_exact_roundtrip_tests!(3);
2996 gen_solve_exact_roundtrip_tests!(4);
2997 gen_solve_exact_roundtrip_tests!(5);
2998
2999 #[test]
3004 fn solve_exact_d0_returns_empty() {
3005 let a = Matrix::<0>::zero();
3006 let b = Vector::<0>::zero();
3007 let x = a.solve_exact(b).unwrap();
3008 assert_eq!(x.as_array().as_slice(), []);
3009 }
3010
3011 #[test]
3012 fn solve_exact_known_2x2() {
3013 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
3015 let b = Vector::<2>::new([5.0, 11.0]);
3016 let x = a.solve_exact(b).unwrap();
3017 assert_eq!(x.as_array()[0], BigRational::from_integer(BigInt::from(1)));
3018 assert_eq!(x.as_array()[1], BigRational::from_integer(BigInt::from(2)));
3019 }
3020
3021 #[test]
3022 fn solve_exact_pivoting_needed() {
3023 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]])
3025 .unwrap();
3026 let b = Vector::<3>::new([2.0, 3.0, 4.0]);
3027 let x = a.solve_exact(b).unwrap();
3028 assert_eq!(x.as_array()[0], f64_to_big_rational(3.0));
3030 assert_eq!(x.as_array()[1], f64_to_big_rational(2.0));
3031 assert_eq!(x.as_array()[2], f64_to_big_rational(4.0));
3032 }
3033
3034 #[test]
3035 fn solve_exact_fractional_result() {
3036 let a = Matrix::<2>::try_from_rows([[2.0, 1.0], [1.0, 3.0]]).unwrap();
3038 let b = Vector::<2>::new([1.0, 1.0]);
3039 let x = a.solve_exact(b).unwrap();
3040 assert_eq!(
3041 x.as_array()[0],
3042 BigRational::new(BigInt::from(2), BigInt::from(5))
3043 );
3044 assert_eq!(
3045 x.as_array()[1],
3046 BigRational::new(BigInt::from(1), BigInt::from(5))
3047 );
3048 }
3049
3050 #[test]
3051 fn solve_exact_singular_duplicate_rows() {
3052 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]])
3053 .unwrap();
3054 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
3055 assert_matches!(
3056 a.solve_exact(b),
3057 Err(LaError::Singular {
3058 reason: SingularityReason::Exact,
3059 ..
3060 })
3061 );
3062 }
3063
3064 #[test]
3065 fn solve_exact_5x5_permutation() {
3066 let a = Matrix::<5>::try_from_rows([
3068 [0.0, 1.0, 0.0, 0.0, 0.0],
3069 [1.0, 0.0, 0.0, 0.0, 0.0],
3070 [0.0, 0.0, 1.0, 0.0, 0.0],
3071 [0.0, 0.0, 0.0, 1.0, 0.0],
3072 [0.0, 0.0, 0.0, 0.0, 1.0],
3073 ])
3074 .unwrap();
3075 let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
3076 let x = a.solve_exact(b).unwrap();
3077 assert_eq!(x.as_array()[0], f64_to_big_rational(20.0));
3078 assert_eq!(x.as_array()[1], f64_to_big_rational(10.0));
3079 assert_eq!(x.as_array()[2], f64_to_big_rational(30.0));
3080 assert_eq!(x.as_array()[3], f64_to_big_rational(40.0));
3081 assert_eq!(x.as_array()[4], f64_to_big_rational(50.0));
3082 }
3083
3084 macro_rules! gen_solve_exact_large_finite_entries_tests {
3090 ($d:literal) => {
3091 paste! {
3092 #[test]
3093 fn [<solve_exact_large_finite_entries_ $d d>]() {
3094 let big = f64::MAX / 2.0;
3095 assert!(big.is_finite());
3096 let mut rows = [[0.0f64; $d]; $d];
3098 for i in 0..$d {
3099 rows[i][i] = big;
3100 }
3101 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3102 let mut b_arr = [big; $d];
3104 b_arr[$d - 1] = 0.0;
3105 let b = Vector::<$d>::new(b_arr);
3106 let x = a.solve_exact(b).unwrap();
3107 for i in 0..($d - 1) {
3108 assert_eq!(x.as_array()[i], BigRational::from_integer(BigInt::from(1)));
3109 }
3110 assert_eq!(x.as_array()[$d - 1], BigRational::from_integer(BigInt::from(0)));
3111 }
3112 }
3113 };
3114 }
3115
3116 gen_solve_exact_large_finite_entries_tests!(2);
3117 gen_solve_exact_large_finite_entries_tests!(3);
3118 gen_solve_exact_large_finite_entries_tests!(4);
3119 gen_solve_exact_large_finite_entries_tests!(5);
3120
3121 macro_rules! gen_solve_exact_mixed_magnitude_entries_tests {
3128 ($d:literal) => {
3129 paste! {
3130 #[test]
3131 fn [<solve_exact_mixed_magnitude_entries_ $d d>]() {
3132 let tiny = f64::MIN_POSITIVE; let huge = 1.0e100_f64;
3134 let mut rows = [[0.0f64; $d]; $d];
3136 let mut b_arr = [0.0f64; $d];
3137 for i in 0..$d {
3138 let val = if i % 2 == 0 { huge } else { tiny };
3139 rows[i][i] = val;
3140 b_arr[i] = val;
3141 }
3142 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3143 let b = Vector::<$d>::new(b_arr);
3144 let x = a.solve_exact(b).unwrap();
3145 for i in 0..$d {
3146 assert_eq!(x.as_array()[i], BigRational::from_integer(BigInt::from(1)));
3147 }
3148 }
3149 }
3150 };
3151 }
3152
3153 gen_solve_exact_mixed_magnitude_entries_tests!(2);
3154 gen_solve_exact_mixed_magnitude_entries_tests!(3);
3155 gen_solve_exact_mixed_magnitude_entries_tests!(4);
3156 gen_solve_exact_mixed_magnitude_entries_tests!(5);
3157
3158 #[test]
3159 fn solve_exact_restores_independent_matrix_and_rhs_scales() {
3160 let large = 2.0_f64.powi(500);
3161 let tiny = 2.0_f64.powi(-1000);
3162
3163 let large_matrix = Matrix::<2>::try_from_rows([[large, 0.0], [0.0, large]]).unwrap();
3164 let tiny_rhs = Vector::<2>::new([tiny, -2.0 * tiny]);
3165 let small_solution = large_matrix.solve_exact(tiny_rhs).unwrap();
3166 assert_eq!(
3167 small_solution.as_array()[0],
3168 BigRational::new(BigInt::from(1_u8), BigInt::from(1_u8) << 1500_u32)
3169 );
3170 assert_eq!(
3171 small_solution.as_array()[1],
3172 BigRational::new(BigInt::from(-1_i8), BigInt::from(1_u8) << 1499_u32)
3173 );
3174
3175 let tiny_matrix = Matrix::<1>::try_from_rows([[tiny]]).unwrap();
3176 let large_rhs = Vector::<1>::new([large]);
3177 let large_solution = tiny_matrix.solve_exact(large_rhs).unwrap();
3178 assert_eq!(
3179 large_solution.as_array()[0],
3180 BigRational::from_integer(BigInt::from(1_u8) << 1500_u32)
3181 );
3182 }
3183
3184 macro_rules! gen_solve_exact_subnormal_rhs_tests {
3190 ($d:literal) => {
3191 paste! {
3192 #[test]
3193 #[expect(
3194 clippy::cast_precision_loss,
3195 reason = "indices are at most five and exactly representable as f64"
3196 )]
3197 fn [<solve_exact_subnormal_rhs_ $d d>]() {
3198 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3200 let a = Matrix::<$d>::identity();
3201 let mut b_arr = [0.0f64; $d];
3203 for i in 0..$d {
3204 b_arr[i] = (i + 1) as f64 * tiny;
3205 assert!(b_arr[i].is_subnormal());
3206 }
3207 let b = Vector::<$d>::new(b_arr);
3208 let x = a.solve_exact(b).unwrap();
3209 for i in 0..$d {
3210 assert_eq!(x.as_array()[i], f64_to_big_rational((i + 1) as f64 * tiny));
3211 }
3212 }
3213 }
3214 };
3215 }
3216
3217 gen_solve_exact_subnormal_rhs_tests!(2);
3218 gen_solve_exact_subnormal_rhs_tests!(3);
3219 gen_solve_exact_subnormal_rhs_tests!(4);
3220 gen_solve_exact_subnormal_rhs_tests!(5);
3221
3222 macro_rules! gen_solve_exact_pivot_swap_fractional_tests {
3232 ($d:literal) => {
3233 paste! {
3234 #[test]
3235 #[expect(
3236 clippy::cast_precision_loss,
3237 reason = "indices and test offsets are small integers exactly representable as f64"
3238 )]
3239 fn [<solve_exact_pivot_swap_with_fractional_result_ $d d>]() {
3240 let mut rows = [[0.0f64; $d]; $d];
3243 rows[0][1] = 1.0;
3244 rows[1][0] = 2.0;
3245 rows[1][1] = 1.0;
3246 for (i, row) in rows.iter_mut().enumerate().skip(2) {
3248 row[i] = 1.0;
3249 }
3250 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3251 let mut b_arr = [0.0f64; $d];
3254 b_arr[0] = 3.0;
3255 b_arr[1] = 4.0;
3256 for (i, value) in b_arr.iter_mut().enumerate().skip(2) {
3257 *value = (i + 10) as f64;
3258 }
3259 let b = Vector::<$d>::new(b_arr);
3260 let x = a.solve_exact(b).unwrap();
3261 assert_eq!(x.as_array()[0], BigRational::new(BigInt::from(1), BigInt::from(2)));
3262 assert_eq!(x.as_array()[1], BigRational::from_integer(BigInt::from(3)));
3263 for (i, value) in x.as_array().iter().enumerate().skip(2) {
3264 assert_eq!(value, &f64_to_big_rational((i + 10) as f64));
3265 }
3266 }
3267 }
3268 };
3269 }
3270
3271 gen_solve_exact_pivot_swap_fractional_tests!(2);
3272 gen_solve_exact_pivot_swap_fractional_tests!(3);
3273 gen_solve_exact_pivot_swap_fractional_tests!(4);
3274 gen_solve_exact_pivot_swap_fractional_tests!(5);
3275
3276 macro_rules! gen_solve_exact_mid_pivot_swap_tests {
3285 ($d:literal) => {
3286 paste! {
3287 #[test]
3288 #[expect(
3289 clippy::cast_precision_loss,
3290 reason = "indices and test offsets are small integers exactly representable as f64"
3291 )]
3292 fn [<solve_exact_mid_pivot_swap_ $d d>]() {
3293 let mut rows = [[0.0f64; $d]; $d];
3294 rows[0][0] = 1.0; rows[0][1] = 2.0; rows[0][2] = 3.0;
3295 rows[1][2] = 4.0;
3297 rows[2][1] = 5.0; rows[2][2] = 6.0;
3298 for (i, row) in rows.iter_mut().enumerate().skip(3) {
3300 row[i] = 1.0;
3301 }
3302 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3303 let mut b_arr = [0.0f64; $d];
3304 b_arr[0] = 6.0;
3305 b_arr[1] = 7.0;
3306 b_arr[2] = 8.0;
3307 for (i, value) in b_arr.iter_mut().enumerate().skip(3) {
3308 *value = (i + 10) as f64;
3309 }
3310 let b = Vector::<$d>::new(b_arr);
3311 let x = a.solve_exact(b).unwrap();
3312 assert_eq!(x.as_array()[0], BigRational::new(BigInt::from(7), BigInt::from(4)));
3314 assert_eq!(x.as_array()[1], BigRational::new(BigInt::from(-1), BigInt::from(2)));
3315 assert_eq!(x.as_array()[2], BigRational::new(BigInt::from(7), BigInt::from(4)));
3316 for (i, value) in x.as_array().iter().enumerate().skip(3) {
3317 assert_eq!(value, &f64_to_big_rational((i + 10) as f64));
3318 }
3319 }
3320 }
3321 };
3322 }
3323
3324 gen_solve_exact_mid_pivot_swap_tests!(3);
3325 gen_solve_exact_mid_pivot_swap_tests!(4);
3326 gen_solve_exact_mid_pivot_swap_tests!(5);
3327
3328 macro_rules! gen_solve_exact_singular_rank_deficient_tests {
3336 ($d:literal) => {
3337 paste! {
3338 #[test]
3339 fn [<solve_exact_singular_rank_deficient_ $d d>]() {
3340 let mut rows = [[0.0f64; $d]; $d];
3341 for i in 0..($d - 1) {
3342 rows[i][i] = 1.0;
3343 rows[$d - 1][i] = 1.0;
3344 }
3345 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3347 let b = Vector::<$d>::new([1.0; $d]);
3348 assert_matches!(
3349 a.solve_exact(b),
3350 Err(LaError::Singular {
3351 pivot_col,
3352 reason: SingularityReason::Exact,
3353 ..
3354 }) if pivot_col == $d - 1
3355 );
3356 }
3357 }
3358 };
3359 }
3360
3361 gen_solve_exact_singular_rank_deficient_tests!(2);
3362 gen_solve_exact_singular_rank_deficient_tests!(3);
3363 gen_solve_exact_singular_rank_deficient_tests!(4);
3364 gen_solve_exact_singular_rank_deficient_tests!(5);
3365
3366 macro_rules! gen_solve_exact_zero_rhs_tests {
3372 ($d:literal) => {
3373 paste! {
3374 #[test]
3375 fn [<solve_exact_zero_rhs_ $d d>]() {
3376 let mut rows = [[1.0f64; $d]; $d];
3378 for i in 0..$d {
3379 rows[i][i] = f64::from($d) + 1.0;
3380 }
3381 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3382 let b = Vector::<$d>::zero();
3383 let x = a.solve_exact(b).unwrap();
3384 for xi in x.as_array() {
3385 assert_eq!(*xi, BigRational::from_integer(BigInt::from(0)));
3386 }
3387 }
3388 }
3389 };
3390 }
3391
3392 gen_solve_exact_zero_rhs_tests!(2);
3393 gen_solve_exact_zero_rhs_tests!(3);
3394 gen_solve_exact_zero_rhs_tests!(4);
3395 gen_solve_exact_zero_rhs_tests!(5);
3396
3397 fn big_rational_matvec<const D: usize>(
3410 a: &Matrix<D>,
3411 x: &[BigRational; D],
3412 ) -> [BigRational; D] {
3413 from_fn(|i| {
3414 let mut sum = BigRational::from_integer(BigInt::from(0));
3415 for (aij, xj) in a.as_rows()[i].iter().zip(x.iter()) {
3416 sum += f64_to_big_rational(*aij) * xj;
3417 }
3418 sum
3419 })
3420 }
3421
3422 fn hilbert<const D: usize>() -> Matrix<D> {
3423 let rows = from_fn(|r| from_fn(|c| 1.0 / f64::from(u32::try_from(r + c + 1).unwrap())));
3424 Matrix::<D>::try_from_rows(rows).unwrap()
3425 }
3426
3427 #[test]
3435 fn solve_exact_near_singular_3x3_integer_x0() {
3436 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::try_from_rows([
3438 [1.0 + perturbation, 2.0, 3.0],
3439 [4.0, 5.0, 6.0],
3440 [7.0, 8.0, 9.0],
3441 ])
3442 .unwrap();
3443 let b = Vector::<3>::new([6.0 + perturbation, 15.0, 24.0]);
3444 let x = a.solve_exact(b).unwrap();
3445 let one = BigRational::from_integer(BigInt::from(1));
3446 assert_eq!(x.as_array()[0], one);
3447 assert_eq!(x.as_array()[1], one);
3448 assert_eq!(x.as_array()[2], one);
3449 }
3450
3451 #[test]
3458 fn solve_exact_large_entries_3x3_unit_vector() {
3459 let big = f64::MAX / 2.0;
3460 assert!(big.is_finite());
3461 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
3462 .unwrap();
3463 let b = Vector::<3>::new([big, 1.0, 1.0]);
3464 let x = a.solve_exact(b).unwrap();
3465 let zero = BigRational::from_integer(BigInt::from(0));
3466 let one = BigRational::from_integer(BigInt::from(1));
3467 assert_eq!(x.as_array()[0], one);
3468 assert_eq!(x.as_array()[1], zero);
3469 assert_eq!(x.as_array()[2], zero);
3470 }
3471
3472 #[test]
3478 fn det_sign_exact_large_entries_3x3_positive() {
3479 let big = f64::MAX / 2.0;
3480 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
3481 .unwrap();
3482 assert_matches!(
3485 a.det_direct(),
3486 Err(LaError::NonFinite {
3487 location: NonFiniteLocation::Scalar,
3488 origin: NonFiniteOrigin::Computation {
3489 operation: ArithmeticOperation::Determinant,
3490 ..
3491 },
3492 ..
3493 })
3494 );
3495 assert_eq!(a.det_sign_exact(), DeterminantSign::Positive);
3496 assert!(a.det_exact().unwrap().is_positive());
3500 assert_unrepresentable(&a.det_exact_f64(), None, UnrepresentableReason::NotFinite);
3501 }
3502
3503 macro_rules! gen_det_sign_exact_hilbert_positive_tests {
3512 ($d:literal) => {
3513 paste! {
3514 #[test]
3515 fn [<det_sign_exact_hilbert_positive_ $d d>]() {
3516 let h = hilbert::<$d>();
3517 assert_eq!(h.det_sign_exact(), DeterminantSign::Positive);
3518 }
3519 }
3520 };
3521 }
3522
3523 gen_det_sign_exact_hilbert_positive_tests!(2);
3524 gen_det_sign_exact_hilbert_positive_tests!(3);
3525 gen_det_sign_exact_hilbert_positive_tests!(4);
3526 gen_det_sign_exact_hilbert_positive_tests!(5);
3527
3528 macro_rules! gen_solve_exact_hilbert_residual_tests {
3535 ($d:literal) => {
3536 paste! {
3537 #[test]
3538 fn [<solve_exact_hilbert_residual_ $d d>]() {
3539 let h = hilbert::<$d>();
3540 let mut b_arr = [0.0f64; $d];
3543 for i in 0usize..$d {
3544 let sign = if i.is_multiple_of(2) { 1.0 } else { -1.0 };
3545 b_arr[i] = sign * f64::from(u32::try_from(i + 1).unwrap());
3546 }
3547 let b = Vector::<$d>::new(b_arr);
3548 let x = h.solve_exact(b).unwrap();
3549 let ax = big_rational_matvec(&h, x.as_array());
3550 for i in 0..$d {
3551 assert_eq!(ax[i], f64_to_big_rational(b_arr[i]));
3552 }
3553 }
3554 }
3555 };
3556 }
3557
3558 gen_solve_exact_hilbert_residual_tests!(2);
3559 gen_solve_exact_hilbert_residual_tests!(3);
3560 gen_solve_exact_hilbert_residual_tests!(4);
3561 gen_solve_exact_hilbert_residual_tests!(5);
3562
3563 #[test]
3568 fn solve_exact_f64_known_2x2() {
3569 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
3570 let b = Vector::<2>::new([5.0, 11.0]);
3571 let x = a.solve_exact_f64(b).unwrap().into_array();
3572 assert!((x[0] - 1.0).abs() <= f64::EPSILON);
3573 assert!((x[1] - 2.0).abs() <= f64::EPSILON);
3574 }
3575
3576 #[test]
3577 fn solve_exact_f64_overflow_returns_err() {
3578 let big = f64::MAX / 2.0;
3581 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
3582 let b = Vector::<2>::new([big, big]);
3583 assert_unrepresentable(
3584 &a.solve_exact_f64(b),
3585 Some(0),
3586 UnrepresentableReason::NotFinite,
3587 );
3588 }
3589
3590 #[test]
3591 fn solve_exact_f64_huge_non_dyadic_component_returns_not_finite() {
3592 let a = Matrix::<1>::try_from_rows([[3.0 * f64::MIN_POSITIVE]]).unwrap();
3593 let b = Vector::<1>::new([f64::MAX]);
3594
3595 assert_unrepresentable(
3596 &a.solve_exact_f64(b),
3597 Some(0),
3598 UnrepresentableReason::NotFinite,
3599 );
3600 }
3601
3602 #[test]
3603 fn solve_exact_rounded_f64_overflow_returns_err() {
3604 let big = f64::MAX / 2.0;
3605 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
3606 let b = Vector::<2>::new([big, big]);
3607
3608 assert_unrepresentable(
3609 &a.solve_exact_rounded_f64(b),
3610 Some(0),
3611 UnrepresentableReason::NotFinite,
3612 );
3613 }
3614
3615 #[test]
3616 fn solve_exact_f64_underflow_returns_err_for_nonzero_exact_component() {
3617 let tiny = f64::from_bits(1);
3618 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3619 let b = Vector::<1>::new([tiny]);
3620
3621 assert_unrepresentable(
3622 &a.solve_exact_f64(b),
3623 Some(0),
3624 UnrepresentableReason::RequiresRounding,
3625 );
3626 }
3627
3628 #[test]
3629 fn solve_exact_f64_accepts_smallest_subnormal_result() {
3630 let tiny = f64::from_bits(1);
3631 let a = Matrix::<1>::identity();
3632 let b = Vector::<1>::new([tiny]);
3633
3634 assert_eq!(
3635 a.solve_exact_f64(b).unwrap().into_array()[0].to_bits(),
3636 tiny.to_bits()
3637 );
3638 }
3639
3640 #[test]
3645 fn bareiss_solve_d1() {
3646 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3647 let b = Vector::<1>::new([6.0]);
3648 let x = a.solve_exact(b).unwrap();
3649 assert_eq!(x.as_array()[0], f64_to_big_rational(3.0));
3650 }
3651
3652 #[test]
3653 fn bareiss_solve_singular_column_all_zero() {
3654 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]])
3655 .unwrap();
3656 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
3657 assert_matches!(
3658 a.solve_exact(b),
3659 Err(LaError::Singular {
3660 pivot_col: 1,
3661 reason: SingularityReason::Exact,
3662 ..
3663 })
3664 );
3665 }
3666
3667 #[test]
3672 fn f64_to_big_rational_scalar_cases() {
3673 let cases = [
3674 ("positive zero", 0.0, 0, 1),
3675 ("negative zero", -0.0, 0, 1),
3676 ("one", 1.0, 1, 1),
3677 ("negative one", -1.0, -1, 1),
3678 ("half", 0.5, 1, 2),
3679 ("quarter", 0.25, 1, 4),
3680 ("negative three and a half", -3.5, -7, 2),
3681 ("integer", 42.0, 42, 1),
3682 ("power of two", 1024.0, 1024, 1),
3683 ];
3684
3685 for (label, value, numerator, denominator) in cases {
3686 assert_eq!(
3687 f64_to_big_rational(value),
3688 BigRational::new(BigInt::from(numerator), BigInt::from(denominator)),
3689 "{label}"
3690 );
3691 }
3692 }
3693
3694 #[test]
3695 fn f64_to_big_rational_subnormal() {
3696 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3698 let r = f64_to_big_rational(tiny);
3699 assert_eq!(
3701 r,
3702 BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
3703 );
3704 }
3705
3706 #[test]
3707 fn f64_to_big_rational_already_lowest_terms() {
3708 let r = f64_to_big_rational(0.5);
3710 assert_eq!(*r.numer(), BigInt::from(1));
3711 assert_eq!(*r.denom(), BigInt::from(2));
3712 }
3713
3714 #[test]
3715 fn f64_to_big_rational_round_trip() {
3716 let values = [
3719 0.0,
3720 1.0,
3721 -1.0,
3722 0.5,
3723 0.25,
3724 0.1,
3725 42.0,
3726 -3.5,
3727 1e10,
3728 1e-10,
3729 f64::MAX / 2.0,
3730 f64::MIN_POSITIVE,
3731 5e-324,
3732 ];
3733 for &v in &values {
3734 let r = f64_to_big_rational(v);
3735 let back = r.to_f64().expect("round-trip to_f64 failed");
3736 assert_eq!(
3737 v.to_bits(),
3738 back.to_bits(),
3739 "round-trip failed for {v}: got {back}"
3740 );
3741 }
3742 }
3743}