about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-08-28 22:31:19 +0000
committerbors <bors@rust-lang.org>2022-08-28 22:31:19 +0000
commit1ea4efd0656599f824e2567a5b7a95454f701c03 (patch)
tree41a7588dfa4aafc000b5afdf157a46777fff9a8a
parentce36e88256f09078519f8bc6b21e4dc88f88f523 (diff)
parent3f10e6c86d9d602a821b3156266978c33a214965 (diff)
downloadrust-1ea4efd0656599f824e2567a5b7a95454f701c03.tar.gz
rust-1ea4efd0656599f824e2567a5b7a95454f701c03.zip
Auto merge of #100578 - Urgau:float-next-up-down, r=scottmcm
Add next_up and next_down for f32/f64 - take 2

This is a revival of https://github.com/rust-lang/rust/pull/88728 which staled due to inactivity of the original author. I've address the last review comment.

---

This is a pull request implementing the features described at https://github.com/rust-lang/rfcs/pull/3173.

`@rustbot` label +T-libs-api -T-libs
r? `@scottmcm`
cc `@orlp`
-rw-r--r--library/core/src/num/f32.rs100
-rw-r--r--library/core/src/num/f64.rs100
-rw-r--r--library/std/src/f32/tests.rs78
-rw-r--r--library/std/src/f64/tests.rs76
-rw-r--r--library/std/src/lib.rs1
5 files changed, 355 insertions, 0 deletions
diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs
index 509a6c3cac5..da41ea53635 100644
--- a/library/core/src/num/f32.rs
+++ b/library/core/src/num/f32.rs
@@ -678,6 +678,106 @@ impl f32 {
         unsafe { mem::transmute::<f32, u32>(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 non-NaN `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 = "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.
+        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 non-NaN `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 = "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.
+        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 5aeaa4b9ada..631d559df5f 100644
--- a/library/core/src/num/f64.rs
+++ b/library/core/src/num/f64.rs
@@ -688,6 +688,106 @@ 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 non-NaN `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 = "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.
+        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 non-NaN `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 = "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.
+        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 69fa203ff4e..4ec16c84aa9 100644
--- a/library/std/src/f32/tests.rs
+++ b/library/std/src/f32/tests.rs
@@ -299,6 +299,84 @@ 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;
+        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);
+    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);
+    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.
+    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);
+    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);
+    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.
+    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]
 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 5c163cfe90e..12baa68f49b 100644
--- a/library/std/src/f64/tests.rs
+++ b/library/std/src/f64/tests.rs
@@ -289,6 +289,82 @@ 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;
+        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);
+    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);
+    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);
+    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);
+    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]
 fn test_mul_add() {
     let nan: f64 = f64::NAN;
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index c918c9ba818..f5dcdab4cd5 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -288,6 +288,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)]