Rollup merge of #129559 - RalfJung:float-nan-semantics, r=thomcc

float types: document NaN bit pattern guarantees

Part of https://github.com/rust-lang/rust/issues/128288: document the guarantees we make for NaN bit patterns.

Cc ``@tgross35``
This commit is contained in:
Trevor Gross 2024-08-27 01:46:53 -05:00 committed by GitHub
commit 75ae913ec0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 150 additions and 56 deletions

View File

@ -454,11 +454,14 @@ pub const fn classify(self) -> FpCategory {
}
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
/// `is_sign_positive` on a NaN might produce an unexpected result in some cases.
/// See [explanation of NaN as a special value](f128) for more info.
/// positive sign bit and positive infinity.
///
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_positive` on
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// #![feature(f128)]
@ -477,11 +480,14 @@ pub fn is_sign_positive(self) -> bool {
}
/// Returns `true` if `self` has a negative sign, including `-0.0`, NaNs with
/// negative sign bit and negative infinity. Note that IEEE 754 doesn't assign any
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
/// `is_sign_negative` on a NaN might produce an unexpected result in some cases.
/// See [explanation of NaN as a special value](f128) for more info.
/// negative sign bit and negative infinity.
///
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_negative` on
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// #![feature(f128)]
@ -750,7 +756,7 @@ pub fn min(self, other: f128) -> f128 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f128) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[inline]
#[unstable(feature = "f128", issue = "116909")]
// #[unstable(feature = "float_minimum_maximum", issue = "91079")]
@ -791,7 +797,7 @@ pub fn maximum(self, other: f128) -> f128 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f128) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[inline]
#[unstable(feature = "f128", issue = "116909")]
// #[unstable(feature = "float_minimum_maximum", issue = "91079")]

View File

@ -464,11 +464,14 @@ pub const fn classify(self) -> FpCategory {
}
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
/// `is_sign_positive` on a NaN might produce an unexpected result in some cases.
/// See [explanation of NaN as a special value](f16) for more info.
/// positive sign bit and positive infinity.
///
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_positive` on
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// #![feature(f16)]
@ -490,11 +493,14 @@ pub fn is_sign_positive(self) -> bool {
}
/// Returns `true` if `self` has a negative sign, including `-0.0`, NaNs with
/// negative sign bit and negative infinity. Note that IEEE 754 doesn't assign any
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
/// `is_sign_negative` on a NaN might produce an unexpected result in some cases.
/// See [explanation of NaN as a special value](f16) for more info.
/// negative sign bit and negative infinity.
///
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_negative` on
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// #![feature(f16)]
@ -762,7 +768,7 @@ pub fn min(self, other: f16) -> f16 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f16) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[inline]
#[unstable(feature = "f16", issue = "116909")]
// #[unstable(feature = "float_minimum_maximum", issue = "91079")]
@ -802,7 +808,7 @@ pub fn maximum(self, other: f16) -> f16 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f16) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[inline]
#[unstable(feature = "f16", issue = "116909")]
// #[unstable(feature = "float_minimum_maximum", issue = "91079")]

View File

@ -700,8 +700,9 @@ pub const fn classify(self) -> FpCategory {
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_positive` on
/// a NaN might produce an unexpected result in some cases. See [explanation
/// of NaN as a special value](f32) for more info.
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// let f = 7.0_f32;
@ -724,8 +725,9 @@ pub const fn is_sign_positive(self) -> bool {
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_negative` on
/// a NaN might produce an unexpected result in some cases. See [explanation
/// of NaN as a special value](f32) for more info.
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// let f = 7.0f32;
@ -954,7 +956,7 @@ pub fn min(self, other: f32) -> f32 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f32) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[must_use = "this returns the result of the comparison, without modifying either input"]
#[unstable(feature = "float_minimum_maximum", issue = "91079")]
#[inline]
@ -989,7 +991,7 @@ pub fn maximum(self, other: f32) -> f32 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f32) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[must_use = "this returns the result of the comparison, without modifying either input"]
#[unstable(feature = "float_minimum_maximum", issue = "91079")]
#[inline]

View File

