Skip to content

Commit c398efc

Browse files
committed
Auto merge of rust-lang#39271 - est31:add_float_bits, r=BurntSushi
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: rust-lang#40470
2 parents e621e1c + 0c14815 commit c398efc

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

src/doc/unstable-book/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
- [fd](fd.md)
7373
- [fd_read](fd-read.md)
7474
- [fixed_size_array](fixed-size-array.md)
75+
- [float_bits_conv](float-bits-conv.md)
7576
- [float_extras](float-extras.md)
7677
- [flt2dec](flt2dec.md)
7778
- [fmt_flags_align](fmt-flags-align.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# `float_bits_conv`
2+
3+
The tracking issue for this feature is: [#40470]
4+
5+
[#40470]: https://github.com/rust-lang/rust/issues/40470
6+
7+
------------------------

src/libstd/f32.rs

+89
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,68 @@ impl f32 {
12261226
pub fn atanh(self) -> f32 {
12271227
0.5 * ((2.0 * self) / (1.0 - self)).ln_1p()
12281228
}
1229+
1230+
/// Raw transmutation to `u32`.
1231+
///
1232+
/// Converts the `f32` into its raw memory representation,
1233+
/// similar to the `transmute` function.
1234+
///
1235+
/// Note that this function is distinct from casting.
1236+
///
1237+
/// # Examples
1238+
///
1239+
/// ```
1240+
/// #![feature(float_bits_conv)]
1241+
/// assert_ne!((1f32).to_bits(), 1f32 as u32); // to_bits() is not casting!
1242+
/// assert_eq!((12.5f32).to_bits(), 0x41480000);
1243+
///
1244+
/// ```
1245+
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
1246+
#[inline]
1247+
pub fn to_bits(self) -> u32 {
1248+
unsafe { ::mem::transmute(self) }
1249+
}
1250+
1251+
/// Raw transmutation from `u32`.
1252+
///
1253+
/// Converts the given `u32` containing the float's raw memory
1254+
/// representation into the `f32` type, similar to the
1255+
/// `transmute` function.
1256+
///
1257+
/// There is only one difference to a bare `transmute`:
1258+
/// Due to the implications onto Rust's safety promises being
1259+
/// uncertain, if the representation of a signaling NaN "sNaN" float
1260+
/// is passed to the function, the implementation is allowed to
1261+
/// return a quiet NaN instead.
1262+
///
1263+
/// Note that this function is distinct from casting.
1264+
///
1265+
/// # Examples
1266+
///
1267+
/// ```
1268+
/// #![feature(float_bits_conv)]
1269+
/// use std::f32;
1270+
/// let v = f32::from_bits(0x41480000);
1271+
/// let difference = (v - 12.5).abs();
1272+
/// assert!(difference <= 1e-5);
1273+
/// // Example for a signaling NaN value:
1274+
/// let snan = 0x7F800001;
1275+
/// assert_ne!(f32::from_bits(snan).to_bits(), snan);
1276+
/// ```
1277+
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
1278+
#[inline]
1279+
pub fn from_bits(mut v: u32) -> Self {
1280+
const EXP_MASK: u32 = 0x7F800000;
1281+
const QNAN_MASK: u32 = 0x00400000;
1282+
const FRACT_MASK: u32 = 0x007FFFFF;
1283+
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
1284+
// If we have a NaN value, we
1285+
// convert signaling NaN values to quiet NaN
1286+
// by setting the the highest bit of the fraction
1287+
v |= QNAN_MASK;
1288+
}
1289+
unsafe { ::mem::transmute(v) }
1290+
}
12291291
}
12301292

12311293
#[cfg(test)]
@@ -1870,4 +1932,31 @@ mod tests {
18701932
assert_approx_eq!(ln_2, 2f32.ln());
18711933
assert_approx_eq!(ln_10, 10f32.ln());
18721934
}
1935+
1936+
#[test]
1937+
fn test_float_bits_conv() {
1938+
assert_eq!((1f32).to_bits(), 0x3f800000);
1939+
assert_eq!((12.5f32).to_bits(), 0x41480000);
1940+
assert_eq!((1337f32).to_bits(), 0x44a72000);
1941+
assert_eq!((-14.25f32).to_bits(), 0xc1640000);
1942+
assert_approx_eq!(f32::from_bits(0x3f800000), 1.0);
1943+
assert_approx_eq!(f32::from_bits(0x41480000), 12.5);
1944+
assert_approx_eq!(f32::from_bits(0x44a72000), 1337.0);
1945+
assert_approx_eq!(f32::from_bits(0xc1640000), -14.25);
1946+
}
1947+
#[test]
1948+
fn test_snan_masking() {
1949+
let snan: u32 = 0x7F801337;
1950+
const PAYLOAD_MASK: u32 = 0x003FFFFF;
1951+
const QNAN_MASK: u32 = 0x00400000;
1952+
let nan_masked_fl = f32::from_bits(snan);
1953+
let nan_masked = nan_masked_fl.to_bits();
1954+
// Ensure that signaling NaNs don't stay the same
1955+
assert_ne!(nan_masked, snan);
1956+
// Ensure that we have a quiet NaN
1957+
assert_ne!(nan_masked & QNAN_MASK, 0);
1958+
assert!(nan_masked_fl.is_nan());
1959+
// Ensure the payload wasn't touched during conversion
1960+
assert_eq!(nan_masked & PAYLOAD_MASK, snan & PAYLOAD_MASK);
1961+
}
18731962
}

src/libstd/f64.rs

+74
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,68 @@ impl f64 {
11181118
}
11191119
}
11201120
}
1121+
1122+
/// Raw transmutation to `u64`.
1123+
///
1124+
/// Converts the `f64` into its raw memory representation,
1125+
/// similar to the `transmute` function.
1126+
///
1127+
/// Note that this function is distinct from casting.
1128+
///
1129+
/// # Examples
1130+
///
1131+
/// ```
1132+
/// #![feature(float_bits_conv)]
1133+
/// assert!((1f64).to_bits() != 1f64 as u64); // to_bits() is not casting!
1134+
/// assert_eq!((12.5f64).to_bits(), 0x4029000000000000);
1135+
///
1136+
/// ```
1137+
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
1138+
#[inline]
1139+
pub fn to_bits(self) -> u64 {
1140+
unsafe { ::mem::transmute(self) }
1141+
}
1142+
1143+
/// Raw transmutation from `u64`.
1144+
///
1145+
/// Converts the given `u64` containing the float's raw memory
1146+
/// representation into the `f64` type, similar to the
1147+
/// `transmute` function.
1148+
///
1149+
/// There is only one difference to a bare `transmute`:
1150+
/// Due to the implications onto Rust's safety promises being
1151+
/// uncertain, if the representation of a signaling NaN "sNaN" float
1152+
/// is passed to the function, the implementation is allowed to
1153+
/// return a quiet NaN instead.
1154+
///
1155+
/// Note that this function is distinct from casting.
1156+
///
1157+
/// # Examples
1158+
///
1159+
/// ```
1160+
/// #![feature(float_bits_conv)]
1161+
/// use std::f64;
1162+
/// let v = f64::from_bits(0x4029000000000000);
1163+
/// let difference = (v - 12.5).abs();
1164+
/// assert!(difference <= 1e-5);
1165+
/// // Example for a signaling NaN value:
1166+
/// let snan = 0x7FF0000000000001;
1167+
/// assert_ne!(f64::from_bits(snan).to_bits(), snan);
1168+
/// ```
1169+
#[unstable(feature = "float_bits_conv", reason = "recently added", issue = "40470")]
1170+
#[inline]
1171+
pub fn from_bits(mut v: u64) -> Self {
1172+
const EXP_MASK: u64 = 0x7FF0000000000000;
1173+
const QNAN_MASK: u64 = 0x0001000000000000;
1174+
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;
1175+
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
1176+
// If we have a NaN value, we
1177+
// convert signaling NaN values to quiet NaN
1178+
// by setting the the highest bit of the fraction
1179+
v |= QNAN_MASK;
1180+
}
1181+
unsafe { ::mem::transmute(v) }
1182+
}
11211183
}
11221184

