Skip to content

Commit b60a734

Browse files
authored
Merge pull request #92 from acgetchell/fix/85-inf-norm-nan-propagation
fix: propagate NaN in Matrix::inf_norm (#85)
2 parents 56973bc + 16ffa45 commit b60a734

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

src/matrix.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)