Skip to content

Commit

Permalink
Rollup merge of #91008 - Urgau:float-minimum-maximum, r=scottmcm
Browse files Browse the repository at this point in the history
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](Urgau@1d8aa13) 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 #83984
  • Loading branch information
matthiaskrgr authored Nov 21, 2021
2 parents 02913c0 + e2ec3b1 commit 789d168
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 0 deletions.
68 changes: 68 additions & 0 deletions library/core/src/num/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,9 @@ impl 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;
Expand All @@ -689,6 +692,9 @@ impl 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;
Expand All @@ -703,6 +709,68 @@ impl 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.
///
Expand Down
68 changes: 68 additions & 0 deletions library/core/src/num/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,9 @@ impl 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;
Expand All @@ -705,6 +708,9 @@ impl 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;
Expand All @@ -719,6 +725,68 @@ impl 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.
///
Expand Down
1 change: 1 addition & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
61 changes: 61 additions & 0 deletions library/core/tests/num/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,67 @@ macro_rules! test_float {
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());
Expand Down
12 changes: 12 additions & 0 deletions library/std/src/f32/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ fn test_max_nan() {
assert_eq!(2.0f32.max(f32::NAN), 2.0);
}

#[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;
Expand Down
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down

0 comments on commit 789d168

Please sign in to comment.