From 04681898f09dad23b2d282a541e1a3b181bed33e Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Tue, 7 Sep 2021 18:46:49 +0200 Subject: [PATCH 1/9] Added next_up and next_down for f32/f64. --- library/core/src/num/f32.rs | 98 ++++++++++++++++++++++++++++++++++++ library/core/src/num/f64.rs | 98 ++++++++++++++++++++++++++++++++++++ library/std/src/f32/tests.rs | 63 +++++++++++++++++++++++ library/std/src/f64/tests.rs | 63 +++++++++++++++++++++++ library/std/src/lib.rs | 1 + 5 files changed, 323 insertions(+) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 6548ad2e514fb..7c38d830636de 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -678,6 +678,104 @@ impl f32 { unsafe { mem::transmute::(self) & 0x8000_0000 != 0 } } + /// Returns the least number greater than `self`. + /// + /// Let `TINY` be the smallest representable positive `f32`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`NEG_INFINITY`], this returns [`MIN`]; + /// - if `self` is `-TINY`, this returns -0.0; + /// - if `self` is -0.0 or +0.0, this returns `TINY`; + /// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; + /// - otherwise the unique least value greater than `self` is returned. + /// + /// The identity `x.next_up() == -(-x).next_down()` holds for all `x`. When `x` + /// is finite `x == x.next_up().next_down()` also holds. + /// + /// ```rust + /// #![feature(float_next_up_down)] + /// // f32::EPSILON is the difference between 1.0 and the next number up. + /// assert_eq!(1.0f32.next_up(), 1.0 + f32::EPSILON); + /// // But not for most numbers. + /// assert!(0.1f32.next_up() < 0.1 + f32::EPSILON); + /// assert_eq!(16777216f32.next_up(), 16777218.0); + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[unstable(feature = "float_next_up_down", issue = "none")] + pub const fn next_up(self) -> Self { + // We must use strictly integer arithmetic to prevent denormals from + // flushing to zero after an arithmetic operation on some platforms. + const TINY_BITS: u32 = 0x1; // Smallest positive f32. + const CLEAR_SIGN_MASK: u32 = 0x7fff_ffff; + + let bits = self.to_bits(); + if self.is_nan() || bits == Self::INFINITY.to_bits() { + return self; + } + + let abs = bits & CLEAR_SIGN_MASK; + let next_bits = if abs == 0 { + TINY_BITS + } else if bits == abs { + bits + 1 + } else { + bits - 1 + }; + Self::from_bits(next_bits) + } + + /// Returns the greatest number less than `self`. + /// + /// Let `TINY` be the smallest representable positive `f32`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`INFINITY`], this returns [`MAX`]; + /// - if `self` is `TINY`, this returns 0.0; + /// - if `self` is -0.0 or +0.0, this returns `-TINY`; + /// - if `self` is [`MIN`] or [`NEG_INFINITY`], this returns [`NEG_INFINITY`]; + /// - otherwise the unique greatest value less than `self` is returned. + /// + /// The identity `x.next_down() == -(-x).next_up()` holds for all `x`. When `x` + /// is finite `x == x.next_down().next_up()` also holds. + /// + /// ```rust + /// #![feature(float_next_up_down)] + /// let x = 1.0f32; + /// // Clamp value into range [0, 1). + /// let clamped = x.clamp(0.0, 1.0f32.next_down()); + /// assert!(clamped < 1.0); + /// assert_eq!(clamped.next_up(), 1.0); + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[unstable(feature = "float_next_up_down", issue = "none")] + pub const fn next_down(self) -> Self { + // We must use strictly integer arithmetic to prevent denormals from + // flushing to zero after an arithmetic operation on some platforms. + const NEG_TINY_BITS: u32 = 0x8000_0001; // Smallest (in magnitude) negative f32. + const CLEAR_SIGN_MASK: u32 = 0x7fff_ffff; + + let bits = self.to_bits(); + if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { + return self; + } + + let abs = bits & CLEAR_SIGN_MASK; + let next_bits = if abs == 0 { + NEG_TINY_BITS + } else if bits == abs { + bits - 1 + } else { + bits + 1 + }; + Self::from_bits(next_bits) + } + /// Takes the reciprocal (inverse) of a number, `1/x`. /// /// ``` diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 75c92c2f8834a..8146f0075316f 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -688,6 +688,104 @@ impl f64 { self.is_sign_negative() } + /// Returns the least number greater than `self`. + /// + /// Let `TINY` be the smallest representable positive `f64`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`NEG_INFINITY`], this returns [`MIN`]; + /// - if `self` is `-TINY`, this returns -0.0; + /// - if `self` is -0.0 or +0.0, this returns `TINY`; + /// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; + /// - otherwise the unique least value greater than `self` is returned. + /// + /// The identity `x.next_up() == -(-x).next_down()` holds for all `x`. When `x` + /// is finite `x == x.next_up().next_down()` also holds. + /// + /// ```rust + /// #![feature(float_next_up_down)] + /// // f64::EPSILON is the difference between 1.0 and the next number up. + /// assert_eq!(1.0f64.next_up(), 1.0 + f64::EPSILON); + /// // But not for most numbers. + /// assert!(0.1f64.next_up() < 0.1 + f64::EPSILON); + /// assert_eq!(9007199254740992f64.next_up(), 9007199254740994.0); + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[unstable(feature = "float_next_up_down", issue = "none")] + pub const fn next_up(self) -> Self { + // We must use strictly integer arithmetic to prevent denormals from + // flushing to zero after an arithmetic operation on some platforms. + const TINY_BITS: u64 = 0x1; // Smallest positive f64. + const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; + + let bits = self.to_bits(); + if self.is_nan() || bits == Self::INFINITY.to_bits() { + return self; + } + + let abs = bits & CLEAR_SIGN_MASK; + let next_bits = if abs == 0 { + TINY_BITS + } else if bits == abs { + bits + 1 + } else { + bits - 1 + }; + Self::from_bits(next_bits) + } + + /// Returns the greatest number less than `self`. + /// + /// Let `TINY` be the smallest representable positive `f64`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`INFINITY`], this returns [`MAX`]; + /// - if `self` is `TINY`, this returns 0.0; + /// - if `self` is -0.0 or +0.0, this returns `-TINY`; + /// - if `self` is [`MIN`] or [`NEG_INFINITY`], this returns [`NEG_INFINITY`]; + /// - otherwise the unique greatest value less than `self` is returned. + /// + /// The identity `x.next_down() == -(-x).next_up()` holds for all `x`. When `x` + /// is finite `x == x.next_down().next_up()` also holds. + /// + /// ```rust + /// #![feature(float_next_up_down)] + /// let x = 1.0f64; + /// // Clamp value into range [0, 1). + /// let clamped = x.clamp(0.0, 1.0f64.next_down()); + /// assert!(clamped < 1.0); + /// assert_eq!(clamped.next_up(), 1.0); + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[unstable(feature = "float_next_up_down", issue = "none")] + pub const fn next_down(self) -> Self { + // We must use strictly integer arithmetic to prevent denormals from + // flushing to zero after an arithmetic operation on some platforms. + const NEG_TINY_BITS: u64 = 0x8000_0000_0000_0001; // Smallest (in magnitude) negative f64. + const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; + + let bits = self.to_bits(); + if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { + return self; + } + + let abs = bits & CLEAR_SIGN_MASK; + let next_bits = if abs == 0 { + NEG_TINY_BITS + } else if bits == abs { + bits - 1 + } else { + bits + 1 + }; + Self::from_bits(next_bits) + } + /// Takes the reciprocal (inverse) of a number, `1/x`. /// /// ``` diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 69fa203ff4e70..19de0cf6c751b 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -299,6 +299,69 @@ fn test_is_sign_negative() { assert!((-f32::NAN).is_sign_negative()); } +#[test] +fn test_next_up() { + let tiny = f32::from_bits(1); + let tiny_up = f32::from_bits(2); + let max_down = f32::from_bits(0x7f7f_fffe); + let largest_subnormal = f32::from_bits(0x007f_ffff); + let smallest_normal = f32::from_bits(0x0080_0000); + + // Check that NaNs roundtrip. + let nan0 = f32::NAN.to_bits(); + let nan1 = f32::NAN.to_bits() ^ 0x002a_aaaa; + let nan2 = f32::NAN.to_bits() ^ 0x0055_5555; + assert_eq!(f32::from_bits(nan0).next_up().to_bits(), nan0); + assert_eq!(f32::from_bits(nan1).next_up().to_bits(), nan1); + assert_eq!(f32::from_bits(nan2).next_up().to_bits(), nan2); + + assert_eq!(f32::NEG_INFINITY.next_up(), f32::MIN); + assert_eq!(f32::MIN.next_up(), -max_down); + assert_eq!((-1.0 - f32::EPSILON).next_up(), -1.0); + assert_eq!((-smallest_normal).next_up(), -largest_subnormal); + assert_eq!((-tiny_up).next_up(), -tiny); + assert_eq!((-tiny).next_up().to_bits(), (-0.0f32).to_bits()); + assert_eq!((-0.0f32).next_up(), tiny); + assert_eq!(0.0f32.next_up(), tiny); + assert_eq!(tiny.next_up(), tiny_up); + assert_eq!(largest_subnormal.next_up(), smallest_normal); + assert_eq!(1.0f32.next_up(), 1.0 + f32::EPSILON); + assert_eq!(f32::MAX.next_up(), f32::INFINITY); + assert_eq!(f32::INFINITY.next_up(), f32::INFINITY); +} + +#[test] +fn test_next_down() { + let tiny = f32::from_bits(1); + let tiny_up = f32::from_bits(2); + let max_down = f32::from_bits(0x7f7f_fffe); + let largest_subnormal = f32::from_bits(0x007f_ffff); + let smallest_normal = f32::from_bits(0x0080_0000); + + // Check that NaNs roundtrip. + let nan0 = f32::NAN.to_bits(); + let nan1 = f32::NAN.to_bits() ^ 0x002a_aaaa; + let nan2 = f32::NAN.to_bits() ^ 0x0055_5555; + assert_eq!(f32::from_bits(nan0).next_down().to_bits(), nan0); + assert_eq!(f32::from_bits(nan1).next_down().to_bits(), nan1); + assert_eq!(f32::from_bits(nan2).next_down().to_bits(), nan2); + + assert_eq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); + assert_eq!(f32::MIN.next_down(), f32::NEG_INFINITY); + assert_eq!((-max_down).next_down(), f32::MIN); + assert_eq!((-1.0f32).next_down(), -1.0 - f32::EPSILON); + assert_eq!((-largest_subnormal).next_down(), -smallest_normal); + assert_eq!((-tiny).next_down(), -tiny_up); + assert_eq!((-0.0f32).next_down(), -tiny); + assert_eq!((0.0f32).next_down(), -tiny); + assert_eq!(tiny.next_down().to_bits(), 0.0f32.to_bits()); + assert_eq!(tiny_up.next_down(), tiny); + assert_eq!(smallest_normal.next_down(), largest_subnormal); + assert_eq!((1.0 + f32::EPSILON).next_down(), 1.0f32); + assert_eq!(f32::MAX.next_down(), max_down); + assert_eq!(f32::INFINITY.next_down(), f32::MAX); +} + #[test] fn test_mul_add() { let nan: f32 = f32::NAN; diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 5c163cfe90e0b..8e172cf4963d2 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -289,6 +289,69 @@ fn test_is_sign_negative() { assert!((-f64::NAN).is_sign_negative()); } +#[test] +fn test_next_up() { + let tiny = f64::from_bits(1); + let tiny_up = f64::from_bits(2); + let max_down = f64::from_bits(0x7fef_ffff_ffff_fffe); + let largest_subnormal = f64::from_bits(0x000f_ffff_ffff_ffff); + let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); + + // Check that NaNs roundtrip. + let nan0 = f64::NAN.to_bits(); + let nan1 = f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa; + let nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; + assert_eq!(f64::from_bits(nan0).next_up().to_bits(), nan0); + assert_eq!(f64::from_bits(nan1).next_up().to_bits(), nan1); + assert_eq!(f64::from_bits(nan2).next_up().to_bits(), nan2); + + assert_eq!(f64::NEG_INFINITY.next_up(), f64::MIN); + assert_eq!(f64::MIN.next_up(), -max_down); + assert_eq!((-1.0 - f64::EPSILON).next_up(), -1.0); + assert_eq!((-smallest_normal).next_up(), -largest_subnormal); + assert_eq!((-tiny_up).next_up(), -tiny); + assert_eq!((-tiny).next_up().to_bits(), (-0.0f64).to_bits()); + assert_eq!((-0.0f64).next_up(), tiny); + assert_eq!(0.0f64.next_up(), tiny); + assert_eq!(tiny.next_up(), tiny_up); + assert_eq!(largest_subnormal.next_up(), smallest_normal); + assert_eq!(1.0f64.next_up(), 1.0 + f64::EPSILON); + assert_eq!(f64::MAX.next_up(), f64::INFINITY); + assert_eq!(f64::INFINITY.next_up(), f64::INFINITY); +} + +#[test] +fn test_next_down() { + let tiny = f64::from_bits(1); + let tiny_up = f64::from_bits(2); + let max_down = f64::from_bits(0x7fef_ffff_ffff_fffe); + let largest_subnormal = f64::from_bits(0x000f_ffff_ffff_ffff); + let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); + + // Check that NaNs roundtrip. + let nan0 = f64::NAN.to_bits(); + let nan1 = f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa; + let nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; + assert_eq!(f64::from_bits(nan0).next_down().to_bits(), nan0); + assert_eq!(f64::from_bits(nan1).next_down().to_bits(), nan1); + assert_eq!(f64::from_bits(nan2).next_down().to_bits(), nan2); + + assert_eq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); + assert_eq!(f64::MIN.next_down(), f64::NEG_INFINITY); + assert_eq!((-max_down).next_down(), f64::MIN); + assert_eq!((-1.0f64).next_down(), -1.0 - f64::EPSILON); + assert_eq!((-largest_subnormal).next_down(), -smallest_normal); + assert_eq!((-tiny).next_down(), -tiny_up); + assert_eq!((-0.0f64).next_down(), -tiny); + assert_eq!((0.0f64).next_down(), -tiny); + assert_eq!(tiny.next_down().to_bits(), 0.0f64.to_bits()); + assert_eq!(tiny_up.next_down(), tiny); + assert_eq!(smallest_normal.next_down(), largest_subnormal); + assert_eq!((1.0 + f64::EPSILON).next_down(), 1.0f64); + assert_eq!(f64::MAX.next_down(), max_down); + assert_eq!(f64::INFINITY.next_down(), f64::MAX); +} + #[test] fn test_mul_add() { let nan: f64 = f64::NAN; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 475a1d9fd9920..e0473f746e54d 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -273,6 +273,7 @@ #![feature(exclusive_wrapper)] #![feature(extend_one)] #![feature(float_minimum_maximum)] +#![feature(float_next_up_down)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] #![feature(int_error_internals)] From 3241bbcbc792c0391ed8ed2772ba02b4c9cae67d Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sat, 30 Oct 2021 09:25:04 +0200 Subject: [PATCH 2/9] Fixed float next_up/down 32-bit x87 float NaN roundtrip test case. --- library/std/src/f32/tests.rs | 24 ++++++++++++------------ library/std/src/f64/tests.rs | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 19de0cf6c751b..07b9ff3a78919 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -308,12 +308,12 @@ fn test_next_up() { let smallest_normal = f32::from_bits(0x0080_0000); // Check that NaNs roundtrip. - let nan0 = f32::NAN.to_bits(); - let nan1 = f32::NAN.to_bits() ^ 0x002a_aaaa; - let nan2 = f32::NAN.to_bits() ^ 0x0055_5555; - assert_eq!(f32::from_bits(nan0).next_up().to_bits(), nan0); - assert_eq!(f32::from_bits(nan1).next_up().to_bits(), nan1); - assert_eq!(f32::from_bits(nan2).next_up().to_bits(), nan2); + let nan0 = f32::NAN; + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); assert_eq!(f32::NEG_INFINITY.next_up(), f32::MIN); assert_eq!(f32::MIN.next_up(), -max_down); @@ -339,12 +339,12 @@ fn test_next_down() { let smallest_normal = f32::from_bits(0x0080_0000); // Check that NaNs roundtrip. - let nan0 = f32::NAN.to_bits(); - let nan1 = f32::NAN.to_bits() ^ 0x002a_aaaa; - let nan2 = f32::NAN.to_bits() ^ 0x0055_5555; - assert_eq!(f32::from_bits(nan0).next_down().to_bits(), nan0); - assert_eq!(f32::from_bits(nan1).next_down().to_bits(), nan1); - assert_eq!(f32::from_bits(nan2).next_down().to_bits(), nan2); + let nan0 = f32::NAN; + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); assert_eq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); assert_eq!(f32::MIN.next_down(), f32::NEG_INFINITY); diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 8e172cf4963d2..cf46ffcffcf2e 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -298,12 +298,12 @@ fn test_next_up() { let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); // Check that NaNs roundtrip. - let nan0 = f64::NAN.to_bits(); - let nan1 = f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa; - let nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; - assert_eq!(f64::from_bits(nan0).next_up().to_bits(), nan0); - assert_eq!(f64::from_bits(nan1).next_up().to_bits(), nan1); - assert_eq!(f64::from_bits(nan2).next_up().to_bits(), nan2); + let nan0 = f64::NAN; + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); assert_eq!(f64::NEG_INFINITY.next_up(), f64::MIN); assert_eq!(f64::MIN.next_up(), -max_down); @@ -329,12 +329,12 @@ fn test_next_down() { let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); // Check that NaNs roundtrip. - let nan0 = f64::NAN.to_bits(); - let nan1 = f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa; - let nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; - assert_eq!(f64::from_bits(nan0).next_down().to_bits(), nan0); - assert_eq!(f64::from_bits(nan1).next_down().to_bits(), nan1); - assert_eq!(f64::from_bits(nan2).next_down().to_bits(), nan2); + let nan0 = f64::NAN; + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); assert_eq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); assert_eq!(f64::MIN.next_down(), f64::NEG_INFINITY); From de757e8a98ce0a4efe91f2f46e93abe1c197bcdf Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Mon, 29 Nov 2021 12:10:09 +0100 Subject: [PATCH 3/9] Ensure NaN references values go through function boundary for next_up/down. --- library/std/src/f32/tests.rs | 24 ++++++++++++++++++------ library/std/src/f64/tests.rs | 24 ++++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 07b9ff3a78919..7a2f9bd487891 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -308,12 +308,18 @@ fn test_next_up() { let smallest_normal = f32::from_bits(0x0080_0000); // Check that NaNs roundtrip. + // Because x87 can lose NaN bits when passed through a function, ensure the reference value + // also passes through a function boundary. + #[inline(never)] + fn identity(x: f32) -> f32 { + crate::hint::black_box(x) + } let nan0 = f32::NAN; let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); - assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); + assert_eq!(nan0.next_up().to_bits(), identity(nan0).to_bits()); + assert_eq!(nan1.next_up().to_bits(), identity(nan1).to_bits()); + assert_eq!(nan2.next_up().to_bits(), identity(nan2).to_bits()); assert_eq!(f32::NEG_INFINITY.next_up(), f32::MIN); assert_eq!(f32::MIN.next_up(), -max_down); @@ -339,12 +345,18 @@ fn test_next_down() { let smallest_normal = f32::from_bits(0x0080_0000); // Check that NaNs roundtrip. + // Because x87 can lose NaN bits when passed through a function, ensure the reference value + // also passes through a function boundary. + #[inline(never)] + fn identity(x: f32) -> f32 { + crate::hint::black_box(x) + } let nan0 = f32::NAN; let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); - assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); + assert_eq!(nan0.next_down().to_bits(), identity(nan0).to_bits()); + assert_eq!(nan1.next_down().to_bits(), identity(nan1).to_bits()); + assert_eq!(nan2.next_down().to_bits(), identity(nan2).to_bits()); assert_eq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); assert_eq!(f32::MIN.next_down(), f32::NEG_INFINITY); diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index cf46ffcffcf2e..2524bfe8b9988 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -298,12 +298,18 @@ fn test_next_up() { let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); // Check that NaNs roundtrip. + // Because x87 can lose NaN bits when passed through a function, ensure the reference value + // also passes through a function boundary. + #[inline(never)] + fn identity(x: f64) -> f64 { + crate::hint::black_box(x) + } let nan0 = f64::NAN; let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); - assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); + assert_eq!(nan0.next_up().to_bits(), identity(nan0).to_bits()); + assert_eq!(nan1.next_up().to_bits(), identity(nan1).to_bits()); + assert_eq!(nan2.next_up().to_bits(), identity(nan2).to_bits()); assert_eq!(f64::NEG_INFINITY.next_up(), f64::MIN); assert_eq!(f64::MIN.next_up(), -max_down); @@ -329,12 +335,18 @@ fn test_next_down() { let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); // Check that NaNs roundtrip. + // Because x87 can lose NaN bits when passed through a function, ensure the reference value + // also passes through a function boundary. + #[inline(never)] + fn identity(x: f64) -> f64 { + crate::hint::black_box(x) + } let nan0 = f64::NAN; let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); - assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); + assert_eq!(nan0.next_down().to_bits(), identity(nan0).to_bits()); + assert_eq!(nan1.next_down().to_bits(), identity(nan1).to_bits()); + assert_eq!(nan2.next_down().to_bits(), identity(nan2).to_bits()); assert_eq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); assert_eq!(f64::MIN.next_down(), f64::NEG_INFINITY); From 712bf2a07a31f1fb8234ec5823d9913f1091e413 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Tue, 30 Nov 2021 20:54:12 +0100 Subject: [PATCH 4/9] Added tracking issue numbers for float_next_up_down. --- library/core/src/num/f32.rs | 6 ++++-- library/core/src/num/f64.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 7c38d830636de..f6cef3f806704 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -704,7 +704,8 @@ impl f32 { /// [`INFINITY`]: Self::INFINITY /// [`MIN`]: Self::MIN /// [`MAX`]: Self::MAX - #[unstable(feature = "float_next_up_down", issue = "none")] + #[unstable(feature = "float_next_up_down", issue = "91399")] + #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_up(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. @@ -753,7 +754,8 @@ impl f32 { /// [`INFINITY`]: Self::INFINITY /// [`MIN`]: Self::MIN /// [`MAX`]: Self::MAX - #[unstable(feature = "float_next_up_down", issue = "none")] + #[unstable(feature = "float_next_up_down", issue = "91399")] + #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_down(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 8146f0075316f..328b19b203c26 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -714,7 +714,8 @@ impl f64 { /// [`INFINITY`]: Self::INFINITY /// [`MIN`]: Self::MIN /// [`MAX`]: Self::MAX - #[unstable(feature = "float_next_up_down", issue = "none")] + #[unstable(feature = "float_next_up_down", issue = "91399")] + #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_up(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. @@ -763,7 +764,8 @@ impl f64 { /// [`INFINITY`]: Self::INFINITY /// [`MIN`]: Self::MIN /// [`MAX`]: Self::MAX - #[unstable(feature = "float_next_up_down", issue = "none")] + #[unstable(feature = "float_next_up_down", issue = "91399")] + #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_down(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. From fbe215af53da287dc220f319543a5ef4e75f6c54 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Tue, 30 Nov 2021 21:08:37 +0100 Subject: [PATCH 5/9] Conditionally do not compile NaN roundtrip tests on x87 fp. --- library/std/src/f32/tests.rs | 44 ++++++++++++++++++------------------ library/std/src/f64/tests.rs | 44 ++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 7a2f9bd487891..4fb9f73ef3771 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -308,18 +308,18 @@ fn test_next_up() { let smallest_normal = f32::from_bits(0x0080_0000); // Check that NaNs roundtrip. - // Because x87 can lose NaN bits when passed through a function, ensure the reference value - // also passes through a function boundary. - #[inline(never)] - fn identity(x: f32) -> f32 { - crate::hint::black_box(x) + // Ignore test on x87 floating point, the code is still correct but these + // platforms do not guarantee NaN payloads are preserved, which caused these + // tests to fail. + #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] + { + let nan0 = f32::NAN; + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); } - let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); - assert_eq!(nan0.next_up().to_bits(), identity(nan0).to_bits()); - assert_eq!(nan1.next_up().to_bits(), identity(nan1).to_bits()); - assert_eq!(nan2.next_up().to_bits(), identity(nan2).to_bits()); assert_eq!(f32::NEG_INFINITY.next_up(), f32::MIN); assert_eq!(f32::MIN.next_up(), -max_down); @@ -345,18 +345,18 @@ fn test_next_down() { let smallest_normal = f32::from_bits(0x0080_0000); // Check that NaNs roundtrip. - // Because x87 can lose NaN bits when passed through a function, ensure the reference value - // also passes through a function boundary. - #[inline(never)] - fn identity(x: f32) -> f32 { - crate::hint::black_box(x) + // Ignore test on x87 floating point, the code is still correct but these + // platforms do not guarantee NaN payloads are preserved, which caused these + // tests to fail. + #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] + { + let nan0 = f32::NAN; + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); } - let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); - assert_eq!(nan0.next_down().to_bits(), identity(nan0).to_bits()); - assert_eq!(nan1.next_down().to_bits(), identity(nan1).to_bits()); - assert_eq!(nan2.next_down().to_bits(), identity(nan2).to_bits()); assert_eq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); assert_eq!(f32::MIN.next_down(), f32::NEG_INFINITY); diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 2524bfe8b9988..e545a10c489d0 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -298,18 +298,18 @@ fn test_next_up() { let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); // Check that NaNs roundtrip. - // Because x87 can lose NaN bits when passed through a function, ensure the reference value - // also passes through a function boundary. - #[inline(never)] - fn identity(x: f64) -> f64 { - crate::hint::black_box(x) + // Ignore test on x87 floating point, the code is still correct but these + // platforms do not guarantee NaN payloads are preserved, which caused these + // tests to fail. + #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] + { + let nan0 = f64::NAN; + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); } - let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); - assert_eq!(nan0.next_up().to_bits(), identity(nan0).to_bits()); - assert_eq!(nan1.next_up().to_bits(), identity(nan1).to_bits()); - assert_eq!(nan2.next_up().to_bits(), identity(nan2).to_bits()); assert_eq!(f64::NEG_INFINITY.next_up(), f64::MIN); assert_eq!(f64::MIN.next_up(), -max_down); @@ -335,18 +335,18 @@ fn test_next_down() { let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); // Check that NaNs roundtrip. - // Because x87 can lose NaN bits when passed through a function, ensure the reference value - // also passes through a function boundary. - #[inline(never)] - fn identity(x: f64) -> f64 { - crate::hint::black_box(x) + // Ignore test on x87 floating point, the code is still correct but these + // platforms do not guarantee NaN payloads are preserved, which caused these + // tests to fail. + #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] + { + let nan0 = f64::NAN; + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); + assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); + assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); } - let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); - assert_eq!(nan0.next_down().to_bits(), identity(nan0).to_bits()); - assert_eq!(nan1.next_down().to_bits(), identity(nan1).to_bits()); - assert_eq!(nan2.next_down().to_bits(), identity(nan2).to_bits()); assert_eq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); assert_eq!(f64::MIN.next_down(), f64::NEG_INFINITY); From 18d61bfbf45f2e1cef44dce832cfe69feb76d13f Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sat, 19 Feb 2022 11:22:27 +0100 Subject: [PATCH 6/9] Skip next_up/down tests entirely on x87. --- library/std/src/f32/tests.rs | 108 ++++++++++++++++---------------- library/std/src/f64/tests.rs | 118 +++++++++++++++++------------------ 2 files changed, 114 insertions(+), 112 deletions(-) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 4fb9f73ef3771..0e2f0561ea06a 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -299,6 +299,19 @@ fn test_is_sign_negative() { assert!((-f32::NAN).is_sign_negative()); } +macro_rules! assert_f32_biteq { + ($left : expr, $right : expr) => { + let l: &f32 = &$left; + let r: &f32 = &$right; + let lb = l.to_bits(); + let rb = r.to_bits(); + assert_eq!(lb, rb, "float {} ({:#x}) is not equal to {} ({:#x})", *l, lb, *r, rb); + } +} + +// Ignore test on x87 floating point, these platforms do not guarantee NaN +// payloads are preserved and flush denormals to zero, failing the tests. +#[cfg(not(target_arch = "x86"))] #[test] fn test_next_up() { let tiny = f32::from_bits(1); @@ -306,36 +319,32 @@ fn test_next_up() { let max_down = f32::from_bits(0x7f7f_fffe); let largest_subnormal = f32::from_bits(0x007f_ffff); let smallest_normal = f32::from_bits(0x0080_0000); + assert_f32_biteq!(f32::NEG_INFINITY.next_up(), f32::MIN); + assert_f32_biteq!(f32::MIN.next_up(), -max_down); + assert_f32_biteq!((-1.0 - f32::EPSILON).next_up(), -1.0); + assert_f32_biteq!((-smallest_normal).next_up(), -largest_subnormal); + assert_f32_biteq!((-tiny_up).next_up(), -tiny); + assert_f32_biteq!((-tiny).next_up(), -0.0f32); + assert_f32_biteq!((-0.0f32).next_up(), tiny); + assert_f32_biteq!(0.0f32.next_up(), tiny); + assert_f32_biteq!(tiny.next_up(), tiny_up); + assert_f32_biteq!(largest_subnormal.next_up(), smallest_normal); + assert_f32_biteq!(1.0f32.next_up(), 1.0 + f32::EPSILON); + assert_f32_biteq!(f32::MAX.next_up(), f32::INFINITY); + assert_f32_biteq!(f32::INFINITY.next_up(), f32::INFINITY); // Check that NaNs roundtrip. - // Ignore test on x87 floating point, the code is still correct but these - // platforms do not guarantee NaN payloads are preserved, which caused these - // tests to fail. - #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] - { - let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); - assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); - } - - assert_eq!(f32::NEG_INFINITY.next_up(), f32::MIN); - assert_eq!(f32::MIN.next_up(), -max_down); - assert_eq!((-1.0 - f32::EPSILON).next_up(), -1.0); - assert_eq!((-smallest_normal).next_up(), -largest_subnormal); - assert_eq!((-tiny_up).next_up(), -tiny); - assert_eq!((-tiny).next_up().to_bits(), (-0.0f32).to_bits()); - assert_eq!((-0.0f32).next_up(), tiny); - assert_eq!(0.0f32.next_up(), tiny); - assert_eq!(tiny.next_up(), tiny_up); - assert_eq!(largest_subnormal.next_up(), smallest_normal); - assert_eq!(1.0f32.next_up(), 1.0 + f32::EPSILON); - assert_eq!(f32::MAX.next_up(), f32::INFINITY); - assert_eq!(f32::INFINITY.next_up(), f32::INFINITY); + let nan0 = f32::NAN; + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + assert_f32_biteq!(nan0.next_up(), nan0); + assert_f32_biteq!(nan1.next_up(), nan1); + assert_f32_biteq!(nan2.next_up(), nan2); } +// Ignore test on x87 floating point, these platforms do not guarantee NaN +// payloads are preserved and flush denormals to zero, failing the tests. +#[cfg(not(target_arch = "x86"))] #[test] fn test_next_down() { let tiny = f32::from_bits(1); @@ -343,35 +352,28 @@ fn test_next_down() { let max_down = f32::from_bits(0x7f7f_fffe); let largest_subnormal = f32::from_bits(0x007f_ffff); let smallest_normal = f32::from_bits(0x0080_0000); + assert_f32_biteq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); + assert_f32_biteq!(f32::MIN.next_down(), f32::NEG_INFINITY); + assert_f32_biteq!((-max_down).next_down(), f32::MIN); + assert_f32_biteq!((-1.0f32).next_down(), -1.0 - f32::EPSILON); + assert_f32_biteq!((-largest_subnormal).next_down(), -smallest_normal); + assert_f32_biteq!((-tiny).next_down(), -tiny_up); + assert_f32_biteq!((-0.0f32).next_down(), -tiny); + assert_f32_biteq!((0.0f32).next_down(), -tiny); + assert_f32_biteq!(tiny.next_down(), 0.0f32); + assert_f32_biteq!(tiny_up.next_down(), tiny); + assert_f32_biteq!(smallest_normal.next_down(), largest_subnormal); + assert_f32_biteq!((1.0 + f32::EPSILON).next_down(), 1.0f32); + assert_f32_biteq!(f32::MAX.next_down(), max_down); + assert_f32_biteq!(f32::INFINITY.next_down(), f32::MAX); // Check that NaNs roundtrip. - // Ignore test on x87 floating point, the code is still correct but these - // platforms do not guarantee NaN payloads are preserved, which caused these - // tests to fail. - #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] - { - let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); - assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); - } - - assert_eq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); - assert_eq!(f32::MIN.next_down(), f32::NEG_INFINITY); - assert_eq!((-max_down).next_down(), f32::MIN); - assert_eq!((-1.0f32).next_down(), -1.0 - f32::EPSILON); - assert_eq!((-largest_subnormal).next_down(), -smallest_normal); - assert_eq!((-tiny).next_down(), -tiny_up); - assert_eq!((-0.0f32).next_down(), -tiny); - assert_eq!((0.0f32).next_down(), -tiny); - assert_eq!(tiny.next_down().to_bits(), 0.0f32.to_bits()); - assert_eq!(tiny_up.next_down(), tiny); - assert_eq!(smallest_normal.next_down(), largest_subnormal); - assert_eq!((1.0 + f32::EPSILON).next_down(), 1.0f32); - assert_eq!(f32::MAX.next_down(), max_down); - assert_eq!(f32::INFINITY.next_down(), f32::MAX); + let nan0 = f32::NAN; + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + assert_f32_biteq!(nan0.next_down(), nan0); + assert_f32_biteq!(nan1.next_down(), nan1); + assert_f32_biteq!(nan2.next_down(), nan2); } #[test] diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index e545a10c489d0..8125b2492275b 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -289,6 +289,19 @@ fn test_is_sign_negative() { assert!((-f64::NAN).is_sign_negative()); } +macro_rules! assert_f64_biteq { + ($left : expr, $right : expr) => { + let l: &f64 = &$left; + let r: &f64 = &$right; + let lb = l.to_bits(); + let rb = r.to_bits(); + assert_eq!(lb, rb, "float {} ({:#x}) is not equal to {} ({:#x})", *l, lb, *r, rb); + } +} + +// Ignore test on x87 floating point, these platforms do not guarantee NaN +// payloads are preserved and flush denormals to zero, failing the tests. +#[cfg(not(target_arch = "x86"))] #[test] fn test_next_up() { let tiny = f64::from_bits(1); @@ -296,36 +309,31 @@ fn test_next_up() { let max_down = f64::from_bits(0x7fef_ffff_ffff_fffe); let largest_subnormal = f64::from_bits(0x000f_ffff_ffff_ffff); let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); - - // Check that NaNs roundtrip. - // Ignore test on x87 floating point, the code is still correct but these - // platforms do not guarantee NaN payloads are preserved, which caused these - // tests to fail. - #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] - { - let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); - assert_eq!(nan0.next_up().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_up().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_up().to_bits(), nan2.to_bits()); - } - - assert_eq!(f64::NEG_INFINITY.next_up(), f64::MIN); - assert_eq!(f64::MIN.next_up(), -max_down); - assert_eq!((-1.0 - f64::EPSILON).next_up(), -1.0); - assert_eq!((-smallest_normal).next_up(), -largest_subnormal); - assert_eq!((-tiny_up).next_up(), -tiny); - assert_eq!((-tiny).next_up().to_bits(), (-0.0f64).to_bits()); - assert_eq!((-0.0f64).next_up(), tiny); - assert_eq!(0.0f64.next_up(), tiny); - assert_eq!(tiny.next_up(), tiny_up); - assert_eq!(largest_subnormal.next_up(), smallest_normal); - assert_eq!(1.0f64.next_up(), 1.0 + f64::EPSILON); - assert_eq!(f64::MAX.next_up(), f64::INFINITY); - assert_eq!(f64::INFINITY.next_up(), f64::INFINITY); -} - + assert_f64_biteq!(f64::NEG_INFINITY.next_up(), f64::MIN); + assert_f64_biteq!(f64::MIN.next_up(), -max_down); + assert_f64_biteq!((-1.0 - f64::EPSILON).next_up(), -1.0); + assert_f64_biteq!((-smallest_normal).next_up(), -largest_subnormal); + assert_f64_biteq!((-tiny_up).next_up(), -tiny); + assert_f64_biteq!((-tiny).next_up(), -0.0f64); + assert_f64_biteq!((-0.0f64).next_up(), tiny); + assert_f64_biteq!(0.0f64.next_up(), tiny); + assert_f64_biteq!(tiny.next_up(), tiny_up); + assert_f64_biteq!(largest_subnormal.next_up(), smallest_normal); + assert_f64_biteq!(1.0f64.next_up(), 1.0 + f64::EPSILON); + assert_f64_biteq!(f64::MAX.next_up(), f64::INFINITY); + assert_f64_biteq!(f64::INFINITY.next_up(), f64::INFINITY); + + let nan0 = f64::NAN; + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + assert_f64_biteq!(nan0.next_up(), nan0); + assert_f64_biteq!(nan1.next_up(), nan1); + assert_f64_biteq!(nan2.next_up(), nan2); +} + +// Ignore test on x87 floating point, these platforms do not guarantee NaN +// payloads are preserved and flush denormals to zero, failing the tests. +#[cfg(not(target_arch = "x86"))] #[test] fn test_next_down() { let tiny = f64::from_bits(1); @@ -333,35 +341,27 @@ fn test_next_down() { let max_down = f64::from_bits(0x7fef_ffff_ffff_fffe); let largest_subnormal = f64::from_bits(0x000f_ffff_ffff_ffff); let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); - - // Check that NaNs roundtrip. - // Ignore test on x87 floating point, the code is still correct but these - // platforms do not guarantee NaN payloads are preserved, which caused these - // tests to fail. - #[cfg(not(all(target_arch = "x86", not(target_feature = "fxsr"))))] - { - let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); - assert_eq!(nan0.next_down().to_bits(), nan0.to_bits()); - assert_eq!(nan1.next_down().to_bits(), nan1.to_bits()); - assert_eq!(nan2.next_down().to_bits(), nan2.to_bits()); - } - - assert_eq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); - assert_eq!(f64::MIN.next_down(), f64::NEG_INFINITY); - assert_eq!((-max_down).next_down(), f64::MIN); - assert_eq!((-1.0f64).next_down(), -1.0 - f64::EPSILON); - assert_eq!((-largest_subnormal).next_down(), -smallest_normal); - assert_eq!((-tiny).next_down(), -tiny_up); - assert_eq!((-0.0f64).next_down(), -tiny); - assert_eq!((0.0f64).next_down(), -tiny); - assert_eq!(tiny.next_down().to_bits(), 0.0f64.to_bits()); - assert_eq!(tiny_up.next_down(), tiny); - assert_eq!(smallest_normal.next_down(), largest_subnormal); - assert_eq!((1.0 + f64::EPSILON).next_down(), 1.0f64); - assert_eq!(f64::MAX.next_down(), max_down); - assert_eq!(f64::INFINITY.next_down(), f64::MAX); + assert_f64_biteq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); + assert_f64_biteq!(f64::MIN.next_down(), f64::NEG_INFINITY); + assert_f64_biteq!((-max_down).next_down(), f64::MIN); + assert_f64_biteq!((-1.0f64).next_down(), -1.0 - f64::EPSILON); + assert_f64_biteq!((-largest_subnormal).next_down(), -smallest_normal); + assert_f64_biteq!((-tiny).next_down(), -tiny_up); + assert_f64_biteq!((-0.0f64).next_down(), -tiny); + assert_f64_biteq!((0.0f64).next_down(), -tiny); + assert_f64_biteq!(tiny.next_down(), 0.0f64); + assert_f64_biteq!(tiny_up.next_down(), tiny); + assert_f64_biteq!(smallest_normal.next_down(), largest_subnormal); + assert_f64_biteq!((1.0 + f64::EPSILON).next_down(), 1.0f64); + assert_f64_biteq!(f64::MAX.next_down(), max_down); + assert_f64_biteq!(f64::INFINITY.next_down(), f64::MAX); + + let nan0 = f64::NAN; + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + assert_f64_biteq!(nan0.next_down(), nan0); + assert_f64_biteq!(nan1.next_down(), nan1); + assert_f64_biteq!(nan2.next_down(), nan2); } #[test] From 85d5171dea32465a3b62acbe7e09fac026a1b20e Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sat, 19 Feb 2022 12:23:54 +0100 Subject: [PATCH 7/9] Float biteq macros can be unused if test is skipped. --- library/std/src/f32/tests.rs | 1 + library/std/src/f64/tests.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 0e2f0561ea06a..e1f9b3fe19d32 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -299,6 +299,7 @@ fn test_is_sign_negative() { assert!((-f32::NAN).is_sign_negative()); } +#[allow(unused_macros)] macro_rules! assert_f32_biteq { ($left : expr, $right : expr) => { let l: &f32 = &$left; diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 8125b2492275b..1619288bedb19 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -289,6 +289,7 @@ fn test_is_sign_negative() { assert!((-f64::NAN).is_sign_negative()); } +#[allow(unused_macros)] macro_rules! assert_f64_biteq { ($left : expr, $right : expr) => { let l: &f64 = &$left; From a1e251024de877f1746e02f856ccb2f666800f5e Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sat, 19 Feb 2022 19:05:55 +0100 Subject: [PATCH 8/9] Semicolon after macro_rules definition. --- library/std/src/f32/tests.rs | 2 +- library/std/src/f64/tests.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index e1f9b3fe19d32..4ec16c84aa916 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -307,7 +307,7 @@ macro_rules! assert_f32_biteq { let lb = l.to_bits(); let rb = r.to_bits(); assert_eq!(lb, rb, "float {} ({:#x}) is not equal to {} ({:#x})", *l, lb, *r, rb); - } + }; } // Ignore test on x87 floating point, these platforms do not guarantee NaN diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 1619288bedb19..12baa68f49b76 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -297,7 +297,7 @@ macro_rules! assert_f64_biteq { let lb = l.to_bits(); let rb = r.to_bits(); assert_eq!(lb, rb, "float {} ({:#x}) is not equal to {} ({:#x})", *l, lb, *r, rb); - } + }; } // Ignore test on x87 floating point, these platforms do not guarantee NaN From 3f10e6c86d9d602a821b3156266978c33a214965 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 15 Aug 2022 12:47:05 +0200 Subject: [PATCH 9/9] Say that the identity holds only for all finite numbers (aka not NaN) --- library/core/src/num/f32.rs | 4 ++-- library/core/src/num/f64.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index f6cef3f806704..dd956e2aa9a4d 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -688,7 +688,7 @@ impl f32 { /// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; /// - otherwise the unique least value greater than `self` is returned. /// - /// The identity `x.next_up() == -(-x).next_down()` holds for all `x`. When `x` + /// The identity `x.next_up() == -(-x).next_down()` holds for all non-NaN `x`. When `x` /// is finite `x == x.next_up().next_down()` also holds. /// /// ```rust @@ -738,7 +738,7 @@ impl f32 { /// - if `self` is [`MIN`] or [`NEG_INFINITY`], this returns [`NEG_INFINITY`]; /// - otherwise the unique greatest value less than `self` is returned. /// - /// The identity `x.next_down() == -(-x).next_up()` holds for all `x`. When `x` + /// The identity `x.next_down() == -(-x).next_up()` holds for all non-NaN `x`. When `x` /// is finite `x == x.next_down().next_up()` also holds. /// /// ```rust diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 328b19b203c26..bf11ada4f6228 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -698,7 +698,7 @@ impl f64 { /// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; /// - otherwise the unique least value greater than `self` is returned. /// - /// The identity `x.next_up() == -(-x).next_down()` holds for all `x`. When `x` + /// The identity `x.next_up() == -(-x).next_down()` holds for all non-NaN `x`. When `x` /// is finite `x == x.next_up().next_down()` also holds. /// /// ```rust @@ -748,7 +748,7 @@ impl f64 { /// - if `self` is [`MIN`] or [`NEG_INFINITY`], this returns [`NEG_INFINITY`]; /// - otherwise the unique greatest value less than `self` is returned. /// - /// The identity `x.next_down() == -(-x).next_up()` holds for all `x`. When `x` + /// The identity `x.next_down() == -(-x).next_up()` holds for all non-NaN `x`. When `x` /// is finite `x == x.next_down().next_up()` also holds. /// /// ```rust