#floating-point #rust #decode #encode
#с плавающей запятой #Ржавчина #декодировать #кодировать
Вопрос:
Я хочу декодировать, сохранять и кодировать значение с плавающей запятой в Rust. Я знаю об num::Float::integer_decode()
этом, но я бы предпочел не терять точность. То есть, если формат, в который я кодирую, не меньше, чем формат, из которого я кодирую, конечно.
Комментарии:
1.
integer_decode()
не теряет точности — это просто разбиение числа с плавающей запятой на его компоненты.2. @trentcl я в курсе, но это выглядит так, как указано в документации вышеупомянутой функции.
Ответ №1:
Более новые версии Rust предоставляют более безопасные варианты, чем предполагают некоторые другие ответы:
- Начиная с Rust 1.20, вы можете использовать to_bits и from_bits для преобразования в
u64
двоичное представление и обратно. - Начиная с Rust 1.40, вы можете использовать to_be_bytes и from_be_bytes для обработки
[u8; 8]
. (Существуют также методы для младшего порядка байтов и собственного порядка байтов.)
Ответ №2:
Интерпретируйте биты с плавающей запятой как целое число и распечатайте значение как шестнадцатеричное:
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(amp;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);
}
Комментарии:
1. Почему бы просто не преобразовать
f64
[u8; 8]
и сохранить его без ненужных преобразований?2. @PavelStrakhov звучит как главный кандидат на ответ!
3. О! Я надеялся
f32
, что иf64
будет напрямую кодироваться как шестнадцатеричное (что является самым простым способом получить переносимое представление), но, по-видимому, они не реализуютLowerHex
илиUpperHex
. Разочарование: (4. @MatthieuM. да, и шестнадцатеричного литерала с плавающей запятой тоже нет, поэтому, если вам нужно иметь очень конкретное значение с плавающей запятой, вам нужно пройти через
transmute
🙁5. @Shepmaster: Я думаю, что здесь стоило бы создать RFC; как для поддержки литералов, так и для форматирования / синтаксического анализа. Форматирование / синтаксический анализ намного проще, чем десятичный формат; однако для буквального синтаксического анализа могут потребоваться некоторые хитрости (чтобы избежать двусмысленностей с интегралами).
Ответ №3:
Если вы не собираетесь передавать сериализованные данные между компьютерами или уверены, что представление с плавающей запятой одинаково на всех целевых платформах, вы можете сохранить байтовое представление числа:
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(amp;bytes).unwrap();
}
{
let mut file = std::fs::File::open("/tmp/1").unwrap();
let mut bytes: [u8; 8] = unsafe { std::mem::uninitialized() };
file.read_exact(amp;mut bytes).unwrap();
let num: f64 = unsafe { std::mem::transmute(bytes) };
println!("result: {}", num);
}
}
Вы также можете использовать существующую платформу сериализации, например serde. Если вам не нужен весь фреймворк и вы просто хотите сериализовать значения с плавающей запятой, вы можете использовать dtoa (он используется serde_json ), хотя я не уверен, обеспечивает ли он надежные гарантии точности.
Ответ №4:
Что не так с 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) amp; 0x7ff) as i16;
let mantissa = if exponent == 0 {
(bits amp; 0xfffffffffffff) << 1
} else {
(bits amp; 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));
}
Комментарии:
1. Исходя из документов, прилагаемых к функции, она может потерять точность во время кодирования.
2. @JeroenBollen вы можете связать эти документы? Я не могу найти эту информацию в
std
. Я реализовалfn integer_encode((mantissa, exponent, sign): (u64, i16, i8)) -> f64 { (sign as f64) * (mantissa as f64) * (2f64.powf(exponent as f64)) }
, и это работает для любогоX
, в который я загружаю, заinteger_encode(integer_decode(X))
исключениемNaN
(возможно, с моим что-то не такinteger_encode
).3. Я сделал ссылку на него в OP.
4. @JeroenBollen эта ссылка не предоставляет никакой информации о потере точности.
5. @JeroenBollen может быть, это только случай с
f32
? Я не могу воспроизвести никакой разницыf64
, по крайней мере, недостаточно, чтобы сломатьсяassert_eq!(2.0, integer_encode(integer_decode(2.0)));
, как в примере в документах, которые вы связали.