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.
This commit is contained in:
Trevor Gross
2026-03-05 19:11:20 -05:00
parent 3a352497c3
commit 7a698be605
9 changed files with 330 additions and 63 deletions
@@ -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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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]
@@ -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: Float>(f: impl Fn(F, F) -> F) {
@@ -122,29 +123,63 @@ fn fminimum_spec_test<F: Float>(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: Float>(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]
@@ -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: Float>(f: impl Fn(F, F) -> F) {
@@ -81,6 +82,8 @@ fn fminimum_num_spec_test<F: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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: Float>(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]
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;