1#![forbid(unsafe_code)]
2
3use core::hint::cold_path;
87use core::mem::take;
88use core::num::NonZeroU64;
89use std::array::from_fn;
90
91use num_bigint::{BigInt, Sign};
92use num_rational::BigRational;
93use num_traits::ToPrimitive;
94
95use crate::matrix::Matrix;
96use crate::vector::Vector;
97use crate::{LaError, UnrepresentableReason};
98
99#[must_use]
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum DeterminantSign {
117 Negative,
119 Zero,
121 Positive,
123}
124
125impl DeterminantSign {
126 #[inline]
128 #[must_use]
129 pub const fn as_i8(self) -> i8 {
130 match self {
131 Self::Negative => -1,
132 Self::Zero => 0,
133 Self::Positive => 1,
134 }
135 }
136}
137
138pub trait ExactF64Conversion {
173 type Output;
175
176 fn try_to_f64(&self) -> Result<Self::Output, LaError>;
190
191 fn to_rounded_f64(&self) -> Result<Self::Output, LaError>;
200}
201
202const F64_SIGNIFICAND_BITS: i64 = 53;
203const F64_FRACTION_BITS: i64 = 52;
204const F64_MIN_BINARY_EXPONENT: i64 = -1074;
205const F64_MIN_NORMAL_EXPONENT: i64 = -1022;
206const F64_MAX_BINARY_EXPONENT: i64 = 1023;
207const F64_EXPONENT_BIAS: i64 = 1023;
208const F64_FRACTION_MASK: u64 = (1u64 << 52) - 1;
209
210const fn decompose_proven_finite_f64(x: f64) -> Component {
217 let bits = x.to_bits();
218 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
219 let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
220
221 if biased_exp == 0 && fraction == 0 {
223 return Component::Zero;
224 }
225
226 let (mantissa, raw_exp) = if biased_exp == 0 {
227 (fraction, -1074_i32)
230 } else {
231 ((1u64 << 52) | fraction, biased_exp - 1075)
234 };
235
236 let tz = mantissa.trailing_zeros();
239 let Some(mantissa) = NonZeroU64::new(mantissa >> tz) else {
240 return Component::Zero;
241 };
242
243 Component::NonZero {
244 mantissa,
245 exponent: raw_exp + tz.cast_signed(),
246 is_negative: bits >> 63 != 0,
247 }
248}
249
250#[cfg(test)]
260const fn decompose_f64(x: f64) -> Result<Component, LaError> {
261 let bits = x.to_bits();
262 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
263
264 if biased_exp == 0x7FF {
265 cold_path();
266 return Err(LaError::non_finite_input_scalar());
267 }
268
269 Ok(decompose_proven_finite_f64(x))
270}
271
272fn big_int_exp_to_big_rational(mut value: BigInt, mut exp: i32) -> BigRational {
278 if value == BigInt::from(0) {
279 return BigRational::from_integer(BigInt::from(0));
280 }
281
282 if exp < 0
284 && let Some(tz) = value.trailing_zeros()
285 {
286 let exp_abs = exp.unsigned_abs();
287 let reduce = tz.min(u64::from(exp_abs));
288 value >>= reduce;
289 let remaining_abs = u64::from(exp_abs) - reduce;
290 exp = negative_exponent_from_magnitude(remaining_abs);
291 }
292
293 if exp >= 0 {
294 BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
295 } else {
296 BigRational::new_raw(value, BigInt::from(1u32) << exp.unsigned_abs())
297 }
298}
299
300#[inline]
306fn negative_exponent_from_magnitude(magnitude: u64) -> i32 {
307 if magnitude == u64::from(i32::MIN.unsigned_abs()) {
308 return i32::MIN;
309 }
310
311 let Ok(value) = i32::try_from(magnitude) else {
312 cold_path();
313 unreachable!("negative exponent magnitude exceeds the i32 domain");
314 };
315 -value
316}
317
318fn exact_rational_to_finite_f64(exact: &BigRational, index: Option<usize>) -> Result<f64, LaError> {
329 if exact.denom().sign() == Sign::NoSign {
330 cold_path();
331 return Err(LaError::unrepresentable(
332 index,
333 UnrepresentableReason::NotFinite,
334 ));
335 }
336
337 if exact.numer().sign() == Sign::NoSign {
338 return Ok(0.0);
339 }
340
341 let denominator = exact.denom();
342 if denominator.sign() == Sign::Plus
343 && let Some(denominator_exp) = positive_power_of_two_exponent(denominator)
344 && let Ok(denominator_exp) = i32::try_from(denominator_exp)
345 {
346 return big_int_exp_ref_to_finite_f64(exact.numer(), -denominator_exp, index, || {
347 rounded_rational_unrepresentable_reason(exact)
348 });
349 }
350
351 let reduced = exact.reduced();
356 reduced_rational_to_finite_f64(&reduced, index)
357}
358
359fn positive_power_of_two_exponent(value: &BigInt) -> Option<u64> {
361 if value.sign() != Sign::Plus {
362 return None;
363 }
364
365 let exponent = value.trailing_zeros()?;
366 (value.bits().checked_sub(1) == Some(exponent)).then_some(exponent)
367}
368
369fn reduced_rational_to_finite_f64(
371 exact: &BigRational,
372 index: Option<usize>,
373) -> Result<f64, LaError> {
374 let Some(denominator_exp) = positive_power_of_two_exponent(exact.denom()) else {
375 cold_path();
376 return Err(LaError::unrepresentable(
377 index,
378 rounded_rational_unrepresentable_reason(exact),
379 ));
380 };
381 let Ok(denominator_exp) = i32::try_from(denominator_exp) else {
382 cold_path();
383 return Err(LaError::unrepresentable(
384 index,
385 rounded_rational_unrepresentable_reason(exact),
386 ));
387 };
388
389 big_int_exp_ref_to_finite_f64(exact.numer(), -denominator_exp, index, || {
390 rounded_rational_unrepresentable_reason(exact)
391 })
392}
393
394fn rounded_rational_unrepresentable_reason(exact: &BigRational) -> UnrepresentableReason {
400 match exact.to_f64() {
401 Some(value) if value.is_finite() => UnrepresentableReason::RequiresRounding,
402 _ => UnrepresentableReason::NotFinite,
403 }
404}
405
406fn exact_rational_to_rounded_f64(
409 exact: &BigRational,
410 index: Option<usize>,
411) -> Result<f64, LaError> {
412 if exact.denom().sign() == Sign::NoSign {
413 cold_path();
414 return Err(LaError::unrepresentable(
415 index,
416 UnrepresentableReason::NotFinite,
417 ));
418 }
419 if exact.numer().sign() == Sign::NoSign {
420 return Ok(0.0);
421 }
422
423 let Some(value) = exact.to_f64() else {
424 cold_path();
425 return Err(LaError::unrepresentable(
426 index,
427 UnrepresentableReason::NotFinite,
428 ));
429 };
430 if value.is_finite() {
431 Ok(value)
432 } else {
433 cold_path();
434 Err(LaError::unrepresentable(
435 index,
436 UnrepresentableReason::NotFinite,
437 ))
438 }
439}
440
441impl ExactF64Conversion for BigRational {
442 type Output = f64;
443
444 #[inline]
445 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
446 exact_rational_to_finite_f64(self, None)
447 }
448
449 #[inline]
450 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
451 exact_rational_to_rounded_f64(self, None)
452 }
453}
454
455impl<const D: usize> ExactF64Conversion for [BigRational; D] {
456 type Output = Vector<D>;
457
458 #[inline]
459 fn try_to_f64(&self) -> Result<Self::Output, LaError> {
460 let mut result = [0.0; D];
461 for (index, value) in self.iter().enumerate() {
462 result[index] = exact_rational_to_finite_f64(value, Some(index))?;
463 }
464 Vector::try_new(result)
465 }
466
467 #[inline]
468 fn to_rounded_f64(&self) -> Result<Self::Output, LaError> {
469 let mut result = [0.0; D];
470 for (index, value) in self.iter().enumerate() {
471 result[index] = exact_rational_to_rounded_f64(value, Some(index))?;
472 }
473 Vector::try_new(result)
474 }
475}
476
477fn shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option<u64> {
493 let word_bits = u64::from(u64::BITS);
494 let word_index = usize::try_from(shift / word_bits).ok()?;
495 let bit_shift = u32::try_from(shift % word_bits).ok()?;
496 let mut digits = value.iter_u64_digits().skip(word_index);
497 let low = digits.next()? >> bit_shift;
498 if bit_shift == 0 {
499 Some(low)
500 } else {
501 let high = digits.next().unwrap_or(0) << (u64::BITS - bit_shift);
502 Some(low | high)
503 }
504}
505
506fn magnitude_bit_is_set(value: &BigInt, bit: u64) -> bool {
508 let word_bits = u64::from(u64::BITS);
509 let Ok(word_index) = usize::try_from(bit / word_bits) else {
510 return false;
511 };
512 let bit_index = u32::try_from(bit % word_bits).unwrap_or(0);
513 value
514 .iter_u64_digits()
515 .nth(word_index)
516 .is_some_and(|word| word & (1_u64 << bit_index) != 0)
517}
518
519fn magnitude_has_lower_bits(value: &BigInt, exclusive_end: u64) -> bool {
521 let word_bits = u64::from(u64::BITS);
522 let Ok(full_words) = usize::try_from(exclusive_end / word_bits) else {
523 return value.sign() != Sign::NoSign;
524 };
525 let partial_bits = u32::try_from(exclusive_end % word_bits).unwrap_or(0);
526 let mut digits = value.iter_u64_digits();
527
528 for _ in 0..full_words {
529 if digits.next().unwrap_or(0) != 0 {
530 return true;
531 }
532 }
533
534 if partial_bits == 0 {
535 false
536 } else {
537 let mask = (1_u64 << partial_bits) - 1;
538 digits.next().is_some_and(|word| word & mask != 0)
539 }
540}
541
542fn rounded_shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option<u64> {
544 if shift > value.bits() {
545 return Some(0);
546 }
547 let retained = shifted_magnitude_to_u64(value, shift).unwrap_or(0);
548 if shift == 0 {
549 return Some(retained);
550 }
551
552 let guard_bit = shift - 1;
553 let increment = magnitude_bit_is_set(value, guard_bit)
554 && (magnitude_has_lower_bits(value, guard_bit) || retained & 1 != 0);
555 retained.checked_add(u64::from(increment))
556}
557
558#[inline]
561fn inexact_big_int_reason(
562 top_bit_exp: i64,
563 rounded_reason: impl FnOnce() -> UnrepresentableReason,
564) -> UnrepresentableReason {
565 if top_bit_exp < F64_MAX_BINARY_EXPONENT {
566 UnrepresentableReason::RequiresRounding
567 } else {
568 rounded_reason()
569 }
570}
571
572fn big_int_exp_ref_to_rounded_f64(
579 value: &BigInt,
580 exp: i32,
581 index: Option<usize>,
582) -> Result<f64, LaError> {
583 if value.sign() == Sign::NoSign {
584 return Ok(0.0);
585 }
586
587 let sign = if value.sign() == Sign::Minus {
588 1_u64 << 63
589 } else {
590 0
591 };
592 let Ok(bit_len) = i64::try_from(value.bits()) else {
593 cold_path();
594 return Err(LaError::unrepresentable(
595 index,
596 UnrepresentableReason::NotFinite,
597 ));
598 };
599 let Some(mut top_bit_exp) = i64::from(exp).checked_add(bit_len - 1) else {
600 cold_path();
601 return Err(LaError::unrepresentable(
602 index,
603 UnrepresentableReason::NotFinite,
604 ));
605 };
606 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
607 cold_path();
608 return Err(LaError::unrepresentable(
609 index,
610 UnrepresentableReason::NotFinite,
611 ));
612 }
613
614 if top_bit_exp >= F64_MIN_NORMAL_EXPONENT {
615 let mut significand = if bit_len <= F64_SIGNIFICAND_BITS {
616 let Some(magnitude) = shifted_magnitude_to_u64(value, 0) else {
617 cold_path();
618 unreachable!("nonzero integer must expose magnitude digits");
619 };
620 let shift = u32::try_from(F64_SIGNIFICAND_BITS - bit_len)
621 .unwrap_or_else(|_| unreachable!("normal significand shift must fit u32"));
622 magnitude
623 .checked_shl(shift)
624 .unwrap_or_else(|| unreachable!("normal significand must fit u64"))
625 } else {
626 let shift = u64::try_from(bit_len - F64_SIGNIFICAND_BITS)
627 .unwrap_or_else(|_| unreachable!("positive significand shift must fit u64"));
628 rounded_shifted_magnitude_to_u64(value, shift)
629 .unwrap_or_else(|| unreachable!("rounded binary64 significand must fit u64"))
630 };
631
632 if significand == 1_u64 << F64_SIGNIFICAND_BITS {
633 significand >>= 1;
634 top_bit_exp += 1;
635 }
636 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
637 cold_path();
638 return Err(LaError::unrepresentable(
639 index,
640 UnrepresentableReason::NotFinite,
641 ));
642 }
643
644 let biased_exp = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS)
645 .unwrap_or_else(|_| unreachable!("normal exponent must be positive"));
646 return Ok(f64::from_bits(
647 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
648 ));
649 }
650
651 let subnormal_shift = i64::from(exp) - F64_MIN_BINARY_EXPONENT;
652 let significand = if subnormal_shift >= 0 {
653 let Some(magnitude) = shifted_magnitude_to_u64(value, 0) else {
654 cold_path();
655 unreachable!("nonzero integer must expose magnitude digits");
656 };
657 let shift = u32::try_from(subnormal_shift)
658 .unwrap_or_else(|_| unreachable!("subnormal left shift must fit u32"));
659 magnitude
660 .checked_shl(shift)
661 .unwrap_or_else(|| unreachable!("subnormal significand must fit u64"))
662 } else {
663 let shift = u64::try_from(-subnormal_shift)
664 .unwrap_or_else(|_| unreachable!("subnormal right shift must fit u64"));
665 rounded_shifted_magnitude_to_u64(value, shift)
666 .unwrap_or_else(|| unreachable!("rounded subnormal significand must fit u64"))
667 };
668
669 if significand == 1_u64 << F64_FRACTION_BITS {
670 return Ok(f64::from_bits(sign | (1_u64 << F64_FRACTION_BITS)));
671 }
672 Ok(f64::from_bits(sign | significand))
673}
674
675fn big_int_exp_ref_to_finite_f64(
682 value: &BigInt,
683 exp: i32,
684 index: Option<usize>,
685 rounded_reason: impl FnOnce() -> UnrepresentableReason,
686) -> Result<f64, LaError> {
687 if value.sign() == Sign::NoSign {
688 return Ok(0.0);
689 }
690
691 let is_negative = value.sign() == Sign::Minus;
692 let mut exp = i64::from(exp);
693 let Some(trailing_zeros) = value.trailing_zeros() else {
694 cold_path();
695 unreachable!("nonzero integer must have a least-significant set bit");
696 };
697 let Ok(trailing_zeros_i64) = i64::try_from(trailing_zeros) else {
698 cold_path();
699 return Err(LaError::unrepresentable(
700 index,
701 UnrepresentableReason::NotFinite,
702 ));
703 };
704 let Some(updated_exp) = exp.checked_add(trailing_zeros_i64) else {
705 cold_path();
706 return Err(LaError::unrepresentable(
707 index,
708 UnrepresentableReason::NotFinite,
709 ));
710 };
711 exp = updated_exp;
712
713 let Some(bit_len) = value.bits().checked_sub(trailing_zeros) else {
714 cold_path();
715 unreachable!("trailing-zero count cannot exceed integer bit length");
716 };
717 let Ok(bit_len) = i64::try_from(bit_len) else {
718 cold_path();
719 return Err(LaError::unrepresentable(
720 index,
721 UnrepresentableReason::NotFinite,
722 ));
723 };
724 let Some(top_bit_exp) = exp.checked_add(bit_len - 1) else {
725 cold_path();
726 return Err(LaError::unrepresentable(
727 index,
728 UnrepresentableReason::NotFinite,
729 ));
730 };
731 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
732 cold_path();
733 return Err(LaError::unrepresentable(
734 index,
735 UnrepresentableReason::NotFinite,
736 ));
737 }
738 if exp < F64_MIN_BINARY_EXPONENT {
739 cold_path();
740 let reason = inexact_big_int_reason(top_bit_exp, rounded_reason);
744 return Err(LaError::unrepresentable(index, reason));
745 }
746 if bit_len > F64_SIGNIFICAND_BITS {
747 cold_path();
748 let reason = inexact_big_int_reason(top_bit_exp, rounded_reason);
752 return Err(LaError::unrepresentable(index, reason));
753 }
754
755 let Some(mantissa) = shifted_magnitude_to_u64(value, trailing_zeros) else {
756 cold_path();
757 return Err(LaError::unrepresentable(
758 index,
759 UnrepresentableReason::NotFinite,
760 ));
761 };
762 let sign = if is_negative { 1u64 << 63 } else { 0 };
763
764 if top_bit_exp < F64_MIN_NORMAL_EXPONENT {
765 let Ok(shift) = u32::try_from(exp - F64_MIN_BINARY_EXPONENT) else {
766 cold_path();
767 return Err(LaError::unrepresentable(
768 index,
769 UnrepresentableReason::RequiresRounding,
770 ));
771 };
772 Ok(f64::from_bits(sign | (mantissa << shift)))
773 } else {
774 let Ok(biased_exp) = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS) else {
775 cold_path();
776 return Err(LaError::unrepresentable(
777 index,
778 UnrepresentableReason::NotFinite,
779 ));
780 };
781 let Ok(shift) = u32::try_from(F64_FRACTION_BITS - (bit_len - 1)) else {
782 cold_path();
783 return Err(LaError::unrepresentable(
784 index,
785 UnrepresentableReason::RequiresRounding,
786 ));
787 };
788 let significand = mantissa << shift;
789 Ok(f64::from_bits(
790 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
791 ))
792 }
793}
794
795fn big_int_exp_to_finite_f64(
796 value: &BigInt,
797 exp: i32,
798 index: Option<usize>,
799) -> Result<f64, LaError> {
800 big_int_exp_ref_to_finite_f64(value, exp, index, || {
801 match big_int_exp_ref_to_rounded_f64(value, exp, index) {
802 Ok(_) => UnrepresentableReason::RequiresRounding,
803 Err(_) => UnrepresentableReason::NotFinite,
804 }
805 })
806}
807
808fn big_int_exp_to_rounded_f64(value: &BigInt, exp: i32) -> Result<f64, LaError> {
810 big_int_exp_ref_to_rounded_f64(value, exp, None)
811}
812
813#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
832enum Component {
833 #[default]
834 Zero,
835 NonZero {
836 mantissa: NonZeroU64,
837 exponent: i32,
838 is_negative: bool,
839 },
840}
841
842impl Component {
843 const fn exponent(self) -> Option<i32> {
845 match self {
846 Self::Zero => None,
847 Self::NonZero { exponent, .. } => Some(exponent),
848 }
849 }
850}
851
852mod decomposition {
853 use super::Component;
854
855 #[derive(Clone, Debug, Eq, PartialEq)]
861 pub(super) struct Decomposed<T> {
862 components: T,
863 min_exponent: Option<i32>,
864 }
865
866 impl<T> Decomposed<T> {
867 pub(super) const fn components(&self) -> &T {
869 &self.components
870 }
871
872 pub(super) const fn min_exponent(&self) -> Option<i32> {
874 self.min_exponent
875 }
876 }
877
878 impl<const D: usize> Decomposed<[Component; D]> {
879 pub(super) fn from_vector_components(components: [Component; D]) -> Self {
881 let min_exponent = components
882 .iter()
883 .filter_map(|component| component.exponent())
884 .min();
885 Self {
886 components,
887 min_exponent,
888 }
889 }
890 }
891
892 impl<const D: usize> Decomposed<[[Component; D]; D]> {
893 pub(super) fn from_matrix_components(components: [[Component; D]; D]) -> Self {
895 let min_exponent = components
896 .iter()
897 .flatten()
898 .filter_map(|component| component.exponent())
899 .min();
900 Self {
901 components,
902 min_exponent,
903 }
904 }
905 }
906
907 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
912 pub(super) struct ScaleExponent {
913 value: i32,
914 }
915
916 impl ScaleExponent {
917 pub(super) const ZERO: Self = Self { value: 0 };
919
920 pub(super) const fn for_decomposed<T>(decomposed: &Decomposed<T>) -> Self {
922 let value = match decomposed.min_exponent() {
923 Some(exponent) => exponent,
924 None => 0,
925 };
926 Self { value }
927 }
928
929 pub(super) const fn min(self, other: Self) -> Self {
931 if self.value < other.value {
932 self
933 } else {
934 other
935 }
936 }
937
938 pub(super) const fn get(self) -> i32 {
940 self.value
941 }
942
943 pub(super) fn shift_for(self, exponent: i32) -> u32 {
949 let Some(shift) = exponent.checked_sub(self.value) else {
950 unreachable!("finite f64 exponent difference cannot overflow");
951 };
952 let Ok(shift) = u32::try_from(shift) else {
953 unreachable!("scale exponent cannot exceed a component exponent");
954 };
955 shift
956 }
957 }
958}
959
960use decomposition::{Decomposed, ScaleExponent};
961
962fn decompose_proven_finite_matrix<const D: usize>(
964 m: &Matrix<D>,
965) -> Decomposed<[[Component; D]; D]> {
966 let components =
967 from_fn(|row| from_fn(|col| decompose_proven_finite_f64(m.as_rows()[row][col])));
968 Decomposed::from_matrix_components(components)
969}
970
971fn decompose_proven_finite_vector<const D: usize>(v: &Vector<D>) -> Decomposed<[Component; D]> {
973 let components = from_fn(|index| decompose_proven_finite_f64(v.as_array()[index]));
974 Decomposed::from_vector_components(components)
975}
976
977#[inline]
980fn component_to_big_int(component: Component, scale: ScaleExponent) -> BigInt {
981 match component {
982 Component::Zero => BigInt::from(0),
983 Component::NonZero {
984 mantissa,
985 exponent,
986 is_negative,
987 } => {
988 let value = BigInt::from(mantissa.get()) << scale.shift_for(exponent);
989 if is_negative { -value } else { value }
990 }
991 }
992}
993
994fn build_big_int_matrix<const D: usize>(
996 components: &[[Component; D]; D],
997 scale: ScaleExponent,
998) -> [[BigInt; D]; D] {
999 from_fn(|row| from_fn(|col| component_to_big_int(components[row][col], scale)))
1000}
1001
1002fn build_big_int_vec<const D: usize>(
1004 components: &[Component; D],
1005 scale: ScaleExponent,
1006) -> [BigInt; D] {
1007 from_fn(|index| component_to_big_int(components[index], scale))
1008}
1009
1010#[inline]
1012fn det2_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1013 &a[0][0] * &a[1][1] - &a[0][1] * &a[1][0]
1014}
1015
1016#[inline]
1022fn det3_big_int_entries(a: [[&BigInt; 3]; 3]) -> BigInt {
1023 let m00 = a[1][1] * a[2][2] - a[1][2] * a[2][1];
1024 let m01 = a[1][0] * a[2][2] - a[1][2] * a[2][0];
1025 let m02 = a[1][0] * a[2][1] - a[1][1] * a[2][0];
1026 a[0][0] * m00 - a[0][1] * m01 + a[0][2] * m02
1027}
1028
1029#[inline]
1031fn det3_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1032 det3_big_int_entries([
1033 [&a[0][0], &a[0][1], &a[0][2]],
1034 [&a[1][0], &a[1][1], &a[1][2]],
1035 [&a[2][0], &a[2][1], &a[2][2]],
1036 ])
1037}
1038
1039#[inline]
1041fn det4_big_int<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
1042 let mut det = BigInt::from(0);
1043
1044 if a[0][0].sign() != Sign::NoSign {
1045 let c00 = det3_big_int_entries([
1046 [&a[1][1], &a[1][2], &a[1][3]],
1047 [&a[2][1], &a[2][2], &a[2][3]],
1048 [&a[3][1], &a[3][2], &a[3][3]],
1049 ]);
1050 det += &a[0][0] * c00;
1051 }
1052 if a[0][1].sign() != Sign::NoSign {
1053 let c01 = det3_big_int_entries([
1054 [&a[1][0], &a[1][2], &a[1][3]],
1055 [&a[2][0], &a[2][2], &a[2][3]],
1056 [&a[3][0], &a[3][2], &a[3][3]],
1057 ]);
1058 det -= &a[0][1] * c01;
1059 }
1060 if a[0][2].sign() != Sign::NoSign {
1061 let c02 = det3_big_int_entries([
1062 [&a[1][0], &a[1][1], &a[1][3]],
1063 [&a[2][0], &a[2][1], &a[2][3]],
1064 [&a[3][0], &a[3][1], &a[3][3]],
1065 ]);
1066 det += &a[0][2] * c02;
1067 }
1068 if a[0][3].sign() != Sign::NoSign {
1069 let c03 = det3_big_int_entries([
1070 [&a[1][0], &a[1][1], &a[1][2]],
1071 [&a[2][0], &a[2][1], &a[2][2]],
1072 [&a[3][0], &a[3][1], &a[3][2]],
1073 ]);
1074 det -= &a[0][3] * c03;
1075 }
1076
1077 det
1078}
1079
1080#[derive(Debug)]
1082enum BareissResult {
1083 Upper { odd_swaps: bool },
1086 Singular { pivot_col: usize },
1088}
1089
1090fn bareiss_forward_eliminate<const D: usize>(
1101 a: &mut [[BigInt; D]; D],
1102 mut rhs: Option<&mut [BigInt; D]>,
1103) -> BareissResult {
1104 let zero = BigInt::from(0);
1105 let mut prev_pivot = BigInt::from(1);
1106 let mut odd_swaps = false;
1107
1108 for k in 0..D {
1109 if a[k][k] == zero {
1111 let mut found = false;
1112 for i in (k + 1)..D {
1113 if a[i][k] != zero {
1114 a.swap(k, i);
1115 if let Some(r) = &mut rhs {
1116 r.swap(k, i);
1117 }
1118 odd_swaps = !odd_swaps;
1119 found = true;
1120 break;
1121 }
1122 }
1123 if !found {
1124 cold_path();
1125 return BareissResult::Singular { pivot_col: k };
1126 }
1127 }
1128
1129 if k + 1 == D {
1132 break;
1133 }
1134
1135 for i in (k + 1)..D {
1139 for j in (k + 1)..D {
1140 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
1141 }
1142 if let Some(r) = &mut rhs {
1143 r[i] = (&a[k][k] * &r[i] - &a[i][k] * &r[k]) / &prev_pivot;
1144 }
1145 a[i][k].clone_from(&zero);
1146 }
1147
1148 prev_pivot.clone_from(&a[k][k]);
1149 }
1150
1151 #[cfg(debug_assertions)]
1155 for (k, row) in a.iter().enumerate() {
1156 assert_ne!(row[k], zero, "pivot at ({k}, {k}) must be non-zero");
1157 for (i, lower_row) in a.iter().enumerate().skip(k + 1) {
1158 assert_eq!(
1159 lower_row[k], zero,
1160 "sub-diagonal at ({i}, {k}) must be zero"
1161 );
1162 }
1163 }
1164
1165 BareissResult::Upper { odd_swaps }
1166}
1167
1168fn determinant_scale_exp<const D: usize>(e_min: i32) -> Result<i32, LaError> {
1178 let Ok(d_i32) = i32::try_from(D) else {
1179 cold_path();
1180 return Err(LaError::determinant_scale_overflow(D, e_min));
1181 };
1182 let Some(total_exp) = e_min.checked_mul(d_i32) else {
1183 cold_path();
1184 return Err(LaError::determinant_scale_overflow(D, e_min));
1185 };
1186 Ok(total_exp)
1187}
1188
1189fn scaled_det_int_finite<const D: usize>(m: &Matrix<D>) -> (BigInt, ScaleExponent) {
1202 let decomposed = decompose_proven_finite_matrix(m);
1203 scaled_det_int_decomposed(&decomposed)
1204}
1205
1206fn scaled_det_int_decomposed<const D: usize>(
1208 decomposed: &Decomposed<[[Component; D]; D]>,
1209) -> (BigInt, ScaleExponent) {
1210 if D == 0 {
1213 return (BigInt::from(1), ScaleExponent::ZERO);
1214 }
1215
1216 if decomposed.min_exponent().is_none() {
1217 return (BigInt::from(0), ScaleExponent::ZERO);
1218 }
1219 let scale = ScaleExponent::for_decomposed(decomposed);
1220 let mut a = build_big_int_matrix(decomposed.components(), scale);
1221 let det_int = match D {
1222 1 => take(&mut a[0][0]),
1223 2 => det2_big_int(&a),
1224 3 => det3_big_int(&a),
1225 4 => det4_big_int(&a),
1226 _ => {
1227 let odd_swaps = match bareiss_forward_eliminate(&mut a, None) {
1228 BareissResult::Upper { odd_swaps } => odd_swaps,
1229 BareissResult::Singular { .. } => {
1230 cold_path();
1231 return (BigInt::from(0), ScaleExponent::ZERO);
1232 }
1233 };
1234
1235 let det = take(&mut a[D - 1][D - 1]);
1236 if odd_swaps { -det } else { det }
1237 }
1238 };
1239
1240 (det_int, scale)
1241}
1242
1243fn exact_det_int_finite<const D: usize>(m: &Matrix<D>) -> Result<(BigInt, i32), LaError> {
1249 let (det_int, scale) = scaled_det_int_finite(m);
1250 if det_int.sign() == Sign::NoSign {
1251 return Ok((det_int, 0));
1252 }
1253 let total_exp = determinant_scale_exp::<D>(scale.get())?;
1254 Ok((det_int, total_exp))
1255}
1256
1257fn exact_det_finite<const D: usize>(m: &Matrix<D>) -> Result<BigRational, LaError> {
1261 let (det_int, total_exp) = exact_det_int_finite(m)?;
1262 Ok(big_int_exp_to_big_rational(det_int, total_exp))
1263}
1264
1265fn bareiss_solve_finite<const D: usize>(
1274 m: &Matrix<D>,
1275 b: &Vector<D>,
1276) -> Result<[BigRational; D], LaError> {
1277 let matrix = decompose_proven_finite_matrix(m);
1278 let rhs = decompose_proven_finite_vector(b);
1279 bareiss_solve_components(&matrix, &rhs)
1280}
1281
1282fn bareiss_solve_components<const D: usize>(
1298 matrix: &Decomposed<[[Component; D]; D]>,
1299 rhs: &Decomposed<[Component; D]>,
1300) -> Result<[BigRational; D], LaError> {
1301 const MAX_SHARED_SCALE_GAP_BITS: u32 = 64;
1302
1303 let independent_matrix_scale = ScaleExponent::for_decomposed(matrix);
1304 let independent_rhs_scale = ScaleExponent::for_decomposed(rhs);
1305 let scale_gap = independent_matrix_scale
1306 .get()
1307 .abs_diff(independent_rhs_scale.get());
1308 let (matrix_scale, rhs_scale) = if scale_gap <= MAX_SHARED_SCALE_GAP_BITS {
1309 let shared = independent_matrix_scale.min(independent_rhs_scale);
1310 (shared, shared)
1311 } else {
1312 (independent_matrix_scale, independent_rhs_scale)
1313 };
1314 let mut a = build_big_int_matrix(matrix.components(), matrix_scale);
1315 let mut rhs = build_big_int_vec(rhs.components(), rhs_scale);
1316
1317 match bareiss_forward_eliminate(&mut a, Some(&mut rhs)) {
1318 BareissResult::Upper { .. } => {}
1319 BareissResult::Singular { pivot_col } => {
1320 cold_path();
1321 return Err(LaError::singular_exact(pivot_col));
1322 }
1323 }
1324
1325 let mut x: [BigRational; D] = from_fn(|_| BigRational::from_integer(BigInt::from(0)));
1326 for i in (0..D).rev() {
1327 let mut sum = BigRational::from_integer(take(&mut rhs[i]));
1328 for j in (i + 1)..D {
1329 let a_ij = BigRational::from_integer(take(&mut a[i][j]));
1330 sum -= &a_ij * &x[j];
1331 }
1332 let a_ii = BigRational::from_integer(take(&mut a[i][i]));
1333 x[i] = sum / &a_ii;
1334 }
1335
1336 let solution_scale_exp = rhs_scale
1337 .get()
1338 .checked_sub(matrix_scale.get())
1339 .unwrap_or_else(|| unreachable!("finite f64 scale difference cannot overflow i32"));
1340 if solution_scale_exp != 0 {
1341 let solution_scale = big_int_exp_to_big_rational(BigInt::from(1_u8), solution_scale_exp);
1342 for component in &mut x {
1343 *component *= &solution_scale;
1344 }
1345 }
1346
1347 Ok(x)
1348}
1349
1350#[inline]
1365fn det_exact_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
1366 let (det_int, total_exp) = exact_det_int_finite(m)?;
1367 big_int_exp_to_finite_f64(&det_int, total_exp, None)
1368}
1369
1370#[inline]
1382fn det_exact_rounded_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
1383 let (det_int, total_exp) = exact_det_int_finite(m)?;
1384 big_int_exp_to_rounded_f64(&det_int, total_exp)
1385}
1386
1387#[inline]
1394fn det_sign_exact_finite<const D: usize>(m: &Matrix<D>) -> DeterminantSign {
1395 if let Ok(Some(estimate)) = m.det_direct_with_errbound() {
1396 let det_f64 = estimate.determinant();
1397 let error_bound = estimate.absolute_error_bound();
1398 if det_f64 > error_bound {
1399 return DeterminantSign::Positive;
1400 }
1401 if det_f64 < -error_bound {
1402 return DeterminantSign::Negative;
1403 }
1404 }
1405
1406 cold_path();
1407 let decomposed = decompose_proven_finite_matrix(m);
1408 let (det_int, _) = scaled_det_int_decomposed(&decomposed);
1409 match det_int.sign() {
1410 Sign::Plus => DeterminantSign::Positive,
1411 Sign::Minus => DeterminantSign::Negative,
1412 Sign::NoSign => DeterminantSign::Zero,
1413 }
1414}
1415
1416impl<const D: usize> Matrix<D> {
1417 #[inline]
1451 pub fn det_exact(&self) -> Result<BigRational, LaError> {
1452 exact_det_finite(self)
1453 }
1454
1455 #[inline]
1489 pub fn det_exact_f64(&self) -> Result<f64, LaError> {
1490 det_exact_f64_finite(self)
1491 }
1492
1493 #[inline]
1533 pub fn det_exact_rounded_f64(&self) -> Result<f64, LaError> {
1534 det_exact_rounded_f64_finite(self)
1535 }
1536
1537 #[inline]
1587 pub fn solve_exact(&self, b: Vector<D>) -> Result<[BigRational; D], LaError> {
1588 bareiss_solve_finite(self, &b)
1589 }
1590
1591 #[inline]
1624 pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1625 self.solve_exact(b)?.try_to_f64()
1626 }
1627
1628 #[inline]
1665 pub fn solve_exact_rounded_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1666 self.solve_exact(b)?.to_rounded_f64()
1667 }
1668
1669 #[inline]
1709 pub fn det_sign_exact(&self) -> DeterminantSign {
1710 det_sign_exact_finite(self)
1711 }
1712}
1713
1714#[cfg(test)]
1715mod tests {
1716 use core::assert_matches;
1717 use std::array::from_fn;
1718
1719 use num_traits::Signed;
1720 use pastey::paste;
1721 use proptest::prelude::*;
1722
1723 use super::*;
1724 use crate::{
1725 ArithmeticOperation, DEFAULT_SINGULAR_TOL, NonFiniteLocation, NonFiniteOrigin,
1726 SingularityReason,
1727 };
1728
1729 fn f64_to_big_rational(x: f64) -> BigRational {
1749 let component = decompose_f64(x).expect("test helper requires finite f64 input");
1750 let Component::NonZero {
1751 mantissa,
1752 exponent,
1753 is_negative,
1754 } = component
1755 else {
1756 return BigRational::from_integer(BigInt::from(0));
1757 };
1758
1759 let numer = if is_negative {
1760 -BigInt::from(mantissa.get())
1761 } else {
1762 BigInt::from(mantissa.get())
1763 };
1764
1765 if exponent >= 0 {
1766 BigRational::new_raw(numer << exponent.cast_unsigned(), BigInt::from(1u32))
1767 } else {
1768 BigRational::new_raw(numer, BigInt::from(1u32) << (-exponent).cast_unsigned())
1769 }
1770 }
1771
1772 fn assert_non_finite_input_scalar<T>(result: &Result<T, LaError>) {
1773 let Err(error) = result else {
1774 panic!("expected a non-finite scalar-input error");
1775 };
1776 assert!(matches!(
1777 *error,
1778 LaError::NonFinite {
1779 location: NonFiniteLocation::Scalar,
1780 origin: NonFiniteOrigin::Input,
1781 ..
1782 }
1783 ));
1784 }
1785
1786 fn assert_unrepresentable<T>(
1787 result: &Result<T, LaError>,
1788 expected_index: Option<usize>,
1789 expected_reason: UnrepresentableReason,
1790 ) {
1791 let Err(error) = result else {
1792 panic!("expected an exact-to-f64 conversion error");
1793 };
1794 assert!(matches!(
1795 *error,
1796 LaError::Unrepresentable { index, reason, .. }
1797 if index == expected_index && reason == expected_reason
1798 ));
1799 }
1800
1801 macro_rules! gen_exact_identity_tests {
1806 ($d:literal) => {
1807 paste! {
1808 #[test]
1809 fn [<exact_identity_paths_ $d d>]() {
1810 let matrix = Matrix::<$d>::identity();
1811 let one = BigRational::from_integer(BigInt::from(1));
1812
1813 assert_eq!(matrix.det_exact().unwrap(), one);
1814 assert_eq!(matrix.det_exact_f64().unwrap().to_bits(), 1.0_f64.to_bits());
1815 assert_eq!(
1816 matrix.det_exact_rounded_f64().unwrap().to_bits(),
1817 1.0_f64.to_bits()
1818 );
1819 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Positive);
1820 }
1821 }
1822 };
1823 }
1824
1825 gen_exact_identity_tests!(2);
1826 gen_exact_identity_tests!(3);
1827 gen_exact_identity_tests!(4);
1828 gen_exact_identity_tests!(5);
1829
1830 macro_rules! gen_det_exact_f64_agrees_with_det_direct {
1833 ($d:literal) => {
1834 paste! {
1835 #[test]
1836 fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
1837 let mut rows = [[0.0f64; $d]; $d];
1840 let mut value = 2.0;
1841 for (i, row) in rows.iter_mut().enumerate() {
1842 row[i] = value;
1843 value *= 2.0;
1844 }
1845 let m = Matrix::<$d>::try_from_rows(rows).unwrap();
1846 let exact = m.det_exact_f64().unwrap();
1847 let direct = m.det_direct().unwrap().unwrap();
1848 assert_eq!(exact.to_bits(), direct.to_bits());
1849 }
1850 }
1851 };
1852 }
1853
1854 gen_det_exact_f64_agrees_with_det_direct!(2);
1855 gen_det_exact_f64_agrees_with_det_direct!(3);
1856 gen_det_exact_f64_agrees_with_det_direct!(4);
1857
1858 #[test]
1859 fn det_sign_exact_d0_is_positive() {
1860 assert_eq!(
1861 Matrix::<0>::zero().det_sign_exact(),
1862 DeterminantSign::Positive
1863 );
1864 }
1865
1866 #[test]
1867 fn det_sign_exact_d1_positive() {
1868 let m = Matrix::<1>::try_from_rows([[42.0]]).unwrap();
1869 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
1870 }
1871
1872 #[test]
1873 fn det_sign_exact_d1_negative() {
1874 let m = Matrix::<1>::try_from_rows([[-3.5]]).unwrap();
1875 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1876 }
1877
1878 #[test]
1879 fn det_sign_exact_d1_zero() {
1880 let m = Matrix::<1>::try_from_rows([[0.0]]).unwrap();
1881 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1882 }
1883
1884 #[test]
1885 fn det_sign_exact_singular_duplicate_rows() {
1886 let m = Matrix::<3>::try_from_rows([
1887 [1.0, 2.0, 3.0],
1888 [4.0, 5.0, 6.0],
1889 [1.0, 2.0, 3.0], ])
1891 .unwrap();
1892 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1893 }
1894
1895 #[test]
1896 fn det_sign_exact_singular_linear_combination() {
1897 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]])
1899 .unwrap();
1900 assert_eq!(m.det_sign_exact(), DeterminantSign::Zero);
1901 }
1902
1903 #[test]
1904 fn det_sign_exact_negative_det_row_swap() {
1905 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]])
1907 .unwrap();
1908 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1909 }
1910
1911 #[test]
1912 fn det_sign_exact_negative_det_known() {
1913 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
1915 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1916 }
1917
1918 #[test]
1919 fn det_sign_exact_agrees_with_det_for_spd() {
1920 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]])
1922 .unwrap();
1923 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
1924 assert!(m.det().unwrap() > 0.0);
1925 }
1926
1927 #[test]
1934 fn det_sign_exact_near_singular_perturbation() {
1935 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
1937 [1.0 + perturbation, 2.0, 3.0],
1938 [4.0, 5.0, 6.0],
1939 [7.0, 8.0, 9.0],
1940 ])
1941 .unwrap();
1942 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1944 }
1945
1946 #[test]
1950 fn det_sign_exact_fast_filter_positive_4x4() {
1951 let m = Matrix::<4>::try_from_rows([
1952 [2.0, 1.0, 0.0, 0.0],
1953 [1.0, 3.0, 1.0, 0.0],
1954 [0.0, 1.0, 4.0, 1.0],
1955 [0.0, 0.0, 1.0, 5.0],
1956 ])
1957 .unwrap();
1958 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
1960 }
1961
1962 #[test]
1963 fn det_sign_exact_fast_filter_negative_4x4() {
1964 let m = Matrix::<4>::try_from_rows([
1966 [1.0, 3.0, 1.0, 0.0],
1967 [2.0, 1.0, 0.0, 0.0],
1968 [0.0, 1.0, 4.0, 1.0],
1969 [0.0, 0.0, 1.0, 5.0],
1970 ])
1971 .unwrap();
1972 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
1973 }
1974
1975 #[test]
1976 fn det_sign_exact_subnormal_entries() {
1977 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
1980
1981 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
1982 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
1984 }
1985
1986 #[test]
1987 fn det_sign_exact_falls_back_when_subnormal_rounding_reverses_direct_sign() {
1988 let scale = 2.0_f64.powi(-360);
1989 let matrix = Matrix::<3>::try_from_rows([
1990 [-5.0 * scale, 3.0 * scale, 6.0 * scale],
1991 [0.0, -7.0 * scale, -7.0 * scale],
1992 [2.0 * scale, -3.0 * scale, -4.0 * scale],
1993 ])
1994 .unwrap();
1995
1996 assert_eq!(
1997 matrix.det_direct().unwrap().unwrap().to_bits(),
1998 (-f64::from_bits(1)).to_bits()
1999 );
2000 assert_eq!(matrix.det_errbound(), Ok(None));
2001 assert!(matrix.det_exact().unwrap().is_positive());
2002 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Positive);
2003 }
2004
2005 #[test]
2006 fn det_sign_exact_falls_back_for_bit_exact_underflow_counterexample() {
2007 let matrix = Matrix::<3>::try_from_rows([
2008 [
2009 f64::from_bits(9_218_868_437_227_405_311),
2010 f64::from_bits(13_830_554_455_654_793_216),
2011 0.0,
2012 ],
2013 [
2014 f64::from_bits(6_790_500_848_393_242_208),
2015 f64::from_bits(2_184_621_143_747_520_227),
2016 f64::from_bits(2_187_555_472_467_513_745),
2017 ],
2018 [
2019 0.0,
2020 f64::from_bits(2_184_859_204_554_904_434),
2021 f64::from_bits(2_184_762_736_385_916_910),
2022 ],
2023 ])
2024 .unwrap();
2025
2026 assert!(matrix.det_direct().unwrap().unwrap().is_sign_positive());
2027 assert_eq!(matrix.det_errbound(), Ok(None));
2028 assert!(matrix.det_exact().unwrap().is_negative());
2029 assert_eq!(matrix.det_sign_exact(), DeterminantSign::Negative);
2030 }
2031
2032 #[test]
2033 fn det_sign_exact_pivot_needed_5x5() {
2034 let m = Matrix::<5>::try_from_rows([
2037 [0.0, 1.0, 0.0, 0.0, 0.0],
2038 [1.0, 0.0, 0.0, 0.0, 0.0],
2039 [0.0, 0.0, 1.0, 0.0, 0.0],
2040 [0.0, 0.0, 0.0, 1.0, 0.0],
2041 [0.0, 0.0, 0.0, 0.0, 1.0],
2042 ])
2043 .unwrap();
2044 assert_eq!(m.det_sign_exact(), DeterminantSign::Negative);
2045 }
2046
2047 #[test]
2048 fn det_sign_exact_5x5_known() {
2049 let m = Matrix::<5>::try_from_rows([
2051 [0.0, 1.0, 0.0, 0.0, 0.0],
2052 [1.0, 0.0, 0.0, 0.0, 0.0],
2053 [0.0, 0.0, 0.0, 1.0, 0.0],
2054 [0.0, 0.0, 1.0, 0.0, 0.0],
2055 [0.0, 0.0, 0.0, 0.0, 1.0],
2056 ])
2057 .unwrap();
2058 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2060 }
2061
2062 #[test]
2067 fn det_errbound_d0_is_zero() {
2068 assert_eq!(Matrix::<0>::zero().det_errbound(), Ok(Some(0.0)));
2069 }
2070
2071 #[test]
2072 fn det_errbound_d1_is_zero() {
2073 assert_eq!(
2074 Matrix::<1>::try_from_rows([[42.0]]).unwrap().det_errbound(),
2075 Ok(Some(0.0))
2076 );
2077 }
2078
2079 #[test]
2080 fn det_errbound_d3_non_identity() {
2081 let m = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]])
2083 .unwrap();
2084 let bound = m.det_errbound().unwrap().unwrap();
2085 assert!(bound > 0.0);
2086 }
2087
2088 #[test]
2089 fn det_errbound_d4_non_identity() {
2090 let m = Matrix::<4>::try_from_rows([
2092 [1.0, 0.0, 0.0, 0.0],
2093 [0.0, 2.0, 0.0, 0.0],
2094 [0.0, 0.0, 3.0, 0.0],
2095 [0.0, 0.0, 0.0, 4.0],
2096 ])
2097 .unwrap();
2098 let bound = m.det_errbound().unwrap().unwrap();
2099 assert!(bound > 0.0);
2100 }
2101
2102 #[test]
2107 fn decompose_f64_zero() {
2108 assert_eq!(decompose_f64(0.0), Ok(Component::Zero));
2109 assert_eq!(decompose_f64(-0.0), Ok(Component::Zero));
2110 }
2111
2112 #[test]
2113 fn decompose_f64_one() {
2114 assert_eq!(
2115 decompose_f64(1.0),
2116 Ok(Component::NonZero {
2117 mantissa: NonZeroU64::new(1).unwrap(),
2118 exponent: 0,
2119 is_negative: false,
2120 })
2121 );
2122 }
2123
2124 #[test]
2125 fn decompose_f64_negative() {
2126 assert_eq!(
2127 decompose_f64(-3.5),
2128 Ok(Component::NonZero {
2129 mantissa: NonZeroU64::new(7).unwrap(),
2130 exponent: -1,
2131 is_negative: true,
2132 })
2133 );
2134 }
2135
2136 #[test]
2137 fn decompose_f64_subnormal() {
2138 let tiny = f64::from_bits(1);
2139 assert!(tiny.is_subnormal());
2140 assert_eq!(
2141 decompose_f64(tiny),
2142 Ok(Component::NonZero {
2143 mantissa: NonZeroU64::new(1).unwrap(),
2144 exponent: -1074,
2145 is_negative: false,
2146 })
2147 );
2148 }
2149
2150 #[test]
2151 fn decompose_f64_normalizes_mixed_subnormal_mantissa() {
2152 let value = f64::from_bits(0x000C_0000_0000_0000);
2153 assert!(value.is_subnormal());
2154 assert_eq!(
2155 decompose_f64(value),
2156 Ok(Component::NonZero {
2157 mantissa: NonZeroU64::new(3).unwrap(),
2158 exponent: -1024,
2159 is_negative: false,
2160 })
2161 );
2162 }
2163
2164 #[test]
2165 fn decompose_f64_power_of_two() {
2166 assert_eq!(
2167 decompose_f64(1024.0),
2168 Ok(Component::NonZero {
2169 mantissa: NonZeroU64::new(1).unwrap(),
2170 exponent: 10,
2171 is_negative: false,
2172 })
2173 );
2174 }
2175
2176 #[test]
2177 fn decompose_f64_rejects_nan() {
2178 assert_non_finite_input_scalar(&decompose_f64(f64::NAN));
2179 }
2180
2181 proptest! {
2182 #[test]
2183 fn finite_f64_round_trips_through_exact_decomposition(bits in any::<u64>()) {
2184 let value = f64::from_bits(bits);
2185 prop_assume!(value.is_finite());
2186
2187 if let Ok(Component::NonZero { mantissa, .. }) = decompose_f64(value) {
2188 prop_assert_eq!(mantissa.get() & 1, 1);
2189 }
2190
2191 let exact = f64_to_big_rational(value);
2192 let reconstructed = exact_rational_to_finite_f64(&exact, None);
2193
2194 prop_assert_eq!(reconstructed, Ok(value));
2195 }
2196 }
2197
2198 #[test]
2199 fn wide_low_exponent_value_reports_non_finite_rounded_result() {
2200 let value = (BigInt::from(1_u8) << 2099_u32) - BigInt::from(1_u8);
2202 let result = big_int_exp_to_finite_f64(&value, -1075, None);
2203
2204 assert!(!result.as_ref().unwrap_err().requires_rounding());
2205 assert_unrepresentable(&result, None, UnrepresentableReason::NotFinite);
2206 }
2207
2208 #[test]
2209 fn direct_big_int_rounding_handles_extreme_negative_exponent_without_large_denominator() {
2210 let positive = big_int_exp_ref_to_rounded_f64(&BigInt::from(1_u8), i32::MIN, None).unwrap();
2211 let negative =
2212 big_int_exp_ref_to_rounded_f64(&BigInt::from(-1_i8), i32::MIN, None).unwrap();
2213
2214 assert_eq!(positive.to_bits(), 0.0_f64.to_bits());
2215 assert_eq!(negative.to_bits(), (-0.0_f64).to_bits());
2216 }
2217
2218 proptest! {
2219 #[test]
2220 fn direct_big_int_rounding_matches_rational_oracle(
2221 value in any::<i128>(),
2222 exp in -1200_i32..=1200_i32,
2223 ) {
2224 let value = BigInt::from(value);
2225 let direct = big_int_exp_ref_to_rounded_f64(&value, exp, None);
2226 let exact = big_int_exp_to_big_rational(value, exp);
2227 let oracle = exact_rational_to_rounded_f64(&exact, None);
2228
2229 match (direct, oracle) {
2230 (Ok(actual), Ok(expected)) => {
2231 prop_assert_eq!(actual.to_bits(), expected.to_bits());
2232 }
2233 (Err(actual), Err(expected)) => {
2234 prop_assert_eq!(actual, expected);
2235 }
2236 (actual, expected) => {
2237 prop_assert_eq!(actual, expected);
2238 }
2239 }
2240 }
2241 }
2242
2243 #[test]
2244 fn component_to_big_int_distinguishes_zero_from_nonzero_mantissa() {
2245 let baseline = Component::NonZero {
2246 mantissa: NonZeroU64::new(1).unwrap(),
2247 exponent: 1,
2248 is_negative: false,
2249 };
2250 let positive = Component::NonZero {
2251 mantissa: NonZeroU64::new(3).unwrap(),
2252 exponent: 4,
2253 is_negative: false,
2254 };
2255 let negative = Component::NonZero {
2256 mantissa: NonZeroU64::new(5).unwrap(),
2257 exponent: 3,
2258 is_negative: true,
2259 };
2260
2261 let decomposed =
2262 Decomposed::from_vector_components([Component::Zero, baseline, positive, negative]);
2263 let scale = ScaleExponent::for_decomposed(&decomposed);
2264
2265 assert_eq!(
2266 component_to_big_int(Component::Zero, scale),
2267 BigInt::from(0)
2268 );
2269 assert_eq!(component_to_big_int(positive, scale), BigInt::from(24));
2270 assert_eq!(component_to_big_int(negative, scale), BigInt::from(-20));
2271 }
2272
2273 #[test]
2274 fn decomposed_all_zero_uses_no_sentinel_exponent() {
2275 let decomposed = decompose_proven_finite_matrix(&Matrix::<2>::zero());
2276 assert_eq!(decomposed.min_exponent(), None);
2277
2278 let scale = ScaleExponent::for_decomposed(&decomposed);
2279 assert_eq!(scale, ScaleExponent::ZERO);
2280 assert_eq!(scale.get(), 0);
2281 assert_eq!(
2282 build_big_int_matrix(decomposed.components(), scale),
2283 [
2284 [BigInt::from(0), BigInt::from(0)],
2285 [BigInt::from(0), BigInt::from(0)]
2286 ]
2287 );
2288 }
2289
2290 #[test]
2291 fn matrix_and_rhs_scales_are_derived_independently() {
2292 let tiny = f64::from_bits(1);
2293 let matrix = Matrix::<2>::try_from_rows([[f64::MAX, 0.0], [0.0, 1.0]]).unwrap();
2294 let rhs = Vector::<2>::try_new([tiny, 0.0]).unwrap();
2295 let matrix = decompose_proven_finite_matrix(&matrix);
2296 let rhs = decompose_proven_finite_vector(&rhs);
2297
2298 assert_eq!(matrix.min_exponent(), Some(0));
2299 assert_eq!(rhs.min_exponent(), Some(-1074));
2300
2301 let matrix_scale = ScaleExponent::for_decomposed(&matrix);
2302 let rhs_scale = ScaleExponent::for_decomposed(&rhs);
2303 assert_eq!(matrix_scale.get(), 0);
2304 assert_eq!(matrix_scale.shift_for(0), 0);
2305 assert_eq!(rhs_scale.get(), -1074);
2306 assert_eq!(rhs_scale.shift_for(-1074), 0);
2307 }
2308
2309 proptest! {
2310 #[test]
2311 fn derived_scale_yields_nonnegative_shifts(bits in any::<[u64; 4]>()) {
2312 let values = bits.map(f64::from_bits);
2313 prop_assume!(values.iter().all(|value| value.is_finite()));
2314 let matrix = Matrix::<2>::try_from_rows([
2315 [values[0], values[1]],
2316 [values[2], values[3]],
2317 ]).unwrap();
2318 let decomposed = decompose_proven_finite_matrix(&matrix);
2319 let scale = ScaleExponent::for_decomposed(&decomposed);
2320
2321 for component in decomposed.components().iter().flatten() {
2322 if let Some(exponent) = component.exponent() {
2323 prop_assert!(exponent >= scale.get());
2324 prop_assert_eq!(
2325 scale.shift_for(exponent),
2326 u32::try_from(exponent - scale.get()).unwrap(),
2327 );
2328 }
2329 }
2330 }
2331 }
2332
2333 #[test]
2334 fn determinant_scale_exp_multiplies_dimension_and_min_exponent() {
2335 assert_eq!(determinant_scale_exp::<4>(-1074), Ok(-4296));
2336 }
2337
2338 #[test]
2339 fn determinant_scale_exp_rejects_dimension_too_large_for_i32() {
2340 assert_eq!(
2341 determinant_scale_exp::<{ i32::MAX as usize + 1 }>(-1074),
2342 Err(LaError::DeterminantScaleOverflow {
2343 dim: i32::MAX as usize + 1,
2344 min_exponent: -1074,
2345 })
2346 );
2347 }
2348
2349 #[test]
2350 fn determinant_scale_exp_rejects_exponent_product_overflow() {
2351 assert_eq!(
2352 determinant_scale_exp::<3_000_000>(-1074),
2353 Err(LaError::DeterminantScaleOverflow {
2354 dim: 3_000_000,
2355 min_exponent: -1074,
2356 })
2357 );
2358 }
2359
2360 #[test]
2361 fn negative_exponent_from_magnitude_covers_i32_domain_boundaries() {
2362 assert_eq!(negative_exponent_from_magnitude(0), 0);
2363 assert_eq!(negative_exponent_from_magnitude(1), -1);
2364 assert_eq!(
2365 negative_exponent_from_magnitude(i32::MAX.cast_unsigned().into()),
2366 -i32::MAX
2367 );
2368 assert_eq!(
2369 negative_exponent_from_magnitude(i32::MIN.unsigned_abs().into()),
2370 i32::MIN
2371 );
2372 }
2373
2374 #[test]
2375 #[should_panic(expected = "negative exponent magnitude exceeds the i32 domain")]
2376 fn negative_exponent_from_magnitude_rejects_values_above_i32_domain() {
2377 let _ = negative_exponent_from_magnitude(u64::from(i32::MIN.unsigned_abs()) + 1);
2378 }
2379
2380 #[test]
2385 fn exact_det_int_d0() {
2386 let m = Matrix::<0>::zero();
2387 let (det, exp) = exact_det_int_finite(&m).unwrap();
2388 assert_eq!(det, BigInt::from(1));
2389 assert_eq!(exp, 0);
2390 }
2391
2392 #[test]
2398 fn exact_det_int_d1_cases() {
2399 let cases: &[(f64, i64, i32)] = &[
2400 (7.0, 7, 0), (0.0, 0, 0), (-3.5, -7, -1), (0.5, 1, -1), ];
2406 for &(input, expected_det_int, expected_exp) in cases {
2407 let m = Matrix::<1>::try_from_rows([[input]]).unwrap();
2408 let (det, exp) = exact_det_int_finite(&m).unwrap();
2409 assert_eq!(
2410 det,
2411 BigInt::from(expected_det_int),
2412 "det_int for input={input}"
2413 );
2414 assert_eq!(exp, expected_exp, "exp for input={input}");
2415 }
2416 }
2417
2418 #[test]
2419 fn exact_det_int_d2_known() {
2420 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2422 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2423 let det = big_int_exp_to_big_rational(det_int, total_exp);
2425 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2426 }
2427
2428 #[test]
2429 fn exact_det_int_all_zeros() {
2430 let m = Matrix::<3>::zero();
2431 let (det, _) = exact_det_int_finite(&m).unwrap();
2432 assert_eq!(det, BigInt::from(0));
2433 }
2434
2435 #[test]
2436 fn exact_det_int_fractional_entries() {
2437 let m = Matrix::<2>::try_from_rows([[0.5, 0.25], [1.0, 1.0]]).unwrap();
2440 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2441 let det = big_int_exp_to_big_rational(det_int, total_exp);
2442 assert_eq!(det, BigRational::new(BigInt::from(1), BigInt::from(4)));
2443 }
2444
2445 #[test]
2446 fn exact_det_int_d3_direct_expansion_handles_zero_diagonal() {
2447 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]])
2449 .unwrap();
2450 let (det_int, total_exp) = exact_det_int_finite(&m).unwrap();
2451 let det = big_int_exp_to_big_rational(det_int, total_exp);
2452 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2453 }
2454
2455 #[test]
2460 fn big_int_exp_to_big_rational_zero() {
2461 let r = big_int_exp_to_big_rational(BigInt::from(0), -50);
2462 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
2463 }
2464
2465 #[test]
2466 fn big_int_exp_to_big_rational_positive_exp() {
2467 let r = big_int_exp_to_big_rational(BigInt::from(3), 2);
2469 assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
2470 }
2471
2472 #[test]
2473 fn big_int_exp_to_big_rational_negative_exp_reduced() {
2474 let r = big_int_exp_to_big_rational(BigInt::from(6), -2);
2476 assert_eq!(*r.numer(), BigInt::from(3));
2477 assert_eq!(*r.denom(), BigInt::from(2));
2478 }
2479
2480 #[test]
2481 fn big_int_exp_to_big_rational_negative_exp_reduces_to_integer() {
2482 let r = big_int_exp_to_big_rational(BigInt::from(8), -3);
2484 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
2485 }
2486
2487 #[test]
2488 fn big_int_exp_to_big_rational_negative_exp_already_odd() {
2489 let r = big_int_exp_to_big_rational(BigInt::from(3), -2);
2491 assert_eq!(*r.numer(), BigInt::from(3));
2492 assert_eq!(*r.denom(), BigInt::from(4));
2493 }
2494
2495 #[test]
2496 fn big_int_exp_to_big_rational_negative_value() {
2497 let r = big_int_exp_to_big_rational(BigInt::from(-5), 1);
2499 assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
2500 }
2501
2502 #[test]
2503 fn big_int_exp_to_big_rational_negative_value_with_denominator() {
2504 let r = big_int_exp_to_big_rational(BigInt::from(-3), -2);
2506 assert_eq!(*r.numer(), BigInt::from(-3));
2507 assert_eq!(*r.denom(), BigInt::from(4));
2508 }
2509
2510 #[test]
2515 fn det_exact_d1_returns_entry() {
2516 let det = Matrix::<1>::try_from_rows([[7.0]])
2517 .unwrap()
2518 .det_exact()
2519 .unwrap();
2520 assert_eq!(det, f64_to_big_rational(7.0));
2521 }
2522
2523 #[test]
2524 fn det_exact_d3_direct_expansion_handles_zero_diagonal() {
2525 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]])
2527 .unwrap();
2528 let det = m.det_exact().unwrap();
2529 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2531 }
2532
2533 #[test]
2534 fn det_exact_d3_singular_zero_column_returns_zero() {
2535 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]])
2537 .unwrap();
2538 let det = m.det_exact().unwrap();
2539 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2540 }
2541
2542 #[test]
2543 fn det_sign_exact_overflow_determinant_finite_entries() {
2544 let big = f64::MAX / 2.0;
2548 assert!(big.is_finite());
2549 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2550 .unwrap();
2551 assert_eq!(m.det_sign_exact(), DeterminantSign::Positive);
2553 }
2554
2555 #[test]
2560 fn det_exact_d0_is_one() {
2561 let det = Matrix::<0>::zero().det_exact().unwrap();
2562 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
2563 }
2564
2565 #[test]
2566 fn det_exact_known_2x2() {
2567 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2569 let det = m.det_exact().unwrap();
2570 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2571 }
2572
2573 #[test]
2574 fn det_exact_known_dense_4x4() {
2575 let m = Matrix::<4>::try_from_rows([
2576 [4.0, 1.0, 3.0, 2.0],
2577 [0.0, 5.0, 2.0, 1.0],
2578 [7.0, 2.0, 6.0, 3.0],
2579 [1.0, 8.0, 4.0, 9.0],
2580 ])
2581 .unwrap();
2582
2583 assert_eq!(
2584 m.det_exact(),
2585 Ok(BigRational::from_integer(BigInt::from(92)))
2586 );
2587 }
2588
2589 #[test]
2590 fn det_exact_singular_returns_zero() {
2591 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]])
2593 .unwrap();
2594 let det = m.det_exact().unwrap();
2595 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2596 }
2597
2598 #[test]
2599 fn det_exact_near_singular_perturbation() {
2600 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
2603 [1.0 + perturbation, 2.0, 3.0],
2604 [4.0, 5.0, 6.0],
2605 [7.0, 8.0, 9.0],
2606 ])
2607 .unwrap();
2608 let det = m.det_exact().unwrap();
2609 let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
2611 assert_eq!(det, expected);
2612 }
2613
2614 #[test]
2615 fn det_exact_5x5_permutation() {
2616 let m = Matrix::<5>::try_from_rows([
2618 [0.0, 1.0, 0.0, 0.0, 0.0],
2619 [1.0, 0.0, 0.0, 0.0, 0.0],
2620 [0.0, 0.0, 1.0, 0.0, 0.0],
2621 [0.0, 0.0, 0.0, 1.0, 0.0],
2622 [0.0, 0.0, 0.0, 0.0, 1.0],
2623 ])
2624 .unwrap();
2625 let det = m.det_exact().unwrap();
2626 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2627 }
2628
2629 #[test]
2634 fn det_exact_f64_known_2x2() {
2635 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2636 let det = m.det_exact_f64().unwrap();
2637 assert!((det - (-2.0)).abs() <= f64::EPSILON);
2638 }
2639
2640 #[test]
2641 fn det_exact_f64_overflow_returns_err() {
2642 let big = f64::MAX / 2.0;
2644 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2645 .unwrap();
2646 assert_unrepresentable(&m.det_exact_f64(), None, UnrepresentableReason::NotFinite);
2648 }
2649
2650 #[test]
2651 fn det_exact_rounded_f64_overflow_returns_err() {
2652 let big = f64::MAX / 2.0;
2653 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2654 .unwrap();
2655
2656 assert_unrepresentable(
2657 &m.det_exact_rounded_f64(),
2658 None,
2659 UnrepresentableReason::NotFinite,
2660 );
2661 }
2662
2663 #[test]
2664 fn det_exact_f64_underflow_returns_err_for_nonzero_exact_result() {
2665 let tiny = f64::from_bits(1);
2666 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
2667
2668 assert!(m.det_exact().unwrap().is_positive());
2669 assert_unrepresentable(
2670 &m.det_exact_f64(),
2671 None,
2672 UnrepresentableReason::RequiresRounding,
2673 );
2674 }
2675
2676 #[test]
2677 fn det_exact_f64_rejects_inexact_rounding() {
2678 let m = Matrix::<2>::try_from_rows([[1.0 + f64::EPSILON, 0.0], [0.0, 1.0 - f64::EPSILON]])
2679 .unwrap();
2680
2681 assert_eq!(
2682 m.det_exact(),
2683 Ok(BigRational::new(
2684 (BigInt::from(1_u128) << 104_u32) - BigInt::from(1),
2685 BigInt::from(1_u128 << 104),
2686 ))
2687 );
2688 assert_unrepresentable(
2689 &m.det_exact_f64(),
2690 None,
2691 UnrepresentableReason::RequiresRounding,
2692 );
2693 }
2694
2695 #[test]
2696 fn det_exact_f64_accepts_max_finite_binary64() {
2697 let m = Matrix::<1>::try_from_rows([[f64::MAX]]).unwrap();
2698
2699 assert_eq!(m.det_exact_f64().unwrap().to_bits(), f64::MAX.to_bits());
2700 }
2701
2702 fn arbitrary_rhs<const D: usize>() -> Vector<D> {
2708 let values = [1.0, -2.5, 3.0, 0.25, -4.0];
2709 let mut arr = [0.0f64; D];
2710 for (dst, src) in arr.iter_mut().zip(values.iter()) {
2711 *dst = *src;
2712 }
2713 Vector::<D>::new(arr)
2714 }
2715
2716 macro_rules! gen_solve_exact_tests {
2717 ($d:literal) => {
2718 paste! {
2719 #[test]
2720 fn [<solve_exact_identity_paths_ $d d>]() {
2721 let a = Matrix::<$d>::identity();
2722 let b = arbitrary_rhs::<$d>();
2723 let exact = a.solve_exact(b).unwrap();
2724 let strict_f64 = a.solve_exact_f64(b).unwrap().into_array();
2725
2726 for i in 0..$d {
2727 assert_eq!(exact[i], f64_to_big_rational(b.as_array()[i]));
2728 assert_eq!(strict_f64[i].to_bits(), b.as_array()[i].to_bits());
2729 }
2730 }
2731
2732 #[test]
2733 fn [<solve_exact_singular_ $d d>]() {
2734 let a = Matrix::<$d>::zero();
2736 let b = arbitrary_rhs::<$d>();
2737 assert_matches!(
2738 a.solve_exact(b),
2739 Err(LaError::Singular {
2740 pivot_col: 0,
2741 reason: SingularityReason::Exact,
2742 ..
2743 })
2744 );
2745 }
2746 }
2747 };
2748 }
2749
2750 gen_solve_exact_tests!(2);
2751 gen_solve_exact_tests!(3);
2752 gen_solve_exact_tests!(4);
2753 gen_solve_exact_tests!(5);
2754
2755 macro_rules! gen_solve_exact_f64_agrees_with_lu {
2758 ($d:literal) => {
2759 paste! {
2760 #[test]
2761 fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
2762 let mut rows = [[0.0f64; $d]; $d];
2766 for r in 0..$d {
2767 for c in 0..$d {
2768 rows[r][c] = if r == c {
2769 f64::from($d) + 1.0
2770 } else {
2771 1.0
2772 };
2773 }
2774 }
2775 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2776 let x_true = {
2777 let mut arr = [0.0f64; $d];
2778 for (dst, src) in arr.iter_mut().zip([1.0, -2.0, 3.0, -4.0, 5.0]) {
2779 *dst = src;
2780 }
2781 arr
2782 };
2783 let mut b_arr = [0.0f64; $d];
2784 for i in 0..$d {
2785 let mut sum = 0.0;
2786 for j in 0..$d {
2787 sum = rows[i][j].mul_add(x_true[j], sum);
2788 }
2789 b_arr[i] = sum;
2790 }
2791 let b = Vector::<$d>::new(b_arr);
2792 let exact = a.solve_exact_f64(b).unwrap().into_array();
2793 let lu_sol = a.lu(DEFAULT_SINGULAR_TOL).unwrap()
2794 .solve(b).unwrap().into_array();
2795 for i in 0..$d {
2796 assert_eq!(exact[i].to_bits(), x_true[i].to_bits());
2797 let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
2798 assert!((exact[i] - lu_sol[i]).abs() <= eps);
2799 }
2800 }
2801 }
2802 };
2803 }
2804
2805 gen_solve_exact_f64_agrees_with_lu!(2);
2806 gen_solve_exact_f64_agrees_with_lu!(3);
2807 gen_solve_exact_f64_agrees_with_lu!(4);
2808 gen_solve_exact_f64_agrees_with_lu!(5);
2809
2810 macro_rules! gen_solve_exact_roundtrip_tests {
2816 ($d:literal) => {
2817 paste! {
2818 #[test]
2819 #[expect(
2820 clippy::cast_precision_loss,
2821 reason = "dimensions and indices are at most five and exactly representable as f64"
2822 )]
2823 fn [<solve_exact_roundtrip_ $d d>]() {
2824 let mut rows = [[0.0f64; $d]; $d];
2827 for r in 0..$d {
2828 for c in 0..$d {
2829 rows[r][c] = if r == c {
2830 f64::from($d) + 1.0
2831 } else {
2832 1.0
2833 };
2834 }
2835 }
2836 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2837
2838 let mut x0 = [0.0f64; $d];
2840 for i in 0..$d {
2841 x0[i] = (i + 1) as f64;
2842 }
2843
2844 let mut b_arr = [0.0f64; $d];
2847 for r in 0..$d {
2848 let mut sum = 0.0_f64;
2849 for c in 0..$d {
2850 sum = rows[r][c].mul_add(x0[c], sum);
2851 }
2852 b_arr[r] = sum;
2853 }
2854 let b = Vector::<$d>::new(b_arr);
2855
2856 let x = a.solve_exact(b).unwrap();
2857 for i in 0..$d {
2858 assert_eq!(x[i], f64_to_big_rational(x0[i]));
2859 }
2860 }
2861 }
2862 };
2863 }
2864
2865 gen_solve_exact_roundtrip_tests!(2);
2866 gen_solve_exact_roundtrip_tests!(3);
2867 gen_solve_exact_roundtrip_tests!(4);
2868 gen_solve_exact_roundtrip_tests!(5);
2869
2870 #[test]
2875 fn solve_exact_d0_returns_empty() {
2876 let a = Matrix::<0>::zero();
2877 let b = Vector::<0>::zero();
2878 let x = a.solve_exact(b).unwrap();
2879 assert!(x.is_empty());
2880 }
2881
2882 #[test]
2883 fn solve_exact_known_2x2() {
2884 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2886 let b = Vector::<2>::new([5.0, 11.0]);
2887 let x = a.solve_exact(b).unwrap();
2888 assert_eq!(x[0], BigRational::from_integer(BigInt::from(1)));
2889 assert_eq!(x[1], BigRational::from_integer(BigInt::from(2)));
2890 }
2891
2892 #[test]
2893 fn solve_exact_pivoting_needed() {
2894 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]])
2896 .unwrap();
2897 let b = Vector::<3>::new([2.0, 3.0, 4.0]);
2898 let x = a.solve_exact(b).unwrap();
2899 assert_eq!(x[0], f64_to_big_rational(3.0));
2901 assert_eq!(x[1], f64_to_big_rational(2.0));
2902 assert_eq!(x[2], f64_to_big_rational(4.0));
2903 }
2904
2905 #[test]
2906 fn solve_exact_fractional_result() {
2907 let a = Matrix::<2>::try_from_rows([[2.0, 1.0], [1.0, 3.0]]).unwrap();
2909 let b = Vector::<2>::new([1.0, 1.0]);
2910 let x = a.solve_exact(b).unwrap();
2911 assert_eq!(x[0], BigRational::new(BigInt::from(2), BigInt::from(5)));
2912 assert_eq!(x[1], BigRational::new(BigInt::from(1), BigInt::from(5)));
2913 }
2914
2915 #[test]
2916 fn solve_exact_singular_duplicate_rows() {
2917 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]])
2918 .unwrap();
2919 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
2920 assert_matches!(
2921 a.solve_exact(b),
2922 Err(LaError::Singular {
2923 reason: SingularityReason::Exact,
2924 ..
2925 })
2926 );
2927 }
2928
2929 #[test]
2930 fn solve_exact_5x5_permutation() {
2931 let a = Matrix::<5>::try_from_rows([
2933 [0.0, 1.0, 0.0, 0.0, 0.0],
2934 [1.0, 0.0, 0.0, 0.0, 0.0],
2935 [0.0, 0.0, 1.0, 0.0, 0.0],
2936 [0.0, 0.0, 0.0, 1.0, 0.0],
2937 [0.0, 0.0, 0.0, 0.0, 1.0],
2938 ])
2939 .unwrap();
2940 let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
2941 let x = a.solve_exact(b).unwrap();
2942 assert_eq!(x[0], f64_to_big_rational(20.0));
2943 assert_eq!(x[1], f64_to_big_rational(10.0));
2944 assert_eq!(x[2], f64_to_big_rational(30.0));
2945 assert_eq!(x[3], f64_to_big_rational(40.0));
2946 assert_eq!(x[4], f64_to_big_rational(50.0));
2947 }
2948
2949 macro_rules! gen_solve_exact_large_finite_entries_tests {
2955 ($d:literal) => {
2956 paste! {
2957 #[test]
2958 fn [<solve_exact_large_finite_entries_ $d d>]() {
2959 let big = f64::MAX / 2.0;
2960 assert!(big.is_finite());
2961 let mut rows = [[0.0f64; $d]; $d];
2963 for i in 0..$d {
2964 rows[i][i] = big;
2965 }
2966 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2967 let mut b_arr = [big; $d];
2969 b_arr[$d - 1] = 0.0;
2970 let b = Vector::<$d>::new(b_arr);
2971 let x = a.solve_exact(b).unwrap();
2972 for i in 0..($d - 1) {
2973 assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
2974 }
2975 assert_eq!(x[$d - 1], BigRational::from_integer(BigInt::from(0)));
2976 }
2977 }
2978 };
2979 }
2980
2981 gen_solve_exact_large_finite_entries_tests!(2);
2982 gen_solve_exact_large_finite_entries_tests!(3);
2983 gen_solve_exact_large_finite_entries_tests!(4);
2984 gen_solve_exact_large_finite_entries_tests!(5);
2985
2986 macro_rules! gen_solve_exact_mixed_magnitude_entries_tests {
2993 ($d:literal) => {
2994 paste! {
2995 #[test]
2996 fn [<solve_exact_mixed_magnitude_entries_ $d d>]() {
2997 let tiny = f64::MIN_POSITIVE; let huge = 1.0e100_f64;
2999 let mut rows = [[0.0f64; $d]; $d];
3001 let mut b_arr = [0.0f64; $d];
3002 for i in 0..$d {
3003 let val = if i % 2 == 0 { huge } else { tiny };
3004 rows[i][i] = val;
3005 b_arr[i] = val;
3006 }
3007 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3008 let b = Vector::<$d>::new(b_arr);
3009 let x = a.solve_exact(b).unwrap();
3010 for i in 0..$d {
3011 assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
3012 }
3013 }
3014 }
3015 };
3016 }
3017
3018 gen_solve_exact_mixed_magnitude_entries_tests!(2);
3019 gen_solve_exact_mixed_magnitude_entries_tests!(3);
3020 gen_solve_exact_mixed_magnitude_entries_tests!(4);
3021 gen_solve_exact_mixed_magnitude_entries_tests!(5);
3022
3023 #[test]
3024 fn solve_exact_restores_independent_matrix_and_rhs_scales() {
3025 let large = 2.0_f64.powi(500);
3026 let tiny = 2.0_f64.powi(-1000);
3027
3028 let large_matrix = Matrix::<2>::try_from_rows([[large, 0.0], [0.0, large]]).unwrap();
3029 let tiny_rhs = Vector::<2>::new([tiny, -2.0 * tiny]);
3030 let small_solution = large_matrix.solve_exact(tiny_rhs).unwrap();
3031 assert_eq!(
3032 small_solution[0],
3033 BigRational::new(BigInt::from(1_u8), BigInt::from(1_u8) << 1500_u32)
3034 );
3035 assert_eq!(
3036 small_solution[1],
3037 BigRational::new(BigInt::from(-1_i8), BigInt::from(1_u8) << 1499_u32)
3038 );
3039
3040 let tiny_matrix = Matrix::<1>::try_from_rows([[tiny]]).unwrap();
3041 let large_rhs = Vector::<1>::new([large]);
3042 let large_solution = tiny_matrix.solve_exact(large_rhs).unwrap();
3043 assert_eq!(
3044 large_solution[0],
3045 BigRational::from_integer(BigInt::from(1_u8) << 1500_u32)
3046 );
3047 }
3048
3049 macro_rules! gen_solve_exact_subnormal_rhs_tests {
3055 ($d:literal) => {
3056 paste! {
3057 #[test]
3058 #[expect(
3059 clippy::cast_precision_loss,
3060 reason = "indices are at most five and exactly representable as f64"
3061 )]
3062 fn [<solve_exact_subnormal_rhs_ $d d>]() {
3063 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3065 let a = Matrix::<$d>::identity();
3066 let mut b_arr = [0.0f64; $d];
3068 for i in 0..$d {
3069 b_arr[i] = (i + 1) as f64 * tiny;
3070 assert!(b_arr[i].is_subnormal());
3071 }
3072 let b = Vector::<$d>::new(b_arr);
3073 let x = a.solve_exact(b).unwrap();
3074 for i in 0..$d {
3075 assert_eq!(x[i], f64_to_big_rational((i + 1) as f64 * tiny));
3076 }
3077 }
3078 }
3079 };
3080 }
3081
3082 gen_solve_exact_subnormal_rhs_tests!(2);
3083 gen_solve_exact_subnormal_rhs_tests!(3);
3084 gen_solve_exact_subnormal_rhs_tests!(4);
3085 gen_solve_exact_subnormal_rhs_tests!(5);
3086
3087 macro_rules! gen_solve_exact_pivot_swap_fractional_tests {
3097 ($d:literal) => {
3098 paste! {
3099 #[test]
3100 #[expect(
3101 clippy::cast_precision_loss,
3102 reason = "indices and test offsets are small integers exactly representable as f64"
3103 )]
3104 fn [<solve_exact_pivot_swap_with_fractional_result_ $d d>]() {
3105 let mut rows = [[0.0f64; $d]; $d];
3108 rows[0][1] = 1.0;
3109 rows[1][0] = 2.0;
3110 rows[1][1] = 1.0;
3111 for (i, row) in rows.iter_mut().enumerate().skip(2) {
3113 row[i] = 1.0;
3114 }
3115 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3116 let mut b_arr = [0.0f64; $d];
3119 b_arr[0] = 3.0;
3120 b_arr[1] = 4.0;
3121 for (i, value) in b_arr.iter_mut().enumerate().skip(2) {
3122 *value = (i + 10) as f64;
3123 }
3124 let b = Vector::<$d>::new(b_arr);
3125 let x = a.solve_exact(b).unwrap();
3126 assert_eq!(x[0], BigRational::new(BigInt::from(1), BigInt::from(2)));
3127 assert_eq!(x[1], BigRational::from_integer(BigInt::from(3)));
3128 for (i, value) in x.iter().enumerate().skip(2) {
3129 assert_eq!(value, &f64_to_big_rational((i + 10) as f64));
3130 }
3131 }
3132 }
3133 };
3134 }
3135
3136 gen_solve_exact_pivot_swap_fractional_tests!(2);
3137 gen_solve_exact_pivot_swap_fractional_tests!(3);
3138 gen_solve_exact_pivot_swap_fractional_tests!(4);
3139 gen_solve_exact_pivot_swap_fractional_tests!(5);
3140
3141 macro_rules! gen_solve_exact_mid_pivot_swap_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_mid_pivot_swap_ $d d>]() {
3158 let mut rows = [[0.0f64; $d]; $d];
3159 rows[0][0] = 1.0; rows[0][1] = 2.0; rows[0][2] = 3.0;
3160 rows[1][2] = 4.0;
3162 rows[2][1] = 5.0; rows[2][2] = 6.0;
3163 for (i, row) in rows.iter_mut().enumerate().skip(3) {
3165 row[i] = 1.0;
3166 }
3167 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3168 let mut b_arr = [0.0f64; $d];
3169 b_arr[0] = 6.0;
3170 b_arr[1] = 7.0;
3171 b_arr[2] = 8.0;
3172 for (i, value) in b_arr.iter_mut().enumerate().skip(3) {
3173 *value = (i + 10) as f64;
3174 }
3175 let b = Vector::<$d>::new(b_arr);
3176 let x = a.solve_exact(b).unwrap();
3177 assert_eq!(x[0], BigRational::new(BigInt::from(7), BigInt::from(4)));
3179 assert_eq!(x[1], BigRational::new(BigInt::from(-1), BigInt::from(2)));
3180 assert_eq!(x[2], BigRational::new(BigInt::from(7), BigInt::from(4)));
3181 for (i, value) in x.iter().enumerate().skip(3) {
3182 assert_eq!(value, &f64_to_big_rational((i + 10) as f64));
3183 }
3184 }
3185 }
3186 };
3187 }
3188
3189 gen_solve_exact_mid_pivot_swap_tests!(3);
3190 gen_solve_exact_mid_pivot_swap_tests!(4);
3191 gen_solve_exact_mid_pivot_swap_tests!(5);
3192
3193 macro_rules! gen_solve_exact_singular_rank_deficient_tests {
3201 ($d:literal) => {
3202 paste! {
3203 #[test]
3204 fn [<solve_exact_singular_rank_deficient_ $d d>]() {
3205 let mut rows = [[0.0f64; $d]; $d];
3206 for i in 0..($d - 1) {
3207 rows[i][i] = 1.0;
3208 rows[$d - 1][i] = 1.0;
3209 }
3210 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3212 let b = Vector::<$d>::new([1.0; $d]);
3213 assert_matches!(
3214 a.solve_exact(b),
3215 Err(LaError::Singular {
3216 pivot_col,
3217 reason: SingularityReason::Exact,
3218 ..
3219 }) if pivot_col == $d - 1
3220 );
3221 }
3222 }
3223 };
3224 }
3225
3226 gen_solve_exact_singular_rank_deficient_tests!(2);
3227 gen_solve_exact_singular_rank_deficient_tests!(3);
3228 gen_solve_exact_singular_rank_deficient_tests!(4);
3229 gen_solve_exact_singular_rank_deficient_tests!(5);
3230
3231 macro_rules! gen_solve_exact_zero_rhs_tests {
3237 ($d:literal) => {
3238 paste! {
3239 #[test]
3240 fn [<solve_exact_zero_rhs_ $d d>]() {
3241 let mut rows = [[1.0f64; $d]; $d];
3243 for i in 0..$d {
3244 rows[i][i] = f64::from($d) + 1.0;
3245 }
3246 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
3247 let b = Vector::<$d>::zero();
3248 let x = a.solve_exact(b).unwrap();
3249 for xi in &x {
3250 assert_eq!(*xi, BigRational::from_integer(BigInt::from(0)));
3251 }
3252 }
3253 }
3254 };
3255 }
3256
3257 gen_solve_exact_zero_rhs_tests!(2);
3258 gen_solve_exact_zero_rhs_tests!(3);
3259 gen_solve_exact_zero_rhs_tests!(4);
3260 gen_solve_exact_zero_rhs_tests!(5);
3261
3262 fn big_rational_matvec<const D: usize>(
3275 a: &Matrix<D>,
3276 x: &[BigRational; D],
3277 ) -> [BigRational; D] {
3278 from_fn(|i| {
3279 let mut sum = BigRational::from_integer(BigInt::from(0));
3280 for (aij, xj) in a.as_rows()[i].iter().zip(x.iter()) {
3281 sum += f64_to_big_rational(*aij) * xj;
3282 }
3283 sum
3284 })
3285 }
3286
3287 fn hilbert<const D: usize>() -> Matrix<D> {
3288 let rows = from_fn(|r| from_fn(|c| 1.0 / f64::from(u32::try_from(r + c + 1).unwrap())));
3289 Matrix::<D>::try_from_rows(rows).unwrap()
3290 }
3291
3292 #[test]
3300 fn solve_exact_near_singular_3x3_integer_x0() {
3301 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::try_from_rows([
3303 [1.0 + perturbation, 2.0, 3.0],
3304 [4.0, 5.0, 6.0],
3305 [7.0, 8.0, 9.0],
3306 ])
3307 .unwrap();
3308 let b = Vector::<3>::new([6.0 + perturbation, 15.0, 24.0]);
3309 let x = a.solve_exact(b).unwrap();
3310 let one = BigRational::from_integer(BigInt::from(1));
3311 assert_eq!(x[0], one);
3312 assert_eq!(x[1], one);
3313 assert_eq!(x[2], one);
3314 }
3315
3316 #[test]
3323 fn solve_exact_large_entries_3x3_unit_vector() {
3324 let big = f64::MAX / 2.0;
3325 assert!(big.is_finite());
3326 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
3327 .unwrap();
3328 let b = Vector::<3>::new([big, 1.0, 1.0]);
3329 let x = a.solve_exact(b).unwrap();
3330 let zero = BigRational::from_integer(BigInt::from(0));
3331 let one = BigRational::from_integer(BigInt::from(1));
3332 assert_eq!(x[0], one);
3333 assert_eq!(x[1], zero);
3334 assert_eq!(x[2], zero);
3335 }
3336
3337 #[test]
3343 fn det_sign_exact_large_entries_3x3_positive() {
3344 let big = f64::MAX / 2.0;
3345 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
3346 .unwrap();
3347 assert_matches!(
3350 a.det_direct(),
3351 Err(LaError::NonFinite {
3352 location: NonFiniteLocation::Scalar,
3353 origin: NonFiniteOrigin::Computation {
3354 operation: ArithmeticOperation::Determinant,
3355 ..
3356 },
3357 ..
3358 })
3359 );
3360 assert_eq!(a.det_sign_exact(), DeterminantSign::Positive);
3361 assert!(a.det_exact().unwrap().is_positive());
3365 assert_unrepresentable(&a.det_exact_f64(), None, UnrepresentableReason::NotFinite);
3366 }
3367
3368 macro_rules! gen_det_sign_exact_hilbert_positive_tests {
3377 ($d:literal) => {
3378 paste! {
3379 #[test]
3380 fn [<det_sign_exact_hilbert_positive_ $d d>]() {
3381 let h = hilbert::<$d>();
3382 assert_eq!(h.det_sign_exact(), DeterminantSign::Positive);
3383 }
3384 }
3385 };
3386 }
3387
3388 gen_det_sign_exact_hilbert_positive_tests!(2);
3389 gen_det_sign_exact_hilbert_positive_tests!(3);
3390 gen_det_sign_exact_hilbert_positive_tests!(4);
3391 gen_det_sign_exact_hilbert_positive_tests!(5);
3392
3393 macro_rules! gen_solve_exact_hilbert_residual_tests {
3400 ($d:literal) => {
3401 paste! {
3402 #[test]
3403 fn [<solve_exact_hilbert_residual_ $d d>]() {
3404 let h = hilbert::<$d>();
3405 let mut b_arr = [0.0f64; $d];
3408 for i in 0usize..$d {
3409 let sign = if i.is_multiple_of(2) { 1.0 } else { -1.0 };
3410 b_arr[i] = sign * f64::from(u32::try_from(i + 1).unwrap());
3411 }
3412 let b = Vector::<$d>::new(b_arr);
3413 let x = h.solve_exact(b).unwrap();
3414 let ax = big_rational_matvec(&h, &x);
3415 for i in 0..$d {
3416 assert_eq!(ax[i], f64_to_big_rational(b_arr[i]));
3417 }
3418 }
3419 }
3420 };
3421 }
3422
3423 gen_solve_exact_hilbert_residual_tests!(2);
3424 gen_solve_exact_hilbert_residual_tests!(3);
3425 gen_solve_exact_hilbert_residual_tests!(4);
3426 gen_solve_exact_hilbert_residual_tests!(5);
3427
3428 #[test]
3433 fn solve_exact_f64_known_2x2() {
3434 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
3435 let b = Vector::<2>::new([5.0, 11.0]);
3436 let x = a.solve_exact_f64(b).unwrap().into_array();
3437 assert!((x[0] - 1.0).abs() <= f64::EPSILON);
3438 assert!((x[1] - 2.0).abs() <= f64::EPSILON);
3439 }
3440
3441 #[test]
3442 fn solve_exact_f64_overflow_returns_err() {
3443 let big = f64::MAX / 2.0;
3446 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
3447 let b = Vector::<2>::new([big, big]);
3448 assert_unrepresentable(
3449 &a.solve_exact_f64(b),
3450 Some(0),
3451 UnrepresentableReason::NotFinite,
3452 );
3453 }
3454
3455 #[test]
3456 fn solve_exact_f64_huge_non_dyadic_component_returns_not_finite() {
3457 let a = Matrix::<1>::try_from_rows([[3.0 * f64::MIN_POSITIVE]]).unwrap();
3458 let b = Vector::<1>::new([f64::MAX]);
3459
3460 assert_unrepresentable(
3461 &a.solve_exact_f64(b),
3462 Some(0),
3463 UnrepresentableReason::NotFinite,
3464 );
3465 }
3466
3467 #[test]
3468 fn solve_exact_rounded_f64_overflow_returns_err() {
3469 let big = f64::MAX / 2.0;
3470 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
3471 let b = Vector::<2>::new([big, big]);
3472
3473 assert_unrepresentable(
3474 &a.solve_exact_rounded_f64(b),
3475 Some(0),
3476 UnrepresentableReason::NotFinite,
3477 );
3478 }
3479
3480 #[test]
3481 fn solve_exact_f64_underflow_returns_err_for_nonzero_exact_component() {
3482 let tiny = f64::from_bits(1);
3483 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3484 let b = Vector::<1>::new([tiny]);
3485
3486 assert_unrepresentable(
3487 &a.solve_exact_f64(b),
3488 Some(0),
3489 UnrepresentableReason::RequiresRounding,
3490 );
3491 }
3492
3493 #[test]
3494 fn solve_exact_f64_accepts_smallest_subnormal_result() {
3495 let tiny = f64::from_bits(1);
3496 let a = Matrix::<1>::identity();
3497 let b = Vector::<1>::new([tiny]);
3498
3499 assert_eq!(
3500 a.solve_exact_f64(b).unwrap().into_array()[0].to_bits(),
3501 tiny.to_bits()
3502 );
3503 }
3504
3505 #[test]
3510 fn bareiss_solve_d1() {
3511 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3512 let b = Vector::<1>::new([6.0]);
3513 let x = a.solve_exact(b).unwrap();
3514 assert_eq!(x[0], f64_to_big_rational(3.0));
3515 }
3516
3517 #[test]
3518 fn bareiss_solve_singular_column_all_zero() {
3519 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]])
3520 .unwrap();
3521 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
3522 assert_matches!(
3523 a.solve_exact(b),
3524 Err(LaError::Singular {
3525 pivot_col: 1,
3526 reason: SingularityReason::Exact,
3527 ..
3528 })
3529 );
3530 }
3531
3532 #[test]
3537 fn f64_to_big_rational_positive_zero() {
3538 let r = f64_to_big_rational(0.0);
3539 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
3540 }
3541
3542 #[test]
3543 fn f64_to_big_rational_negative_zero() {
3544 let r = f64_to_big_rational(-0.0);
3545 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
3546 }
3547
3548 #[test]
3549 fn f64_to_big_rational_one() {
3550 let r = f64_to_big_rational(1.0);
3551 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
3552 }
3553
3554 #[test]
3555 fn f64_to_big_rational_negative_one() {
3556 let r = f64_to_big_rational(-1.0);
3557 assert_eq!(r, BigRational::from_integer(BigInt::from(-1)));
3558 }
3559
3560 #[test]
3561 fn f64_to_big_rational_half() {
3562 let r = f64_to_big_rational(0.5);
3563 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(2)));
3564 }
3565
3566 #[test]
3567 fn f64_to_big_rational_quarter() {
3568 let r = f64_to_big_rational(0.25);
3569 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(4)));
3570 }
3571
3572 #[test]
3573 fn f64_to_big_rational_negative_three_and_a_half() {
3574 let r = f64_to_big_rational(-3.5);
3576 assert_eq!(r, BigRational::new(BigInt::from(-7), BigInt::from(2)));
3577 }
3578
3579 #[test]
3580 fn f64_to_big_rational_integer() {
3581 let r = f64_to_big_rational(42.0);
3582 assert_eq!(r, BigRational::from_integer(BigInt::from(42)));
3583 }
3584
3585 #[test]
3586 fn f64_to_big_rational_power_of_two() {
3587 let r = f64_to_big_rational(1024.0);
3588 assert_eq!(r, BigRational::from_integer(BigInt::from(1024)));
3589 }
3590
3591 #[test]
3592 fn f64_to_big_rational_subnormal() {
3593 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3595 let r = f64_to_big_rational(tiny);
3596 assert_eq!(
3598 r,
3599 BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
3600 );
3601 }
3602
3603 #[test]
3604 fn f64_to_big_rational_already_lowest_terms() {
3605 let r = f64_to_big_rational(0.5);
3607 assert_eq!(*r.numer(), BigInt::from(1));
3608 assert_eq!(*r.denom(), BigInt::from(2));
3609 }
3610
3611 #[test]
3612 fn f64_to_big_rational_round_trip() {
3613 let values = [
3616 0.0,
3617 1.0,
3618 -1.0,
3619 0.5,
3620 0.25,
3621 0.1,
3622 42.0,
3623 -3.5,
3624 1e10,
3625 1e-10,
3626 f64::MAX / 2.0,
3627 f64::MIN_POSITIVE,
3628 5e-324,
3629 ];
3630 for &v in &values {
3631 let r = f64_to_big_rational(v);
3632 let back = r.to_f64().expect("round-trip to_f64 failed");
3633 assert_eq!(
3634 v.to_bits(),
3635 back.to_bits(),
3636 "round-trip failed for {v}: got {back}"
3637 );
3638 }
3639 }
3640
3641 #[test]
3642 fn decompose_f64_rejects_non_finite_inputs() {
3643 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
3644 assert_non_finite_input_scalar(&decompose_f64(value));
3645 }
3646 }
3647}