Как декодировать и кодировать значение с плавающей запятой в Rust?

#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))); , как в примере в документах, которые вы связали.