Skip to content

Commit 439576f

Browse files
committed
Make float::from_bits transmute (and update the documentation to reflect this).
The current implementation/documentation was made to avoid sNaN because of potential safety issues implied by old/bad LLVM documentation. These issues aren't real, so we can just make the implementation transmute (as permitted by the existing documentation of this method). Also the documentation didn't actually match the behaviour: it said we may change sNaNs, but in fact we canonicalized *all* NaNs. Also an example in the documentation was wrong: it said we *always* change sNaNs, when the documentation was explicitly written to indicate it was implementation-defined. This makes to_bits and from_bits perfectly roundtrip cross-platform, except for one caveat: although the 2008 edition of IEEE-754 specifies how to interpet the signaling bit, earlier editions didn't. This lead to some platforms picking the opposite interpretation, so all signaling NaNs on x86/ARM are quiet on MIPS, and vice-versa. NaN-boxing is a fairly important optimization, while we don't even guarantee that float operations properly preserve signalingness. As such, this seems like the more natural strategy to take (as opposed to trying to mangle the signaling bit on a per-platform basis). This implementation is also, of course, faster.
1 parent 88a28ff commit 439576f

File tree

2 files changed

+86
-76
lines changed

2 files changed

+86
-76
lines changed

src/libstd/f32.rs

