Skip to content

Commit d15a6ef

Browse files
committedAug 25, 2024
float types: document NaN bit pattern guarantees
1 parent 717aec0 commit d15a6ef

File tree

5 files changed

+115
-28
lines changed

5 files changed

+115
-28
lines changed
 

Diff for: ‎library/core/src/num/f128.rs

+16-10
Original file line numberDiff line numberDiff line change
@@ -454,11 +454,14 @@ impl f128 {
454454
}
455455

456456
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
457-
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
458-
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
459-
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
460-
/// `is_sign_positive` on a NaN might produce an unexpected result in some cases.
461-
/// See [explanation of NaN as a special value](f128) for more info.
457+
/// positive sign bit and positive infinity.
458+
///
459+
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
460+
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
461+
/// conserved over arithmetic operations, the result of `is_sign_positive` on
462+
/// a NaN might produce an unexpected or non-portable result. See [explanation
463+
/// of NaN as a special value](f32) for more info. Use `self >= 0.0` if you
464+
/// need fully portable behavior and are okay with `-0.0` being considered positive.
462465
///
463466
/// ```
464467
/// #![feature(f128)]
@@ -477,11 +480,14 @@ impl f128 {
477480
}
478481

479482
/// Returns `true` if `self` has a negative sign, including `-0.0`, NaNs with
480-
/// negative sign bit and negative infinity. Note that IEEE 754 doesn't assign any
481-
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
482-
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
483-
/// `is_sign_negative` on a NaN might produce an unexpected result in some cases.
484-
/// See [explanation of NaN as a special value](f128) for more info.
483+
/// negative sign bit and negative infinity.
484+
///
485+
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
486+
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
487+
/// conserved over arithmetic operations, the result of `is_sign_negative` on
488+
/// a NaN might produce an unexpected or non-portable result. See [explanation
489+
/// of NaN as a special value](f32) for more info. Use `self <= 0.0` if you
490+
/// need fully portable behavior and are okay with `+0.0` being considered negative.
485491
///
486492
/// ```
487493
/// #![feature(f128)]

Diff for: ‎library/core/src/num/f16.rs

+16-10
Original file line numberDiff line numberDiff line change
@@ -464,11 +464,14 @@ impl f16 {
464464
}
465465

466466
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
467-
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
468-
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
469-
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
470-
/// `is_sign_positive` on a NaN might produce an unexpected result in some cases.
471-
/// See [explanation of NaN as a special value](f16) for more info.
467+
/// positive sign bit and positive infinity.
468+
///
469+
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
470+
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
471+
/// conserved over arithmetic operations, the result of `is_sign_positive` on
472+
/// a NaN might produce an unexpected or non-portable result. See [explanation
473+
/// of NaN as a special value](f32) for more info. Use `self >= 0.0` if you
474+
/// need fully portable behavior and are okay with `-0.0` being considered positive.
472475
///
473476
/// ```
474477
/// #![feature(f16)]
@@ -490,11 +493,14 @@ impl f16 {
490493
}
491494

492495
/// Returns `true` if `self` has a negative sign, including `-0.0`, NaNs with
493-
/// negative sign bit and negative infinity. Note that IEEE 754 doesn't assign any
494-
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
495-
/// the bit pattern of NaNs are conserved over arithmetic operations, the result of
496-
/// `is_sign_negative` on a NaN might produce an unexpected result in some cases.
497-
/// See [explanation of NaN as a special value](f16) for more info.
496+
/// negative sign bit and negative infinity.
497+
///
498+
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
499+
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
500+
/// conserved over arithmetic operations, the result of `is_sign_negative` on
501+
/// a NaN might produce an unexpected or non-portable result. See [explanation
502+
/// of NaN as a special value](f32) for more info. Use `self <= 0.0` if you
503+
/// need fully portable behavior and are okay with `+0.0` being considered negative.
498504
///
499505
/// ```
500506
/// #![feature(f16)]

