From c8400c9565d12ddb5a9f49512e9b5be0cf7e9d11 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Fri, 14 Aug 2026 17:04:31 +0900 Subject: [PATCH] Fix inverted is_all_nan / is_any_nan / is_all_infinite / is_any_infinite The boolean_ops! macro in src/numeric/impl_float_maths.rs had an inverted $all predicate: Zip::from(self).all(|&elt| !elt.$func()) tests "all elements are NOT NaN", the opposite of the documented "true if all elements are NaN". $any was defined as !self.$all(), so the double negation made is_any_* appear correct while is_all_* returned true for an all-finite array. Correct both predicates together (fixing only $all would break the is_any_* methods that ride on the double negation): $all -> Zip::from(self).all(|&elt| elt.$func()) $any -> Zip::from(self).any(|&elt| elt.$func()) Empty-array semantics are preserved (all -> true, any -> false). Fixes #1612 --- src/numeric/impl_float_maths.rs | 4 +-- tests/nan_all_repro.rs | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/nan_all_repro.rs diff --git a/src/numeric/impl_float_maths.rs b/src/numeric/impl_float_maths.rs index 6d6ebce52..edcc03c01 100644 --- a/src/numeric/impl_float_maths.rs +++ b/src/numeric/impl_float_maths.rs @@ -20,12 +20,12 @@ macro_rules! boolean_ops { $(#[$meta2])* #[must_use = "method returns a new boolean value and does not mutate the original value"] pub fn $all(&self) -> bool { - $crate::Zip::from(self).all(|&elt| !elt.$func()) + $crate::Zip::from(self).all(|&elt| elt.$func()) } $(#[$meta3])* #[must_use = "method returns a new boolean value and does not mutate the original value"] pub fn $any(&self) -> bool { - !self.$all() + $crate::Zip::from(self).any(|&elt| elt.$func()) } }; } diff --git a/tests/nan_all_repro.rs b/tests/nan_all_repro.rs new file mode 100644 index 000000000..8cf65e6d5 --- /dev/null +++ b/tests/nan_all_repro.rs @@ -0,0 +1,47 @@ +// Regression test for https://github.com/rust-ndarray/ndarray/issues/1612 +// +// `is_all_nan`, `is_any_nan`, `is_all_infinite`, and `is_any_infinite` were +// inverted: the `$all` predicate tested `!elt.$func()` ("all NOT nan") and the +// `$any` predicate rode on the double negation, so `is_all_nan` returned `true` +// for an all-finite array. These tests pin the correct behavior for both the +// NaN and infinite families. + +use ndarray::array; + +#[test] +fn is_all_nan_and_is_any_nan() +{ + // All finite: neither all nor any element is NaN. + let all_finite = array![1.0_f64, 2.0, 3.0]; + assert!(!all_finite.is_all_nan()); + assert!(!all_finite.is_any_nan()); + + // All NaN: every element is NaN. + let all_nan = array![f64::NAN, f64::NAN, f64::NAN]; + assert!(all_nan.is_all_nan()); + assert!(all_nan.is_any_nan()); + + // Mixed: not all elements are NaN, but at least one is. + let mixed = array![1.0_f64, f64::NAN, 3.0]; + assert!(!mixed.is_all_nan()); + assert!(mixed.is_any_nan()); +} + +#[test] +fn is_all_infinite_and_is_any_infinite() +{ + // All finite: neither all nor any element is infinite. + let all_finite = array![1.0_f64, 2.0, 3.0]; + assert!(!all_finite.is_all_infinite()); + assert!(!all_finite.is_any_infinite()); + + // All infinite: every element is infinite. + let all_inf = array![f64::INFINITY, f64::NEG_INFINITY, f64::INFINITY]; + assert!(all_inf.is_all_infinite()); + assert!(all_inf.is_any_infinite()); + + // Mixed: not all elements are infinite, but at least one is. + let mixed = array![1.0_f64, f64::INFINITY, 3.0]; + assert!(!mixed.is_all_infinite()); + assert!(mixed.is_any_infinite()); +}