+43-48
Original file line numberDiff line numberDiff line change
@@ -998,10 +998,13 @@ impl f32 {
998998

999999
/// Raw transmutation to `u32`.
10001000
///
1001-
/// Converts the `f32` into its raw memory representation,
1002-
/// similar to the `transmute` function.
1001+
/// This is currently identical to `transmute::<f32, u32>(self)` on all platforms.
10031002
///
1004-
/// Note that this function is distinct from casting.
1003+
/// See `from_bits` for some discussion of the portability of this operation
1004+
/// (there are almost no issues).
1005+
///
1006+
/// Note that this function is distinct from `as` casting, which attempts to
1007+
/// preserve the *numeric* value, and not the bitwise value.
10051008
///
10061009
/// # Examples
10071010
///
@@ -1018,17 +1021,33 @@ impl f32 {
10181021

10191022
/// Raw transmutation from `u32`.
10201023
///
1021-
/// Converts the given `u32` containing the float's raw memory
1022-
/// representation into the `f32` type, similar to the
1023-
/// `transmute` function.
1024+
/// This is currently identical to `transmute::<u32, f32>(v)` on all platforms.
1025+
/// It turns out this is incredibly portable, for two reasons:
1026+
///
1027+
/// * Floats and Ints have the same endianess on all supported platforms.
1028+
/// * IEEE-754 very precisely specifies the bit layout of floats.
1029+
///
1030+
/// However there is one caveat: prior to the 2008 version of IEEE-754, how
1031+
/// to interpret the NaN signaling bit wasn't actually specified. Most platforms
1032+
/// (notably x86 and ARM) picked the interpretation that was ultimately
1033+
/// standardized in 2008, but some didn't (notably MIPS). As a result, all
1034+
/// signaling NaNs on MIPS are quiet NaNs on x86, and vice-versa.
1035+
///
1036+
/// Rather than trying to preserve signaling-ness cross-platform, this
1037+
/// implementation favours preserving the exact bits. This means that
1038+
/// any payloads encoded in NaNs will be preserved even if the result of
1039+
/// this method is sent over the network from an x86 machine to a MIPS one.
1040+
///
1041+
/// If the results of this method are only manipulated by the same
1042+
/// architecture that produced them, then there is no portability concern.
1043+
///
1044+
/// If the input isn't NaN, then there is no portability concern.
10241045
///
1025-
/// There is only one difference to a bare `transmute`:
1026-
/// Due to the implications onto Rust's safety promises being
1027-
/// uncertain, if the representation of a signaling NaN "sNaN" float
1028-
/// is passed to the function, the implementation is allowed to
1029-
/// return a quiet NaN instead.
1046+
/// If you don't care about signalingness (very likely), then there is no
1047+
/// portability concern.
10301048
///
1031-
/// Note that this function is distinct from casting.
1049+
/// Note that this function is distinct from `as` casting, which attempts to
1050+
/// preserve the *numeric* value, and not the bitwise value.
10321051
///
10331052
/// # Examples
10341053
///
@@ -1037,25 +1056,11 @@ impl f32 {
10371056
/// let v = f32::from_bits(0x41480000);
10381057
/// let difference = (v - 12.5).abs();
10391058
/// assert!(difference <= 1e-5);
1040-
/// // Example for a signaling NaN value:
1041-
/// let snan = 0x7F800001;
1042-
/// assert_ne!(f32::from_bits(snan).to_bits(), snan);
10431059
/// ```
10441060
#[stable(feature = "float_bits_conv", since = "1.20.0")]
10451061
#[inline]
1046-
pub fn from_bits(mut v: u32) -> Self {
1047-
const EXP_MASK: u32 = 0x7F800000;
1048-
const FRACT_MASK: u32 = 0x007FFFFF;
1049-
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
1050-
// While IEEE 754-2008 specifies encodings for quiet NaNs
1051-
// and signaling ones, certain MIPS and PA-RISC
1052-
// CPUs treat signaling NaNs differently.
1053-
// Therefore to be safe, we pass a known quiet NaN
1054-
// if v is any kind of NaN.
1055-
// The check above only assumes IEEE 754-1985 to be
1056-
// valid.
1057-
v = unsafe { ::mem::transmute(NAN) };
1058-
}
1062+
pub fn from_bits(v: u32) -> Self {
1063+
// It turns out the safety issues with sNaN were overblown! Hooray!
10591064
unsafe { ::mem::transmute(v) }
10601065
}
10611066
}
@@ -1646,25 +1651,15 @@ mod tests {
16461651
assert_approx_eq!(f32::from_bits(0x41480000), 12.5);
16471652
assert_approx_eq!(f32::from_bits(0x44a72000), 1337.0);
16481653
assert_approx_eq!(f32::from_bits(0xc1640000), -14.25);
1649-
}
1650-
#[test]
1651-
fn test_snan_masking() {
1652-
// NOTE: this test assumes that our current platform
1653-
// implements IEEE 754-2008 that specifies the difference
1654-
// in encoding of quiet and signaling NaNs.
1655-
// If you are porting Rust to a platform that does not
1656-
// implement IEEE 754-2008 (but e.g. IEEE 754-1985, which
1657-
// only says that "Signaling NaNs shall be reserved operands"
1658-
// but doesn't specify the actual setup), feel free to
1659-
// cfg out this test.
1660-
let snan: u32 = 0x7F801337;
1661-
const QNAN_MASK: u32 = 0x00400000;
1662-
let nan_masked_fl = f32::from_bits(snan);
1663-
let nan_masked = nan_masked_fl.to_bits();
1664-
// Ensure that signaling NaNs don't stay the same
1665-
assert_ne!(nan_masked, snan);
1666-
// Ensure that we have a quiet NaN
1667-
assert_ne!(nan_masked & QNAN_MASK, 0);
1668-
assert!(nan_masked_fl.is_nan());
1654+
1655+
// Check that NaNs roundtrip their bits regardless of signalingness
1656+
// 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits
1657+
let masked_nan1 = f32::NAN.to_bits() ^ 0x002A_AAAA;
1658+
let masked_nan2 = f32::NAN.to_bits() ^ 0x0055_5555;
1659+
assert!(f32::from_bits(masked_nan1).is_nan());
1660+
assert!(f32::from_bits(masked_nan2).is_nan());
1661+
1662+
assert_eq!(f32::from_bits(masked_nan1).to_bits(), masked_nan1);
1663+
assert_eq!(f32::from_bits(masked_nan2).to_bits(), masked_nan2);
16691664
}
16701665
}

src/libstd/f64.rs

+43-28
Original file line numberDiff line numberDiff line change
@@ -952,10 +952,13 @@ impl f64 {
952952

953953
/// Raw transmutation to `u64`.
954954
///
955-
/// Converts the `f64` into its raw memory representation,
956-
/// similar to the `transmute` function.
955+
/// This is currently identical to `transmute::<f64, u64>(self)` on all platforms.
957956
///
958-
/// Note that this function is distinct from casting.
957+
/// See `from_bits` for some discussion of the portability of this operation
958+
/// (there are almost no issues).
959+
///
960+
/// Note that this function is distinct from `as` casting, which attempts to
961+
/// preserve the *numeric* value, and not the bitwise value.
959962
///
960963
/// # Examples
961964
///
@@ -972,17 +975,33 @@ impl f64 {
972975

973976
/// Raw transmutation from `u64`.
974977
///
975-
/// Converts the given `u64` containing the float's raw memory
976-
/// representation into the `f64` type, similar to the
977-
/// `transmute` function.
978+
/// This is currently identical to `transmute::<u64, f64>(v)` on all platforms.
979+
/// It turns out this is incredibly portable, for two reasons:
980+
///
981+
/// * Floats and Ints have the same endianess on all supported platforms.
982+
/// * IEEE-754 very precisely specifies the bit layout of floats.
983+
///
984+
/// However there is one caveat: prior to the 2008 version of IEEE-754, how
985+
/// to interpret the NaN signaling bit wasn't actually specified. Most platforms
986+
/// (notably x86 and ARM) picked the interpretation that was ultimately
987+
/// standardized in 2008, but some didn't (notably MIPS). As a result, all
988+
/// signaling NaNs on MIPS are quiet NaNs on x86, and vice-versa.
989+
///
990+
/// Rather than trying to preserve signaling-ness cross-platform, this
991+
/// implementation favours preserving the exact bits. This means that
992+
/// any payloads encoded in NaNs will be preserved even if the result of
993+
/// this method is sent over the network from an x86 machine to a MIPS one.
994+
///
995+
/// If the results of this method are only manipulated by the same
996+
/// architecture that produced them, then there is no portability concern.
978997
///
979-
/// There is only one difference to a bare `transmute`:
980-
/// Due to the implications onto Rust's safety promises being
981-
/// uncertain, if the representation of a signaling NaN "sNaN" float
982-
/// is passed to the function, the implementation is allowed to
983-
/// return a quiet NaN instead.
998+
/// If the input isn't NaN, then there is no portability concern.
984999
///
985-
/// Note that this function is distinct from casting.
1000+
/// If you don't care about signalingness (very likely), then there is no
1001+
/// portability concern.
1002+
///
1003+
/// Note that this function is distinct from `as` casting, which attempts to
1004+
/// preserve the *numeric* value, and not the bitwise value.
9861005
///
9871006
/// # Examples
9881007
///
@@ -991,25 +1010,11 @@ impl f64 {
9911010
/// let v = f64::from_bits(0x4029000000000000);
9921011
/// let difference = (v - 12.5).abs();
9931012
/// assert!(difference <= 1e-5);
994-
/// // Example for a signaling NaN value:
995-
/// let snan = 0x7FF0000000000001;
996-
/// assert_ne!(f64::from_bits(snan).to_bits(), snan);
9971013
/// ```
9981014
#[stable(feature = "float_bits_conv", since = "1.20.0")]
9991015
#[inline]
1000-
pub fn from_bits(mut v: u64) -> Self {
1001-
const EXP_MASK: u64 = 0x7FF0000000000000;
1002-
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;
1003-
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
1004-
// While IEEE 754-2008 specifies encodings for quiet NaNs
1005-
// and signaling ones, certain MIPS and PA-RISC
1006-
// CPUs treat signaling NaNs differently.
1007-
// Therefore to be safe, we pass a known quiet NaN
1008-
// if v is any kind of NaN.
1009-
// The check above only assumes IEEE 754-1985 to be
1010-
// valid.
1011-
v = unsafe { ::mem::transmute(NAN) };
1012-
}
1016+
pub fn from_bits(v: u64) -> Self {
1017+
// It turns out the safety issues with sNaN were overblown! Hooray!
10131018
unsafe { ::mem::transmute(v) }
10141019
}
10151020
}
@@ -1596,5 +1601,15 @@ mod tests {
15961601
assert_approx_eq!(f64::from_bits(0x4029000000000000), 12.5);
15971602
assert_approx_eq!(f64::from_bits(0x4094e40000000000), 1337.0);
15981603
assert_approx_eq!(f64::from_bits(0xc02c800000000000), -14.25);
1604+
1605+
// Check that NaNs roundtrip their bits regardless of signalingness
1606+
// 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits
1607+
let masked_nan1 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA;
1608+
let masked_nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555;
1609+
assert!(f64::from_bits(masked_nan1).is_nan());
1610+
assert!(f64::from_bits(masked_nan2).is_nan());
1611+
1612+
assert_eq!(f64::from_bits(masked_nan1).to_bits(), masked_nan1);
1613+
assert_eq!(f64::from_bits(masked_nan2).to_bits(), masked_nan2);
15991614
}
16001615
}

0 commit comments

Comments
 (0)