@@ -113,12 +113,22 @@ impl<const D: usize> Matrix<D> {
113113
114114 /// Infinity norm (maximum absolute row sum).
115115 ///
116+ /// # Non-finite handling
117+ /// If any entry is NaN, the result is NaN. NaN is detected explicitly
118+ /// because a naive `row_sum > max_row_sum` comparison silently skips NaN
119+ /// rows (every ordered comparison against NaN is `false`). If any entry
120+ /// is infinite (and no entry is NaN), the result is `+∞`.
121+ ///
116122 /// # Examples
117123 /// ```
118124 /// use la_stack::prelude::*;
119125 ///
120126 /// let m = Matrix::<2>::from_rows([[1.0, -2.0], [3.0, 4.0]]);
121127 /// assert!((m.inf_norm() - 7.0).abs() <= 1e-12);
128+ ///
129+ /// // NaN entries propagate to the norm.
130+ /// let nan = Matrix::<2>::from_rows([[f64::NAN, 1.0], [2.0, 3.0]]);
131+ /// assert!(nan.inf_norm().is_nan());
122132 /// ```
123133 #[ inline]
124134 #[ must_use]
@@ -127,6 +137,13 @@ impl<const D: usize> Matrix<D> {
127137
128138 for row in & self . rows {
129139 let row_sum: f64 = row. iter ( ) . map ( |& x| x. abs ( ) ) . sum ( ) ;
140+ // Propagate NaN explicitly: `f64::max` drops NaN (IEEE 754 `maxNum`)
141+ // and `f64::maximum` (IEEE 754-2019 `maximum`) is still unstable,
142+ // so we short-circuit on NaN instead.
143+ if row_sum. is_nan ( ) {
144+ cold_path ( ) ;
145+ return f64:: NAN ;
146+ }
130147 if row_sum > max_row_sum {
131148 max_row_sum = row_sum;
132149 }
@@ -758,4 +775,48 @@ mod tests {
758775 // D=5 has no fast filter
759776 assert_eq ! ( Matrix :: <5 >:: identity( ) . det_errbound( ) , None ) ;
760777 }
778+
779+ // === inf_norm NaN / Inf propagation (regression tests for #85) ===
780+
781+ macro_rules! gen_inf_norm_nonfinite_tests {
782+ ( $d: literal) => {
783+ paste! {
784+ #[ test]
785+ fn [ <inf_norm_all_nan_returns_nan_ $d d>] ( ) {
786+ // Before the fix, `NaN > max_row_sum` was always false, so a
787+ // matrix full of NaN silently produced inf_norm == 0.0.
788+ let m = Matrix :: <$d>:: from_rows( [ [ f64 :: NAN ; $d] ; $d] ) ;
789+ assert!( m. inf_norm( ) . is_nan( ) ) ;
790+ }
791+
792+ #[ test]
793+ fn [ <inf_norm_single_nan_entry_returns_nan_ $d d>] ( ) {
794+ // A single NaN entry must contaminate its row sum and
795+ // propagate through `f64::maximum` to the final result.
796+ let mut rows = [ [ 0.0f64 ; $d] ; $d] ;
797+ rows[ 0 ] [ 0 ] = f64 :: NAN ;
798+ rows[ $d - 1 ] [ $d - 1 ] = 1.0 ;
799+ let m = Matrix :: <$d>:: from_rows( rows) ;
800+ assert!( m. inf_norm( ) . is_nan( ) ) ;
801+ }
802+
803+ #[ test]
804+ fn [ <inf_norm_infinity_entry_propagates_ $d d>] ( ) {
805+ // Infinity entries should propagate to +∞ via the row sum,
806+ // not be silently dropped. The norm is a sum of absolute
807+ // values, so any infinite result is necessarily +∞.
808+ let mut rows = [ [ 0.0f64 ; $d] ; $d] ;
809+ rows[ 0 ] [ 0 ] = f64 :: INFINITY ;
810+ let m = Matrix :: <$d>:: from_rows( rows) ;
811+ let norm = m. inf_norm( ) ;
812+ assert!( norm. is_infinite( ) && norm. is_sign_positive( ) ) ;
813+ }
814+ }
815+ } ;
816+ }
817+
818+ gen_inf_norm_nonfinite_tests ! ( 2 ) ;
819+ gen_inf_norm_nonfinite_tests ! ( 3 ) ;
820+ gen_inf_norm_nonfinite_tests ! ( 4 ) ;
821+ gen_inf_norm_nonfinite_tests ! ( 5 ) ;
761822}
0 commit comments