Diff for: ‎library/core/src/num/f32.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -700,8 +700,9 @@ impl f32 {
700700
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
701701
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
702702
/// conserved over arithmetic operations, the result of `is_sign_positive` on
703-
/// a NaN might produce an unexpected result in some cases. See [explanation
704-
/// of NaN as a special value](f32) for more info.
703+
/// a NaN might produce an unexpected or non-portable result. See [explanation
704+
/// of NaN as a special value](f32) for more info. Use `self >= 0.0` if you
705+
/// need fully portable behavior and are okay with `-0.0` being considered positive.
705706
///
706707
/// ```
707708
/// let f = 7.0_f32;
@@ -724,8 +725,9 @@ impl f32 {
724725
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
725726
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
726727
/// conserved over arithmetic operations, the result of `is_sign_negative` on
727-
/// a NaN might produce an unexpected result in some cases. See [explanation
728-
/// of NaN as a special value](f32) for more info.
728+
/// a NaN might produce an unexpected or non-portable result. See [explanation
729+
/// of NaN as a special value](f32) for more info. Use `self <= 0.0` if you
730+
/// need fully portable behavior and are okay with `+0.0` being considered negative.
729731
///
730732
/// ```
731733
/// let f = 7.0f32;

Diff for: ‎library/core/src/num/f64.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -695,8 +695,9 @@ impl f64 {
695695
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
696696
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
697697
/// conserved over arithmetic operations, the result of `is_sign_positive` on
698-
/// a NaN might produce an unexpected result in some cases. See [explanation
699-
/// of NaN as a special value](f32) for more info.
698+
/// a NaN might produce an unexpected or non-portable result. See [explanation
699+
/// of NaN as a special value](f32) for more info. Use `self >= 0.0` if you
700+
/// need fully portable behavior and are okay with `-0.0` being considered positive.
700701
///
701702
/// ```
702703
/// let f = 7.0_f64;
@@ -728,8 +729,9 @@ impl f64 {
728729
/// Note that IEEE 754 doesn't assign any meaning to the sign bit in case of
729730
/// a NaN, and as Rust doesn't guarantee that the bit pattern of NaNs are
730731
/// conserved over arithmetic operations, the result of `is_sign_negative` on
731-
/// a NaN might produce an unexpected result in some cases. See [explanation
732-
/// of NaN as a special value](f32) for more info.
732+
/// a NaN might produce an unexpected or non-portable result. See [explanation
733+
/// of NaN as a special value](f32) for more info. Use `self <= 0.0` if you
734+
/// need fully portable behavior and are okay with `+0.0` being considered negative.
733735
///
734736
/// ```
735737
/// let f = 7.0_f64;

Diff for: ‎library/core/src/primitive_docs.rs

+71
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,11 @@ mod prim_f16 {}
11901190
/// portable or even fully deterministic! This means that there may be some
11911191
/// surprising results upon inspecting the bit patterns,
11921192
/// as the same calculations might produce NaNs with different bit patterns.
1193+
/// This also affects the sign of the NaN: checking `is_sign_positive` or `is_sign_negative` on
1194+
/// a NaN is the most common way to run into these surprising results.
1195+
/// (Checking `x >= 0.0` or `x <= 0.0` avoids those surprises, but also how negative/positive
1196+
/// zero are treated.)
1197+
/// See the section below for what exactly is guaranteed about the bit pattern of a NaN.
11931198
///
11941199
/// When a primitive operation (addition, subtraction, multiplication, or
11951200
/// division) is performed on this type, the result is rounded according to the
@@ -1211,6 +1216,72 @@ mod prim_f16 {}
12111216
/// *[See also the `std::f32::consts` module](crate::f32::consts).*
12121217
///
12131218
/// [wikipedia]: https://en.wikipedia.org/wiki/Single-precision_floating-point_format
1219+
///
1220+
/// # NaN bit patterns
1221+
///
1222+
/// This section defines the possible NaN bit patterns returned by non-"bitwise" floating point
1223+
/// operations. The bitwise operations are unary `-`, `abs`, `copysign`; those are guaranteed to
1224+
/// exactly preserve the bit pattern of their input except for possibly changing the sign bit.
1225+
///
1226+
/// A floating-point NaN value consists of:
1227+
/// - a sign bit
1228+
/// - a quiet/signaling bit
1229+
/// - a payload, which makes up the rest of the significand (i.e., the mantissa) except for the
1230+
/// quiet/signaling bit.
1231+
///
1232+
/// Rust assumes that the quiet/signaling bit being set to ``1`` indicates a quiet NaN (QNaN), and a
1233+
/// value of ``0`` indicates a signaling NaN (SNaN). In the following we will hence just call it the
1234+
/// "quiet bit".
1235+
///
1236+
/// The following rules apply when a NaN value is returned: the result has a non-deterministic sign.
1237+
/// The quiet bit and payload are non-deterministically chosen from the following set of options:
1238+
///
1239+
/// - **Preferred NaN**: The quiet bit is set and the payload is all-zero.
1240+
/// - **Quieting NaN propagation**: The quiet bit is set and the payload is copied from any input
1241+
/// operand that is a NaN. If the inputs and outputs do not have the same payload size (i.e., for
1242+
/// `as` casts), then
1243+
/// - If the output is smaller than the input, low-order bits of the payload get dropped.
1244+
/// - If the output is larger than the input, the payload gets filled up with 0s in the low-order
1245+
/// bits.
1246+
/// - **Unchanged NaN propagation**: The quiet bit and payload are copied from any input operand
1247+
/// that is a NaN. If the inputs and outputs do not have the same size (i.e., for `as` casts), the
1248+
/// same rules as for "quieting NaN propagation" apply, with one caveat: if the output is smaller
1249+
/// than the input, droppig the low-order bits may result in a payload of 0; a payload of 0 is not
1250+
/// possible with a signaling NaN (the all-0 significand encodes an infinity) so unchanged NaN
1251+
/// propagation cannot occur with some inputs.
1252+
/// - **Target-specific NaN**: The quiet bit is set and the payload is picked from a target-specific
1253+
/// set of "extra" possible NaN payloads. The set can depend on the input operand values. This set
1254+
/// is empty on x86, ARM, and RISC-V (32bit and 64bit), but can be non-empty on other
1255+
/// architectures. Targets where this set is non-empty should document this in a suitable
1256+
/// location, e.g. their platform support page. (For instance, on wasm, if any input NaN does not
1257+
/// have the preferred all-zero payload or any input NaN is an SNaN, then this set contains all
1258+
/// possible payloads; otherwise, it is empty. On SPARC, this set consists of the all-one
1259+
/// payload.)
1260+
///
1261+
/// In particular, if all input NaNs are quiet (or if there are no input NaNs), then the output NaN
1262+
/// is definitely quiet. Signaling NaN outputs can only occur if they are provided as an input
1263+
/// value. Similarly, if all input NaNs are preferred (or if there are no input NaNs) and the target
1264+
/// does not have any "extra" NaN payloads, then the output NaN is guaranteed to be preferred.
1265+
///
1266+
/// The non-deterministic choice happens when the operation is executed; i.e., the result of a
1267+
/// NaN-producing floating point operation is a stable bit pattern (looking at these bits multiple
1268+
/// times will yield consistent results), but running the same operation twice with the same inputs
1269+
/// can produce different results.
1270+
///
1271+
/// These guarantees are neither stronger nor weaker than those of IEEE 754: IEEE 754 guarantees
1272+
/// that an operation never returns a signaling NaN, whereas it is possible for operations like
1273+
/// `SNAN * 1.0` to return a signaling NaN in Rust. Conversely, IEEE 754 makes no statement at all
1274+
/// about which quiet NaN is returned, whereas Rust restricts the set of possible results to the
1275+
/// ones listed above.
1276+
///
1277+
/// Unless noted otherwise, the same rules also apply to NaNs returned by other library functions
1278+
/// (e.g. `min`, `minimum`, `max`, `maximum`); other aspects of their semantics and which IEEE 754
1279+
/// operation they correspond to are documented with the respective functions.
1280+
///
1281+
/// When a floating-point operation is executed in `const` context, the same rules apply: no
1282+
/// guarantee is made about which of the NaN bit patterns described above will be returned. The
1283+
/// result does not have to match what happens when executing the same code at runtime, and the
1284+
/// result can vary depending on factors such as compiler version and flags.
12141285
#[stable(feature = "rust1", since = "1.0.0")]
12151286
mod prim_f32 {}
12161287

0 commit comments

Comments
 (0)