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

Make float::from_bits transmute #46012

Merged
merged 1 commit into from
Nov 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 43 additions & 48 deletions src/libstd/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -998,10 +998,13 @@ impl f32 {

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

/// Raw transmutation from `u32`.
///
/// Converts the given `u32` containing the float's raw memory
/// representation into the `f32` type, similar to the
/// `transmute` function.
/// This is currently identical to `transmute::<u32, f32>(v)` on all platforms.
/// It turns out this is incredibly portable, for two reasons:
///
/// * Floats and Ints have the same endianess on all supported platforms.
/// * IEEE-754 very precisely specifies the bit layout of floats.
///
/// However there is one caveat: prior to the 2008 version of IEEE-754, how
/// to interpret the NaN signaling bit wasn't actually specified. Most platforms
/// (notably x86 and ARM) picked the interpretation that was ultimately
/// standardized in 2008, but some didn't (notably MIPS). As a result, all
/// signaling NaNs on MIPS are quiet NaNs on x86, and vice-versa.
///
/// Rather than trying to preserve signaling-ness cross-platform, this
/// implementation favours preserving the exact bits. This means that
/// any payloads encoded in NaNs will be preserved even if the result of
/// this method is sent over the network from an x86 machine to a MIPS one.
///
/// If the results of this method are only manipulated by the same
/// architecture that produced them, then there is no portability concern.
///
/// If the input isn't NaN, then there is no portability concern.
///
/// There is only one difference to a bare `transmute`:
/// Due to the implications onto Rust's safety promises being
/// uncertain, if the representation of a signaling NaN "sNaN" float
/// is passed to the function, the implementation is allowed to
/// return a quiet NaN instead.
/// If you don't care about signalingness (very likely), then there is no
/// portability concern.
///
/// Note that this function is distinct from casting.
/// Note that this function is distinct from `as` casting, which attempts to
/// preserve the *numeric* value, and not the bitwise value.
///
/// # Examples
///
Expand All @@ -1037,25 +1056,11 @@ impl f32 {
/// let v = f32::from_bits(0x41480000);
/// let difference = (v - 12.5).abs();
/// assert!(difference <= 1e-5);
/// // Example for a signaling NaN value:
/// let snan = 0x7F800001;
/// assert_ne!(f32::from_bits(snan).to_bits(), snan);
/// ```
#[stable(feature = "float_bits_conv", since = "1.20.0")]
#[inline]
pub fn from_bits(mut v: u32) -> Self {
const EXP_MASK: u32 = 0x7F800000;
const FRACT_MASK: u32 = 0x007FFFFF;
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
// While IEEE 754-2008 specifies encodings for quiet NaNs
// and signaling ones, certain MIPS and PA-RISC
// CPUs treat signaling NaNs differently.
// Therefore to be safe, we pass a known quiet NaN
// if v is any kind of NaN.
// The check above only assumes IEEE 754-1985 to be
// valid.
v = unsafe { ::mem::transmute(NAN) };
}
pub fn from_bits(v: u32) -> Self {
// It turns out the safety issues with sNaN were overblown! Hooray!
unsafe { ::mem::transmute(v) }
}
}
Expand Down Expand Up @@ -1646,25 +1651,15 @@ mod tests {
assert_approx_eq!(f32::from_bits(0x41480000), 12.5);
assert_approx_eq!(f32::from_bits(0x44a72000), 1337.0);
assert_approx_eq!(f32::from_bits(0xc1640000), -14.25);
}
#[test]
fn test_snan_masking() {
// NOTE: this test assumes that our current platform
// implements IEEE 754-2008 that specifies the difference
// in encoding of quiet and signaling NaNs.
// If you are porting Rust to a platform that does not
// implement IEEE 754-2008 (but e.g. IEEE 754-1985, which
// only says that "Signaling NaNs shall be reserved operands"
// but doesn't specify the actual setup), feel free to
// cfg out this test.
let snan: u32 = 0x7F801337;
const QNAN_MASK: u32 = 0x00400000;
let nan_masked_fl = f32::from_bits(snan);
let nan_masked = nan_masked_fl.to_bits();
// Ensure that signaling NaNs don't stay the same
assert_ne!(nan_masked, snan);
// Ensure that we have a quiet NaN
assert_ne!(nan_masked & QNAN_MASK, 0);
assert!(nan_masked_fl.is_nan());

// Check that NaNs roundtrip their bits regardless of signalingness
// 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits
let masked_nan1 = f32::NAN.to_bits() ^ 0x002A_AAAA;
let masked_nan2 = f32::NAN.to_bits() ^ 0x0055_5555;
assert!(f32::from_bits(masked_nan1).is_nan());
assert!(f32::from_bits(masked_nan2).is_nan());

assert_eq!(f32::from_bits(masked_nan1).to_bits(), masked_nan1);
assert_eq!(f32::from_bits(masked_nan2).to_bits(), masked_nan2);
}
}
71 changes: 43 additions & 28 deletions src/libstd/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,10 +952,13 @@ impl f64 {

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

/// Raw transmutation from `u64`.
///
/// Converts the given `u64` containing the float's raw memory
/// representation into the `f64` type, similar to the
/// `transmute` function.
/// This is currently identical to `transmute::<u64, f64>(v)` on all platforms.
/// It turns out this is incredibly portable, for two reasons:
///
/// * Floats and Ints have the same endianess on all supported platforms.
/// * IEEE-754 very precisely specifies the bit layout of floats.
///
/// However there is one caveat: prior to the 2008 version of IEEE-754, how
/// to interpret the NaN signaling bit wasn't actually specified. Most platforms
/// (notably x86 and ARM) picked the interpretation that was ultimately
/// standardized in 2008, but some didn't (notably MIPS). As a result, all
/// signaling NaNs on MIPS are quiet NaNs on x86, and vice-versa.
///
/// Rather than trying to preserve signaling-ness cross-platform, this
/// implementation favours preserving the exact bits. This means that
/// any payloads encoded in NaNs will be preserved even if the result of
/// this method is sent over the network from an x86 machine to a MIPS one.
///
/// If the results of this method are only manipulated by the same
/// architecture that produced them, then there is no portability concern.
///
/// There is only one difference to a bare `transmute`:
/// Due to the implications onto Rust's safety promises being
/// uncertain, if the representation of a signaling NaN "sNaN" float
/// is passed to the function, the implementation is allowed to
/// return a quiet NaN instead.
/// If the input isn't NaN, then there is no portability concern.
///
/// Note that this function is distinct from casting.
/// If you don't care about signalingness (very likely), then there is no
/// portability concern.
///
/// Note that this function is distinct from `as` casting, which attempts to
/// preserve the *numeric* value, and not the bitwise value.
///
/// # Examples
///
Expand All @@ -991,25 +1010,11 @@ impl f64 {
/// let v = f64::from_bits(0x4029000000000000);
/// let difference = (v - 12.5).abs();
/// assert!(difference <= 1e-5);
/// // Example for a signaling NaN value:
/// let snan = 0x7FF0000000000001;
/// assert_ne!(f64::from_bits(snan).to_bits(), snan);
/// ```
#[stable(feature = "float_bits_conv", since = "1.20.0")]
#[inline]
pub fn from_bits(mut v: u64) -> Self {
const EXP_MASK: u64 = 0x7FF0000000000000;
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
// While IEEE 754-2008 specifies encodings for quiet NaNs
// and signaling ones, certain MIPS and PA-RISC
// CPUs treat signaling NaNs differently.
// Therefore to be safe, we pass a known quiet NaN
// if v is any kind of NaN.
// The check above only assumes IEEE 754-1985 to be
// valid.
v = unsafe { ::mem::transmute(NAN) };
}
pub fn from_bits(v: u64) -> Self {
// It turns out the safety issues with sNaN were overblown! Hooray!
unsafe { ::mem::transmute(v) }
}
}
Expand Down Expand Up @@ -1596,5 +1601,15 @@ mod tests {
assert_approx_eq!(f64::from_bits(0x4029000000000000), 12.5);
assert_approx_eq!(f64::from_bits(0x4094e40000000000), 1337.0);
assert_approx_eq!(f64::from_bits(0xc02c800000000000), -14.25);

// Check that NaNs roundtrip their bits regardless of signalingness
// 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits
let masked_nan1 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA;
let masked_nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555;
assert!(f64::from_bits(masked_nan1).is_nan());
assert!(f64::from_bits(masked_nan2).is_nan());

assert_eq!(f64::from_bits(masked_nan1).to_bits(), masked_nan1);
assert_eq!(f64::from_bits(masked_nan2).to_bits(), masked_nan2);
}
}