Rollup merge of #91008 - Urgau:float-minimum-maximum, r=scottmcm
Adds IEEE 754-2019 minimun and maximum functions for f32/f64
IEEE 754-2019 removed the `minNum` (`min` in Rust) and `maxNum` (`max` in Rust) operations in favor of the newly created `minimum` and `maximum` operations due to their [non-associativity](https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf) that cannot be fix in a backwards compatible manner. This PR adds `fN::{minimun,maximum}` functions following the new rules.
### IEEE 754-2019 Rules
> **minimum(x, y)** is x if x < y, y if y < x, and a quiet NaN if either operand is a NaN, according to 6.2.
For this operation, −0 compares less than +0. Otherwise (i.e., when x = y and signs are the same)
it is either x or y.
> **maximum(x, y)** is x if x > y, y if y > x, and a quiet NaN if either operand is a NaN, according to 6.2.
For this operation, +0 compares greater than −0. Otherwise (i.e., when x = y and signs are the
same) it is either x or y.
"IEEE Standard for Floating-Point Arithmetic," in IEEE Std 754-2019 (Revision of IEEE 754-2008) , vol., no., pp.1-84, 22 July 2019, doi: 10.1109/IEEESTD.2019.8766229.
### Implementation
This implementation is inspired by the one in [`glibc` ](https://github.com/bminor/glibc/blob/90f0ac10a74b2d43b5a65aab4be40565e359be43/math/s_fminimum_template.c) (it self derived from the C2X draft) expect that:
- it doesn't use `copysign` because it's not available in `core` and also because `copysign` is unnecessary (we only want to check the sign, no need to create a new float)
- it also prefer `other > self` instead of `self < other` like IEEE 754-2019 does
I originally tried to implement them [using intrinsics](https://github.com/Urgau/rust/commit/1d8aa13bc39eeef1afba0524dc5ea10d073522e6) but LLVM [error out](https://godbolt.org/z/7sMrxW49a) when trying to lower them to machine intructions, GCC doesn't yet have built-ins for them, only cranelift support them nativelly (as it doesn't support the nativelly the old sementics).
Helps with https://github.com/rust-lang/rust/issues/83984
diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs
index 905b0c4..c4a232e 100644
--- a/library/core/src/num/f32.rs
+++ b/library/core/src/num/f32.rs
@@ -673,6 +673,9 @@ pub fn to_radians(self) -> f32 {
/// Returns the maximum of the two numbers.
///
+ /// Follows the IEEE-754 2008 semantics for maxNum, except for handling of signaling NaNs.
+ /// This matches the behavior of libm’s fmin.
+ ///
/// ```
/// let x = 1.0f32;
/// let y = 2.0f32;
@@ -689,6 +692,9 @@ pub fn max(self, other: f32) -> f32 {
/// Returns the minimum of the two numbers.
///
+ /// Follows the IEEE-754 2008 semantics for minNum, except for handling of signaling NaNs.
+ /// This matches the behavior of libm’s fmin.
+ ///
/// ```
/// let x = 1.0f32;
/// let y = 2.0f32;
@@ -703,6 +709,68 @@ pub fn min(self, other: f32) -> f32 {
intrinsics::minnumf32(self, other)
}
+ /// Returns the maximum of the two numbers, propagating NaNs.
+ ///
+ /// This returns NaN when *either* argument is NaN, as opposed to
+ /// [`f32::max`] which only returns NaN when *both* arguments are NaN.
+ ///
+ /// ```
+ /// #![feature(float_minimum_maximum)]
+ /// let x = 1.0f32;
+ /// let y = 2.0f32;
+ ///
+ /// assert_eq!(x.maximum(y), y);
+ /// assert!(x.maximum(f32::NAN).is_nan());
+ /// ```
+ ///
+ /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the greater
+ /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0.
+ /// Note that this follows the semantics specified in IEEE 754-2019.
+ #[unstable(feature = "float_minimum_maximum", issue = "91079")]
+ #[inline]
+ pub fn maximum(self, other: f32) -> f32 {
+ if self > other {
+ self
+ } else if other > self {
+ other
+ } else if self == other {
+ if self.is_sign_positive() && other.is_sign_negative() { self } else { other }
+ } else {
+ self + other
+ }
+ }
+
+ /// Returns the minimum of the two numbers, propagating NaNs.
+ ///
+ /// This returns NaN when *either* argument is NaN, as opposed to
+ /// [`f32::min`] which only returns NaN when *both* arguments are NaN.
+ ///
+ /// ```
+ /// #![feature(float_minimum_maximum)]
+ /// let x = 1.0f32;
+ /// let y = 2.0f32;
+ ///
+ /// assert_eq!(x.minimum(y), x);
+ /// assert!(x.minimum(f32::NAN).is_nan());
+ /// ```
+ ///
+ /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the lesser
+ /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0.
+ /// Note that this follows the semantics specified in IEEE 754-2019.
+ #[unstable(feature = "float_minimum_maximum", issue = "91079")]
+ #[inline]
+ pub fn minimum(self, other: f32) -> f32 {
+ if self < other {
+ self
+ } else if other < self {
+ other
+ } else if self == other {
+ if self.is_sign_negative() && other.is_sign_positive() { self } else { other }
+ } else {
+ self + other
+ }
+ }
+
/// Rounds toward zero and converts to any primitive integer type,
/// assuming that the value is finite and fits in that type.
///
diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs
index 112a239..85ee6aa 100644
--- a/library/core/src/num/f64.rs
+++ b/library/core/src/num/f64.rs
@@ -689,6 +689,9 @@ pub fn to_radians(self) -> f64 {
/// Returns the maximum of the two numbers.
///
+ /// Follows the IEEE-754 2008 semantics for maxNum, except for handling of signaling NaNs.
+ /// This matches the behavior of libm’s fmin.
+ ///
/// ```
/// let x = 1.0_f64;
/// let y = 2.0_f64;
@@ -705,6 +708,9 @@ pub fn max(self, other: f64) -> f64 {
/// Returns the minimum of the two numbers.
///
+ /// Follows the IEEE-754 2008 semantics for minNum, except for handling of signaling NaNs.
+ /// This matches the behavior of libm’s fmin.
+ ///
/// ```
/// let x = 1.0_f64;
/// let y = 2.0_f64;
@@ -719,6 +725,68 @@ pub fn min(self, other: f64) -> f64 {
intrinsics::minnumf64(self, other)
}
+ /// Returns the maximum of the two numbers, propagating NaNs.
+ ///
+ /// This returns NaN when *either* argument is NaN, as opposed to
+ /// [`f64::max`] which only returns NaN when *both* arguments are NaN.
+ ///
+ /// ```
+ /// #![feature(float_minimum_maximum)]
+ /// let x = 1.0_f64;
+ /// let y = 2.0_f64;
+ ///
+ /// assert_eq!(x.maximum(y), y);
+ /// assert!(x.maximum(f64::NAN).is_nan());
+ /// ```
+ ///
+ /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the greater
+ /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0.
+ /// Note that this follows the semantics specified in IEEE 754-2019.
+ #[unstable(feature = "float_minimum_maximum", issue = "91079")]
+ #[inline]
+ pub fn maximum(self, other: f64) -> f64 {
+ if self > other {
+ self
+ } else if other > self {
+ other
+ } else if self == other {
+ if self.is_sign_positive() && other.is_sign_negative() { self } else { other }
+ } else {
+ self + other
+ }
+ }
+
+ /// Returns the minimum of the two numbers, propagating NaNs.
+ ///
+ /// This returns NaN when *either* argument is NaN, as opposed to
+ /// [`f64::min`] which only returns NaN when *both* arguments are NaN.
+ ///
+ /// ```
+ /// #![feature(float_minimum_maximum)]
+ /// let x = 1.0_f64;
+ /// let y = 2.0_f64;
+ ///
+ /// assert_eq!(x.minimum(y), x);
+ /// assert!(x.minimum(f64::NAN).is_nan());
+ /// ```
+ ///
+ /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the lesser
+ /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0.
+ /// Note that this follows the semantics specified in IEEE 754-2019.
+ #[unstable(feature = "float_minimum_maximum", issue = "91079")]
+ #[inline]
+ pub fn minimum(self, other: f64) -> f64 {
+ if self < other {
+ self
+ } else if other < self {
+ other
+ } else if self == other {
+ if self.is_sign_negative() && other.is_sign_positive() { self } else { other }
+ } else {
+ self + other
+ }
+ }
+
/// Rounds toward zero and converts to any primitive integer type,
/// assuming that the value is finite and fits in that type.
///
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index b9acd0d..a56a1db 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -27,6 +27,7 @@
#![feature(extern_types)]
#![feature(flt2dec)]
#![feature(fmt_internals)]
+#![feature(float_minimum_maximum)]
#![feature(array_from_fn)]
#![feature(hashmap_internals)]
#![feature(try_find)]
diff --git a/library/core/tests/num/mod.rs b/library/core/tests/num/mod.rs
index 37b5e912..4f773a8 100644
--- a/library/core/tests/num/mod.rs
+++ b/library/core/tests/num/mod.rs
@@ -715,6 +715,67 @@ fn max() {
assert!(($nan as $fty).max($nan).is_nan());
}
#[test]
+ fn minimum() {
+ assert_eq!((0.0 as $fty).minimum(0.0), 0.0);
+ assert!((0.0 as $fty).minimum(0.0).is_sign_positive());
+ assert_eq!((-0.0 as $fty).minimum(0.0), -0.0);
+ assert!((-0.0 as $fty).minimum(0.0).is_sign_negative());
+ assert_eq!((-0.0 as $fty).minimum(-0.0), -0.0);
+ assert!((-0.0 as $fty).minimum(-0.0).is_sign_negative());
+ assert_eq!((9.0 as $fty).minimum(9.0), 9.0);
+ assert_eq!((-9.0 as $fty).minimum(0.0), -9.0);
+ assert_eq!((0.0 as $fty).minimum(9.0), 0.0);
+ assert!((0.0 as $fty).minimum(9.0).is_sign_positive());
+ assert_eq!((-0.0 as $fty).minimum(9.0), -0.0);
+ assert!((-0.0 as $fty).minimum(9.0).is_sign_negative());
+ assert_eq!((-0.0 as $fty).minimum(-9.0), -9.0);
+ assert_eq!(($inf as $fty).minimum(9.0), 9.0);
+ assert_eq!((9.0 as $fty).minimum($inf), 9.0);
+ assert_eq!(($inf as $fty).minimum(-9.0), -9.0);
+ assert_eq!((-9.0 as $fty).minimum($inf), -9.0);
+ assert_eq!(($neginf as $fty).minimum(9.0), $neginf);
+ assert_eq!((9.0 as $fty).minimum($neginf), $neginf);
+ assert_eq!(($neginf as $fty).minimum(-9.0), $neginf);
+ assert_eq!((-9.0 as $fty).minimum($neginf), $neginf);
+ assert!(($nan as $fty).minimum(9.0).is_nan());
+ assert!(($nan as $fty).minimum(-9.0).is_nan());
+ assert!((9.0 as $fty).minimum($nan).is_nan());
+ assert!((-9.0 as $fty).minimum($nan).is_nan());
+ assert!(($nan as $fty).minimum($nan).is_nan());
+ }
+ #[test]
+ fn maximum() {
+ assert_eq!((0.0 as $fty).maximum(0.0), 0.0);
+ assert!((0.0 as $fty).maximum(0.0).is_sign_positive());
+ assert_eq!((-0.0 as $fty).maximum(0.0), 0.0);
+ assert!((-0.0 as $fty).maximum(0.0).is_sign_positive());
+ assert_eq!((-0.0 as $fty).maximum(-0.0), -0.0);
+ assert!((-0.0 as $fty).maximum(-0.0).is_sign_negative());
+ assert_eq!((9.0 as $fty).maximum(9.0), 9.0);
+ assert_eq!((-9.0 as $fty).maximum(0.0), 0.0);
+ assert!((-9.0 as $fty).maximum(0.0).is_sign_positive());
+ assert_eq!((-9.0 as $fty).maximum(-0.0), -0.0);
+ assert!((-9.0 as $fty).maximum(-0.0).is_sign_negative());
+ assert_eq!((0.0 as $fty).maximum(9.0), 9.0);
+ assert_eq!((0.0 as $fty).maximum(-9.0), 0.0);
+ assert!((0.0 as $fty).maximum(-9.0).is_sign_positive());
+ assert_eq!((-0.0 as $fty).maximum(-9.0), -0.0);
+ assert!((-0.0 as $fty).maximum(-9.0).is_sign_negative());
+ assert_eq!(($inf as $fty).maximum(9.0), $inf);
+ assert_eq!((9.0 as $fty).maximum($inf), $inf);
+ assert_eq!(($inf as $fty).maximum(-9.0), $inf);
+ assert_eq!((-9.0 as $fty).maximum($inf), $inf);
+ assert_eq!(($neginf as $fty).maximum(9.0), 9.0);
+ assert_eq!((9.0 as $fty).maximum($neginf), 9.0);
+ assert_eq!(($neginf as $fty).maximum(-9.0), -9.0);
+ assert_eq!((-9.0 as $fty).maximum($neginf), -9.0);
+ assert!(($nan as $fty).maximum(9.0).is_nan());
+ assert!(($nan as $fty).maximum(-9.0).is_nan());
+ assert!((9.0 as $fty).maximum($nan).is_nan());
+ assert!((-9.0 as $fty).maximum($nan).is_nan());
+ assert!(($nan as $fty).maximum($nan).is_nan());
+ }
+ #[test]
fn rem_euclid() {
let a: $fty = 42.0;
assert!($inf.rem_euclid(a).is_nan());
diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs
index 0d4b865..69fa203 100644
--- a/library/std/src/f32/tests.rs
+++ b/library/std/src/f32/tests.rs
@@ -20,6 +20,18 @@ fn test_max_nan() {
}
#[test]
+fn test_minimum() {
+ assert!(f32::NAN.minimum(2.0).is_nan());
+ assert!(2.0f32.minimum(f32::NAN).is_nan());
+}
+
+#[test]
+fn test_maximum() {
+ assert!(f32::NAN.maximum(2.0).is_nan());
+ assert!(2.0f32.maximum(f32::NAN).is_nan());
+}
+
+#[test]
fn test_nan() {
let nan: f32 = f32::NAN;
assert!(nan.is_nan());
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index f2490a7..afd8d8e 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -287,6 +287,7 @@
#![feature(exhaustive_patterns)]
#![feature(extend_one)]
#![feature(fn_traits)]
+#![feature(float_minimum_maximum)]
#![feature(format_args_nl)]
#![feature(gen_future)]
#![feature(generator_trait)]