11231185
#[cfg(test)]
@@ -1755,4 +1817,16 @@ mod tests {
17551817
assert_approx_eq!(ln_2, 2f64.ln());
17561818
assert_approx_eq!(ln_10, 10f64.ln());
17571819
}
1820+
1821+
#[test]
1822+
fn test_float_bits_conv() {
1823+
assert_eq!((1f64).to_bits(), 0x3ff0000000000000);
1824+
assert_eq!((12.5f64).to_bits(), 0x4029000000000000);
1825+
assert_eq!((1337f64).to_bits(), 0x4094e40000000000);
1826+
assert_eq!((-14.25f64).to_bits(), 0xc02c800000000000);
1827+
assert_approx_eq!(f64::from_bits(0x3ff0000000000000), 1.0);
1828+
assert_approx_eq!(f64::from_bits(0x4029000000000000), 12.5);
1829+
assert_approx_eq!(f64::from_bits(0x4094e40000000000), 1337.0);
1830+
assert_approx_eq!(f64::from_bits(0xc02c800000000000), -14.25);
1831+
}
17581832
}

src/libstd/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
#![feature(zero_one)]
322322
#![cfg_attr(test, feature(update_panic_count))]
323323
#![cfg_attr(stage0, feature(pub_restricted))]
324+
#![cfg_attr(test, feature(float_bits_conv))]
324325

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

0 commit comments

Comments
 (0)