Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added next_up and next_down for f32/f64. #88728

Closed
wants to merge 8 commits into from
98 changes: 98 additions & 0 deletions library/core/src/num/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,104 @@ impl f32 {
self.to_bits() & 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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically not true for x = NAN.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I think I see what you're getting at, == here is to be understood as a bitwise / definition equality, not floating point comparison where NaN != NaN.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I update this doc comment to say "for all non-NaN x"? Or maybe reword it to "x.next_up() is equivalent to -(-x).next_down() for all x" to prevent confusion with == failing for NaNs?

/// 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")]
orlp marked this conversation as resolved.
Show resolved Hide resolved
pub const fn next_up(self) -> Self {
orlp marked this conversation as resolved.
Show resolved Hide resolved
// 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`.
///
/// ```
Expand Down
98 changes: 98 additions & 0 deletions library/core/src/num/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,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`.
///
/// ```
Expand Down
75 changes: 75 additions & 0 deletions library/std/src/f32/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,81 @@ fn test_is_sign_negative() {
assert!((-f32::NAN).is_sign_negative());
}

#[test]
orlp marked this conversation as resolved.
Show resolved Hide resolved
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.
// 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(), 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);
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.
// 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(), 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);
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;
Expand Down
75 changes: 75 additions & 0 deletions library/std/src/f64/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,81 @@ 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.
// 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(), 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);
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.
// 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(), 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);
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;
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 @@ -285,6 +285,7 @@
#![feature(exact_size_is_empty)]
#![feature(exhaustive_patterns)]
#![feature(extend_one)]
#![feature(float_next_up_down)]
#![feature(fn_traits)]
#![feature(format_args_nl)]
#![feature(gen_future)]
Expand Down