Skip to content

Commit

Permalink
Auto merge of #39271 - est31:add_float_bits, r=BurntSushi
Browse files Browse the repository at this point in the history
Add functions to safely transmute float to int

The safe subset of Rust tries to be as powerful as possible. While it is very powerful already, its currently impossible to safely transmute integers to floats. While crates exist that provide a safe interface, most prominently the `iee754` crate (which also inspired naming of the added functions), they themselves only use the unsafe `mem::transmute` function to accomplish this task.

Also, including an entire crate for just two lines of unsafe code seems quite wasteful.

That's why this PR adds functions to safely transmute integers to floats and vice versa, currently gated by the newly added `float_bits_conv` feature.

The functions added are no niche case. Not just `ieee754` [currently implements](https://github.com/huonw/ieee754/blob/master/src/lib.rs#L441) float to int transmutation via unsafe code but also the [very popular `byteorder` crate](https://github.com/BurntSushi/byteorder/blob/1.0.0/src/lib.rs#L258). This functionality of byteorder is in turn used by higher level crates. I only give two examples out of many: [chor](https://github.com/pyfisch/cbor/blob/a7363ea9aaf372e3d24b52414b5c76552ecc91c8/src/ser.rs#L227) and [bincode](https://github.com/TyOverby/bincode/blob/f06a4cfcb5b194e54d4997c200c75b88b6c3fba4/src/serde/reader.rs#L218).

One alternative would be to manually use functions like pow or multiplication by 1 to get a similar result, but they only work in the int -> float direction, and are not bit exact, and much slower (also, most likely the optimizer will never optimize it to a transmute because the conversion is not bit exact while the transmute is).

Tracking issue: #40470
  • Loading branch information
bors committed Apr 18, 2017
2 parents e621e1c + 0c14815 commit 0fdf2eb
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/doc/unstable-book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
- [fd](fd.md)
- [fd_read](fd-read.md)
- [fixed_size_array](fixed-size-array.md)
- [float_bits_conv](float-bits-conv.md)
- [float_extras](float-extras.md)
- [flt2dec](flt2dec.md)
- [fmt_flags_align](fmt-flags-align.md)
Expand Down
7 changes: 7 additions & 0 deletions src/doc/unstable-book/src/float-bits-conv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# `float_bits_conv`

The tracking issue for this feature is: [#40470]

[#40470]: https://github.com/rust-lang/rust/issues/40470

------------------------
89 changes: 89 additions & 0 deletions src/libstd/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,68 @@ impl f32 {
pub fn atanh(self) -> f32 {
0.5 * ((2.0 * self) / (1.0 - self)).ln_1p()
}

/// Raw transmutation to `u32`.
///
/// Converts the `f32` into its raw memory representation,
/// similar to the `transmute` function.
///
/// Note that this function is distinct from casting.
///
/// # Examples
///
/// ```
/// #![feature(float_bits_conv)]
/// assert_ne!((1f32).to_bits(), 1f32 as u32); // to_bits() is not casting!
/// assert_eq!((12.5f32).to_bits(), 0x41480000);
///
/// ```
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
#[inline]
pub fn to_bits(self) -> u32 {
unsafe { ::mem::transmute(self) }
}

/// Raw transmutation from `u32`.
///
/// Converts the given `u32` containing the float's raw memory
/// representation into the `f32` type, similar to the
/// `transmute` function.
///
/// 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.
///
/// Note that this function is distinct from casting.
///
/// # Examples
///
/// ```
/// #![feature(float_bits_conv)]
/// use std::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);
/// ```
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
#[inline]
pub fn from_bits(mut v: u32) -> Self {
const EXP_MASK: u32 = 0x7F800000;
const QNAN_MASK: u32 = 0x00400000;
const FRACT_MASK: u32 = 0x007FFFFF;
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
// If we have a NaN value, we
// convert signaling NaN values to quiet NaN
// by setting the the highest bit of the fraction
v |= QNAN_MASK;
}
unsafe { ::mem::transmute(v) }
}
}

#[cfg(test)]
Expand Down Expand Up @@ -1870,4 +1932,31 @@ mod tests {
assert_approx_eq!(ln_2, 2f32.ln());
assert_approx_eq!(ln_10, 10f32.ln());
}

#[test]
fn test_float_bits_conv() {
assert_eq!((1f32).to_bits(), 0x3f800000);
assert_eq!((12.5f32).to_bits(), 0x41480000);
assert_eq!((1337f32).to_bits(), 0x44a72000);
assert_eq!((-14.25f32).to_bits(), 0xc1640000);
assert_approx_eq!(f32::from_bits(0x3f800000), 1.0);
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() {
let snan: u32 = 0x7F801337;
const PAYLOAD_MASK: u32 = 0x003FFFFF;
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());
// Ensure the payload wasn't touched during conversion
assert_eq!(nan_masked & PAYLOAD_MASK, snan & PAYLOAD_MASK);
}
}
74 changes: 74 additions & 0 deletions src/libstd/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,68 @@ impl f64 {
}
}
}

/// Raw transmutation to `u64`.
///
/// Converts the `f64` into its raw memory representation,
/// similar to the `transmute` function.
///
/// Note that this function is distinct from casting.
///
/// # Examples
///
/// ```
/// #![feature(float_bits_conv)]
/// assert!((1f64).to_bits() != 1f64 as u64); // to_bits() is not casting!
/// assert_eq!((12.5f64).to_bits(), 0x4029000000000000);
///
/// ```
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
#[inline]
pub fn to_bits(self) -> u64 {
unsafe { ::mem::transmute(self) }
}

/// Raw transmutation from `u64`.
///
/// Converts the given `u64` containing the float's raw memory
/// representation into the `f64` type, similar to the
/// `transmute` function.
///
/// 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.
///
/// Note that this function is distinct from casting.
///
/// # Examples
///
/// ```
/// #![feature(float_bits_conv)]
/// use std::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);
/// ```
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
#[inline]
pub fn from_bits(mut v: u64) -> Self {
const EXP_MASK: u64 = 0x7FF0000000000000;
const QNAN_MASK: u64 = 0x0001000000000000;
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
// If we have a NaN value, we
// convert signaling NaN values to quiet NaN
// by setting the the highest bit of the fraction
v |= QNAN_MASK;
}
unsafe { ::mem::transmute(v) }
}
}

#[cfg(test)]
Expand Down Expand Up @@ -1755,4 +1817,16 @@ mod tests {
assert_approx_eq!(ln_2, 2f64.ln());
assert_approx_eq!(ln_10, 10f64.ln());
}

#[test]
fn test_float_bits_conv() {
assert_eq!((1f64).to_bits(), 0x3ff0000000000000);
assert_eq!((12.5f64).to_bits(), 0x4029000000000000);
assert_eq!((1337f64).to_bits(), 0x4094e40000000000);
assert_eq!((-14.25f64).to_bits(), 0xc02c800000000000);
assert_approx_eq!(f64::from_bits(0x3ff0000000000000), 1.0);
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);
}
}
1 change: 1 addition & 0 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
#![feature(zero_one)]
#![cfg_attr(test, feature(update_panic_count))]
#![cfg_attr(stage0, feature(pub_restricted))]
#![cfg_attr(test, feature(float_bits_conv))]

// Explicitly import the prelude. The compiler uses this same unstable attribute
// to import the prelude implicitly when building crates that depend on std.
Expand Down

0 comments on commit 0fdf2eb

Please sign in to comment.