From 7a698be60580b1e9d68620e4ca3a8517eed654f5 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:11:20 -0500 Subject: [PATCH] min,max: Add tests for signaling NaNs and update documentation We do handle signaling NaNs properly, with the exception of raising exceptions as IEEE 754 requires. Add tests to this effect for `fmin`, `fminimum`, `fminimum_num`, and the max variants. --- .../libm/src/math/fmin_fmax.rs | 109 ++++++++++++-- .../libm/src/math/fminimum_fmaximum.rs | 137 +++++++++++++----- .../libm/src/math/fminimum_fmaximum_num.rs | 115 +++++++++++++-- .../libm/src/math/generic/fmax.rs | 10 +- .../libm/src/math/generic/fmaximum.rs | 3 +- .../libm/src/math/generic/fmaximum_num.rs | 3 +- .../libm/src/math/generic/fmin.rs | 10 +- .../libm/src/math/generic/fminimum.rs | 3 +- .../libm/src/math/generic/fminimum_num.rs | 3 +- 9 files changed, 330 insertions(+), 63 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/fmin_fmax.rs b/library/compiler-builtins/libm/src/math/fmin_fmax.rs index c4c1b0435dd2..ead9e6599f1b 100644 --- a/library/compiler-builtins/libm/src/math/fmin_fmax.rs +++ b/library/compiler-builtins/libm/src/math/fmin_fmax.rs @@ -77,9 +77,12 @@ pub fn fmaxf128(x: f128, y: f128) -> f128 { #[cfg(test)] mod tests { use super::*; + use crate::support::hex_float::Hexi; use crate::support::{Float, Hexf}; fn fmin_spec_test(f: impl Fn(F, F) -> F) { + // Note that (YaN, sNaN) and (sNaN, YaN) results differ from 754-2008. This is intentional, + // see comments in the generic implementations. let cases = [ (F::ZERO, F::ZERO, F::ZERO), (F::ZERO, F::ONE, F::ZERO), @@ -88,6 +91,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_ONE, F::NEG_ONE), @@ -95,6 +100,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ZERO), (F::ONE, F::NEG_ZERO, F::NEG_ZERO), (F::ONE, F::ONE, F::ONE), @@ -103,6 +110,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::NEG_ONE), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ONE), (F::NEG_ONE, F::ONE, F::NEG_ONE), @@ -111,6 +120,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::ZERO), (F::INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::INFINITY, F::ONE, F::ONE), @@ -119,6 +130,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::ONE, F::NEG_INFINITY), @@ -127,6 +140,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -140,6 +155,18 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, res) in cases { @@ -147,12 +174,29 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { assert_biteq!(val, res, "fmin({}, {})", Hexf(x), Hexf(y)); } - // Ordering between zeros and NaNs does not matter + // Ordering between zeros does not matter assert_eq!(f(F::ZERO, F::NEG_ZERO), F::ZERO); assert_eq!(f(F::NEG_ZERO, F::ZERO), F::ZERO); - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); } #[test] @@ -186,6 +230,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::ZERO), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::ONE), (F::NEG_ZERO, F::NEG_ONE, F::NEG_ZERO), @@ -193,6 +239,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_ZERO), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ONE), (F::ONE, F::NEG_ZERO, F::ONE), (F::ONE, F::ONE, F::ONE), @@ -201,6 +249,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::ONE), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::ZERO), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ONE, F::ONE, F::ONE), @@ -209,6 +259,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_ONE), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::INFINITY), (F::INFINITY, F::NEG_ZERO, F::INFINITY), (F::INFINITY, F::ONE, F::INFINITY), @@ -217,6 +269,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::ZERO), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_INFINITY, F::ONE, F::ONE), @@ -225,6 +279,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -238,19 +294,54 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, res) in cases { let val = f(x, y); - assert_biteq!(val, res, "fmax({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + val, + res, + "fmax({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between zeros and NaNs does not matter + // Ordering between zeros assert_eq!(f(F::ZERO, F::NEG_ZERO), F::ZERO); assert_eq!(f(F::NEG_ZERO, F::ZERO), F::ZERO); - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::SNAN, F::SNAN).is_nan()); } #[test] diff --git a/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs b/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs index a3c9c9c3991b..ffc724e3a8d7 100644 --- a/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs +++ b/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs @@ -69,6 +69,7 @@ pub fn fmaximumf128(x: f128, y: f128) -> f128 { #[cfg(test)] mod tests { use super::*; + use crate::support::hex_float::Hexi; use crate::support::{Float, Hexf}; fn fminimum_spec_test(f: impl Fn(F, F) -> F) { @@ -122,29 +123,63 @@ fn fminimum_spec_test(f: impl Fn(F, F) -> F) { (F::NAN, F::INFINITY, F::NAN), (F::NAN, F::NEG_INFINITY, F::NAN), (F::NAN, F::NAN, F::NAN), + (F::NAN, F::SNAN, F::NAN), ]; for (x, y, res) in cases { let val = f(x, y); - assert_biteq!(val, res, "fminimum({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + val, + res, + "fminimum({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::ONE, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ONE, F::NEG_NAN).is_nan()); - assert!(f(F::INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::ONE).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ONE).is_nan()); - assert!(f(F::NEG_NAN, F::INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // On platforms where operations only return a single canonical NaN (e.g. RISC-V), the + // result may not exactly match one of the inputs which is fine. + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_ONE, F::SNAN,).is_nan()); + assert!(f(F::NEG_SNAN, F::INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ZERO).is_nan()); + assert!(f(F::NEG_SNAN, F::ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::ZERO).is_nan()); + assert!(f(F::NEG_ZERO, F::SNAN,).is_nan()); + assert!(f(F::ONE, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_ONE,).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN,).is_nan()); + assert!(f(F::SNAN, F::NEG_ZERO,).is_nan()); + assert!(f(F::SNAN, F::ONE,).is_nan()); + assert!(f(F::SNAN, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::ZERO,).is_nan()); + assert!(f(F::ZERO, F::SNAN,).is_nan()); } #[test] @@ -220,29 +255,63 @@ fn fmaximum_spec_test(f: impl Fn(F, F) -> F) { (F::NAN, F::INFINITY, F::NAN), (F::NAN, F::NEG_INFINITY, F::NAN), (F::NAN, F::NAN, F::NAN), + (F::NAN, F::SNAN, F::NAN), ]; for (x, y, res) in cases { let val = f(x, y); - assert_biteq!(val, res, "fmaximum({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + val, + res, + "fmaximum({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::ONE, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ONE, F::NEG_NAN).is_nan()); - assert!(f(F::INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::ONE).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ONE).is_nan()); - assert!(f(F::NEG_NAN, F::INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // On platforms where operations only return a single canonical NaN (e.g. RISC-V), the + // result may not exactly match one of the inputs which is fine. + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_ONE, F::SNAN,).is_nan()); + assert!(f(F::NEG_SNAN, F::INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ZERO).is_nan()); + assert!(f(F::NEG_SNAN, F::ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::ZERO).is_nan()); + assert!(f(F::NEG_ZERO, F::SNAN,).is_nan()); + assert!(f(F::ONE, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_ONE,).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN,).is_nan()); + assert!(f(F::SNAN, F::NEG_ZERO,).is_nan()); + assert!(f(F::SNAN, F::ONE,).is_nan()); + assert!(f(F::SNAN, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::ZERO,).is_nan()); + assert!(f(F::ZERO, F::SNAN,).is_nan()); } #[test] diff --git a/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs b/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs index 612cefe756e3..3157f8a3fee8 100644 --- a/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs +++ b/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs @@ -69,6 +69,7 @@ pub fn fmaximum_numf128(x: f128, y: f128) -> f128 { #[cfg(test)] mod tests { use super::*; + use crate::support::hex_float::Hexi; use crate::support::{Float, Hexf}; fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { @@ -81,6 +82,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::NEG_ZERO), @@ -89,6 +92,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ZERO), (F::ONE, F::NEG_ZERO, F::NEG_ZERO), (F::ONE, F::ONE, F::ONE), @@ -97,6 +102,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::NEG_ONE), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ONE), (F::NEG_ONE, F::ONE, F::NEG_ONE), @@ -105,6 +112,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::ZERO), (F::INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::INFINITY, F::ONE, F::ONE), @@ -113,6 +122,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::ONE, F::NEG_INFINITY), @@ -121,6 +132,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -134,17 +147,52 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, expected) in cases { let actual = f(x, y); - assert_biteq!(actual, expected, "fminimum_num({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + actual, + expected, + "fminimum_num({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::SNAN, F::SNAN).is_nan()); } #[test] @@ -179,6 +227,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::ZERO), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::ZERO, F::ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::ONE), @@ -187,6 +237,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_ZERO), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ONE), (F::ONE, F::NEG_ZERO, F::ONE), (F::ONE, F::ONE, F::ONE), @@ -195,6 +247,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::ONE), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::ZERO), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ONE, F::ONE, F::ONE), @@ -203,6 +257,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_ONE), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::INFINITY), (F::INFINITY, F::NEG_ZERO, F::INFINITY), (F::INFINITY, F::ONE, F::INFINITY), @@ -211,6 +267,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::ZERO), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_INFINITY, F::ONE, F::ONE), @@ -219,6 +277,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -232,17 +292,52 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, expected) in cases { let actual = f(x, y); - assert_biteq!(actual, expected, "fmaximum_num({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + actual, + expected, + "fmaximum_num({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::SNAN, F::SNAN).is_nan()); } #[test] diff --git a/library/compiler-builtins/libm/src/math/generic/fmax.rs b/library/compiler-builtins/libm/src/math/generic/fmax.rs index b05804704d03..bdd46cc38a81 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmax.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmax.rs @@ -8,11 +8,15 @@ //! - Otherwise, either `x` or `y`, canonicalized //! - -0.0 and +0.0 may be disregarded (unlike newer operations) //! -//! Excluded from our implementation is sNaN handling. +//! We do not treat sNaN and qNaN differently; even though IEEE technically requires this, (a call +//! with sNaN should return qNaN rather than the other result), it breaks associativity so isn't +//! desired behavior. C23 does not differentiate between sNaN and qNaN, so we do not either. More +//! on the problems with `minNum` [here][minnum-removal]. //! -//! More on the differences: [link]. +//! IEEE also specifies that a sNaN in either argument should signal invalid, but we do not +//! implement this. //! -//! [link]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf +//! [minnum-removal]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf use crate::support::Float; diff --git a/library/compiler-builtins/libm/src/math/generic/fmaximum.rs b/library/compiler-builtins/libm/src/math/generic/fmaximum.rs index 55a031e18ee8..1fa48f964804 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmaximum.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmaximum.rs @@ -7,7 +7,8 @@ //! - +0.0 if x and y are zero with opposite signs //! - qNaN if either operation is NaN //! -//! Excluded from our implementation is sNaN handling. +//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid, +//! but we do not implement this. use crate::support::Float; diff --git a/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs b/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs index 2dc60b2d237f..c7ca50a89dc8 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs @@ -9,7 +9,8 @@ //! - Non-NaN if one operand is NaN //! - qNaN if both operands are NaNx //! -//! Excluded from our implementation is sNaN handling. +//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid, +//! but we do not implement this. use crate::support::Float; diff --git a/library/compiler-builtins/libm/src/math/generic/fmin.rs b/library/compiler-builtins/libm/src/math/generic/fmin.rs index e2245bf9e137..e7755e82345c 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmin.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmin.rs @@ -8,11 +8,15 @@ //! - Otherwise, either `x` or `y`, canonicalized //! - -0.0 and +0.0 may be disregarded (unlike newer operations) //! -//! Excluded from our implementation is sNaN handling. +//! We do not treat sNaN and qNaN differently; even though IEEE technically requires this, (a call +//! with sNaN should return qNaN rather than the other result), it breaks associativity so isn't +//! desired behavior. C23 does not differentiate between sNaN and qNaN, so we do not either. More +//! on the problems with `minNum` [here][minnum-removal]. //! -//! More on the differences: [link]. +//! IEEE also specifies that a sNaN in either argument should signal invalid, but we do not +//! implement this. //! -//! [link]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf +//! [minnum-removal]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf use crate::support::Float; diff --git a/library/compiler-builtins/libm/src/math/generic/fminimum.rs b/library/compiler-builtins/libm/src/math/generic/fminimum.rs index aa68b1291d42..82e72d644064 100644 --- a/library/compiler-builtins/libm/src/math/generic/fminimum.rs +++ b/library/compiler-builtins/libm/src/math/generic/fminimum.rs @@ -7,7 +7,8 @@ //! - -0.0 if x and y are zero with opposite signs //! - qNaN if either operation is NaN //! -//! Excluded from our implementation is sNaN handling. +//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid, +//! but we do not implement this. use crate::support::Float; diff --git a/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs b/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs index 265bd4605ce3..5b5271b123ba 100644 --- a/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs +++ b/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs @@ -9,7 +9,8 @@ //! - Non-NaN if one operand is NaN //! - qNaN if both operands are NaNx //! -//! Excluded from our implementation is sNaN handling. +//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid, +//! but we do not implement this. use crate::support::Float;