@ -695,8 +695,9 @@ pub const fn classify(self) -> FpCategory {
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_positive` on
/// a NaN might produce an unexpected result in some cases. See [explanation
/// of NaN as a special value](f32) for more info.
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// let f = 7.0_f64;
@ -728,8 +729,9 @@ pub fn is_positive(self) -> bool {
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
/// conserved over arithmetic operations, the result of `is_sign_negative` on
/// a NaN might produce an unexpected result in some cases. See [explanation
/// of NaN as a special value](f32) for more info.
/// a NaN might produce an unexpected or non-portable result. See the [specification
/// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0`
/// if you need fully portable behavior (will return `false` for all NaNs).
///
/// ```
/// let f = 7.0_f64;
@ -968,7 +970,7 @@ pub fn min(self, other: f64) -> f64 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f32) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[must_use = "this returns the result of the comparison, without modifying either input"]
#[unstable(feature = "float_minimum_maximum", issue = "91079")]
#[inline]
@ -1003,7 +1005,7 @@ pub fn maximum(self, other: f64) -> f64 {
/// Note that this follows the semantics specified in IEEE 754-2019.
///
/// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN
/// operand is conserved; see [explanation of NaN as a special value](f32) for more info.
/// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info.
#[must_use = "this returns the result of the comparison, without modifying either input"]
#[unstable(feature = "float_minimum_maximum", issue = "91079")]
#[inline]

View File

@ -1190,6 +1190,11 @@ mod prim_f16 {}
/// portable or even fully deterministic! This means that there may be some
/// surprising results upon inspecting the bit patterns,
/// as the same calculations might produce NaNs with different bit patterns.
/// This also affects the sign of the NaN: checking `is_sign_positive` or `is_sign_negative` on
/// a NaN is the most common way to run into these surprising results.
/// (Checking `x >= 0.0` or `x <= 0.0` avoids those surprises, but also how negative/positive
/// zero are treated.)
/// See the section below for what exactly is guaranteed about the bit pattern of a NaN.
///
/// When a primitive operation (addition, subtraction, multiplication, or
/// division) is performed on this type, the result is rounded according to the
@ -1211,6 +1216,79 @@ mod prim_f16 {}
/// *[See also the `std::f32::consts` module](crate::f32::consts).*
///
/// [wikipedia]: https://en.wikipedia.org/wiki/Single-precision_floating-point_format
///
/// # NaN bit patterns
///
/// This section defines the possible NaN bit patterns returned by non-"bitwise" floating point
/// operations. The bitwise operations are unary `-`, `abs`, `copysign`; those are guaranteed to
/// exactly preserve the bit pattern of their input except for possibly changing the sign bit.
///
/// A floating-point NaN value consists of:
/// - a sign bit
/// - a quiet/signaling bit
/// - a payload, which makes up the rest of the significand (i.e., the mantissa) except for the
/// quiet/signaling bit.
///
/// Rust assumes that the quiet/signaling bit being set to `1` indicates a quiet NaN (QNaN), and a
/// value of `0` indicates a signaling NaN (SNaN). In the following we will hence just call it the
/// "quiet bit".
///
/// The following rules apply when a NaN value is returned: the result has a non-deterministic sign.
/// The quiet bit and payload are non-deterministically chosen from the following set of options:
///
/// - **Preferred NaN**: The quiet bit is set and the payload is all-zero.
/// - **Quieting NaN propagation**: The quiet bit is set and the payload is copied from any input
/// operand that is a NaN. If the inputs and outputs do not have the same payload size (i.e., for
/// `as` casts), then
/// - If the output is smaller than the input, low-order bits of the payload get dropped.
/// - If the output is larger than the input, the payload gets filled up with 0s in the low-order
/// bits.
/// - **Unchanged NaN propagation**: The quiet bit and payload are copied from any input operand
/// that is a NaN. If the inputs and outputs do not have the same size (i.e., for `as` casts), the
/// same rules as for "quieting NaN propagation" apply, with one caveat: if the output is smaller
/// than the input, droppig the low-order bits may result in a payload of 0; a payload of 0 is not
/// possible with a signaling NaN (the all-0 significand encodes an infinity) so unchanged NaN
/// propagation cannot occur with some inputs.
/// - **Target-specific NaN**: The quiet bit is set and the payload is picked from a target-specific
/// set of "extra" possible NaN payloads. The set can depend on the input operand values.
/// See the table below for the concrete NaNs this set contains on various targets.
///
/// In particular, if all input NaNs are quiet (or if there are no input NaNs), then the output NaN
/// is definitely quiet. Signaling NaN outputs can only occur if they are provided as an input
/// value. Similarly, if all input NaNs are preferred (or if there are no input NaNs) and the target
/// does not have any "extra" NaN payloads, then the output NaN is guaranteed to be preferred.
///
/// The non-deterministic choice happens when the operation is executed; i.e., the result of a
/// NaN-producing floating point operation is a stable bit pattern (looking at these bits multiple
/// times will yield consistent results), but running the same operation twice with the same inputs
/// can produce different results.
///
/// These guarantees are neither stronger nor weaker than those of IEEE 754: IEEE 754 guarantees
/// that an operation never returns a signaling NaN, whereas it is possible for operations like
/// `SNAN * 1.0` to return a signaling NaN in Rust. Conversely, IEEE 754 makes no statement at all
/// about which quiet NaN is returned, whereas Rust restricts the set of possible results to the
/// ones listed above.
///
/// Unless noted otherwise, the same rules also apply to NaNs returned by other library functions
/// (e.g. `min`, `minimum`, `max`, `maximum`); other aspects of their semantics and which IEEE 754
/// operation they correspond to are documented with the respective functions.
///
/// When a floating-point operation is executed in `const` context, the same rules apply: no
/// guarantee is made about which of the NaN bit patterns described above will be returned. The
/// result does not have to match what happens when executing the same code at runtime, and the
/// result can vary depending on factors such as compiler version and flags.
///
/// ### Target-specific "extra" NaN values
// FIXME: Is there a better place to put this?
///
/// | `target_arch` | Extra payloads possible on this platform |
/// |---------------|---------|
/// | `x86`, `x86_64`, `arm`, `aarch64`, `riscv32`, `riscv64` | None |
/// | `sparc`, `sparc64` | The all-one payload |
/// | `wasm32`, `wasm64` | If all input NaNs are quiet with all-zero payload: None.<br> Otherwise: all possible payloads. |
///
/// For targets not in this table, all payloads are possible.
#[stable(feature = "rust1", since = "1.0.0")]
mod prim_f32 {}

View File

@ -248,11 +248,11 @@ pub fn signum(self) -> f128 {
/// Returns a number composed of the magnitude of `self` and the sign of
/// `sign`.
///
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise
/// equal to `-self`. If `self` is a NaN, then a NaN with the sign bit of
/// `sign` is returned. Note, however, that conserving the sign bit on NaN
/// across arithmetical operations is not generally guaranteed.
/// See [explanation of NaN as a special value](primitive@f128) for more info.
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`.
/// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is
/// returned. Note, however, that conserving the sign bit on NaN across arithmetical operations
/// is not generally guaranteed. See [specification of NaN bit
/// patterns](primitive@f32#nan-bit-patterns) for more info.
///
/// # Examples
///

View File

@ -247,11 +247,11 @@ pub fn signum(self) -> f16 {
/// Returns a number composed of the magnitude of `self` and the sign of
/// `sign`.
///
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise
/// equal to `-self`. If `self` is a NaN, then a NaN with the sign bit of
/// `sign` is returned. Note, however, that conserving the sign bit on NaN
/// across arithmetical operations is not generally guaranteed.
/// See [explanation of NaN as a special value](primitive@f16) for more info.
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`.
/// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is
/// returned. Note, however, that conserving the sign bit on NaN across arithmetical operations
/// is not generally guaranteed. See [specification of NaN bit
/// patterns](primitive@f32#nan-bit-patterns) for more info.
///
/// # Examples
///

View File

@ -226,11 +226,11 @@ pub fn signum(self) -> f32 {
/// Returns a number composed of the magnitude of `self` and the sign of
/// `sign`.
///
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise
/// equal to `-self`. If `self` is a NaN, then a NaN with the sign bit of
/// `sign` is returned. Note, however, that conserving the sign bit on NaN
/// across arithmetical operations is not generally guaranteed.
/// See [explanation of NaN as a special value](primitive@f32) for more info.
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`.
/// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is
/// returned. Note, however, that conserving the sign bit on NaN across arithmetical operations
/// is not generally guaranteed. See [specification of NaN bit
/// patterns](primitive@f32#nan-bit-patterns) for more info.
///
/// # Examples
///

View File

@ -226,11 +226,11 @@ pub fn signum(self) -> f64 {
/// Returns a number composed of the magnitude of `self` and the sign of
/// `sign`.
///
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise
/// equal to `-self`. If `self` is a NaN, then a NaN with the sign bit of
/// `sign` is returned. Note, however, that conserving the sign bit on NaN
/// across arithmetical operations is not generally guaranteed.
/// See [explanation of NaN as a special value](primitive@f32) for more info.
/// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`.
/// If `self` is a NaN, then a NaN with the same payload as `self` and the sign bit of `sign` is
/// returned. Note, however, that conserving the sign bit on NaN across arithmetical operations
/// is not generally guaranteed. See [specification of NaN bit
/// patterns](primitive@f32#nan-bit-patterns) for more info.
///
/// # Examples
///