Как декодировать и кодировать поплавок в Rust?
Я хочу декодировать, хранить и кодировать поплавок в Rust. Я знаю о num::Float::integer_decode()
но я бы предпочел не терять точность. То есть, если формат, в который я кодирую, меньше, чем формат, из которого я, конечно, кодирую.
3 ответа
Новые версии Rust предоставляют более безопасные варианты, чем предлагают некоторые другие ответы:
- Начиная с Rust 1.20, вы можете использовать to_bits и from_bits для преобразования в и из
u64
двоичное представление. - Начиная с Rust 1.40 вы можете использовать to_be_bytes и from_be_bytes для работы с
[u8; 8]
. (Существуют также методы для порядка байтов с прямым порядком байтов и собственного порядка байтов.)
Интерпретировать биты с плавающей запятой как целое число и вывести значение как шестнадцатеричное:
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);
}
Если вы не собираетесь передавать сериализованные данные между компьютерами или уверены, что представление с плавающей запятой одинаково на всех целевых платформах, вы можете сохранить байтовое представление числа:
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);
}
}
Вы также можете использовать существующую платформу сериализации, например, serde. Если вам не нужен весь фреймворк и вы хотите просто сериализовать float, вы можете использовать dtoa (он используется serde_json), хотя я не уверен, обеспечивает ли он строгие гарантии точности.
Что случилось с integer_decode()
? Это без потерь и работает для конечных чисел, а также NaN и бесконечности:
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));
}