I want to decode, store and encode a float in Rust. I know about num::Float::integer_decode()
but I'd rather not lose any precision. That is, unless the format I encode into is smaller than the format I encode from of course.
Newer versions of Rust provide safer options than some of the other answers suggest:
- From Rust 1.20, you can use to_bits and from_bits to convert to and from the
u64
binary representation. - From Rust 1.40, you can use to_be_bytes and from_be_bytes to deal with
[u8; 8]
. (There are also methods for little-endian byte order and native byte order.)
Interpret the floating point bits as an integer and print out the value as hex:
use std::mem;
fn main() {
let a_third: f64 = 1.0 / 3.0;
let as_int: u64 = unsafe { mem::transmute(a_third) };
println!("{}", as_int);
let as_string = format!("{:016x}", as_int);
println!("{}", as_string);
let back_to_int = u64::from_str_radix(&as_string, 16).expect("Not an integer");
println!("{}", back_to_int);
let back_to_float: f64 = unsafe { mem::transmute(back_to_int) };
println!("{}", back_to_float);
assert_eq!(back_to_float, a_third);
}
f64
to [u8; 8]
and store it without unnecessary conversions? –
Lennox f32
and f64
to be directly encodable as hexadecimal (which is the easiest way to get a portable representation) but apparently they do not implement LowerHex
or UpperHex
. Disappointment :( –
Uturn transmute
:-( –
Partlow If you don't intend to transfer serialized data between machines or you're certain that float representation is the same on all platforms you target, you can store byte representation of the number:
use std::io::{Read, Write};
fn main() {
{
let num: f64 = 1.0 / 3.0;
let bytes: [u8; 8] = unsafe { std::mem::transmute(num) };
let mut file = std::fs::File::create("/tmp/1").unwrap();
file.write_all(&bytes).unwrap();
}
{
let mut file = std::fs::File::open("/tmp/1").unwrap();
let mut bytes: [u8; 8] = unsafe { std::mem::uninitialized() };
file.read_exact(&mut bytes).unwrap();
let num: f64 = unsafe { std::mem::transmute(bytes) };
println!("result: {}", num);
}
}
You can also use existing serialization framework, like serde. If you don't want the entire framework and just want to serialize floats, you can use dtoa (it's used by serde_json), although I'm not sure if it provides strong precision guarantees.
What's wrong with integer_decode()
? It is lossless and works for finite numbers as well as NaN and infinities:
use std::mem;
fn integer_decode(val: f64) -> (u64, i16, i8) {
let bits: u64 = unsafe { mem::transmute(val) };
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
let mantissa = if exponent == 0 {
(bits & 0xfffffffffffff) << 1
} else {
(bits & 0xfffffffffffff) | 0x10000000000000
};
exponent -= 1023 + 52;
(mantissa, exponent, sign)
}
fn main() {
println!("{:?}", integer_decode(std::f64::NAN));
println!("{:?}", integer_decode(std::f64::INFINITY));
println!("{:?}", integer_decode(std::f64::NEG_INFINITY));
}
std
. I implemented fn integer_encode((mantissa, exponent, sign): (u64, i16, i8)) -> f64 { (sign as f64) * (mantissa as f64) * (2f64.powf(exponent as f64)) }
and it works for any X
I feed into integer_encode(integer_decode(X))
except for NaN
(maybe there is something wrong with my integer_encode
, though). –
Schizophyceous f32
? I can't reproduce any difference for f64
, at least not enough to break assert_eq!(2.0, integer_encode(integer_decode(2.0)));
like in the example in the docs you linked. –
Schizophyceous © 2022 - 2024 — McMap. All rights reserved.
integer_decode()
does not lose precision -- it's merely a deconstruction of the floating-point number into its components. – Suckling