【问题标题】:How to decode and encode a float in Rust?如何在 Rust 中解码和编码浮点数?
【发布时间】:2017-02-23 03:23:38
【问题描述】:

我想在 Rust 中对浮点数进行解码、存储和编码。我知道num::Float::integer_decode(),但我宁愿不要失去任何 精度。也就是说,除非我编码的格式小于我编码的格式。

【问题讨论】:

  • integer_decode() 不会丢失精度——它只是将浮点数解构为其组成部分。
  • @trentcl 我知道,但它在上述函数的文档中显示为编码。

标签: floating-point rust decode encode


【解决方案1】:

将浮点位解释为整数并将值打印为十六进制:

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 转换为[u8; 8] 并存储它而不进行不必要的转换?
  • @PavelStrakhov 听起来像是答案的主要候选人!
  • 哦!我希望f32f64 可以直接编码为十六进制(这是获得可移植表示的最简单方法),但显然它们没有实现LowerHexUpperHex。失望:(
  • @MatthieuM。是的,也没有十六进制浮点文字,所以如果你需要一个非常具体的浮点数,你必须通过transmute :-(
  • @Shepmaster:我认为在这里提出一个 RFC 是值得的;既支持文字又支持格式化/解析。格式化/解析比十进制格式简单得多;然而,文字解析可能需要一些技巧(以避免积分的歧义)。
【解决方案2】:

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));
}

【讨论】:

  • 从函数附带的文档来看,在编码过程中可能会丢失精度。
  • @JeroenBollen 你能链接这些文档吗?我在std 中找不到此信息。我实现了fn integer_encode((mantissa, exponent, sign): (u64, i16, i8)) -&gt; f64 { (sign as f64) * (mantissa as f64) * (2f64.powf(exponent as f64)) },它适用于我提供给integer_encode(integer_decode(X))的任何X,除了NaN(不过,我的integer_encode可能有问题)。
  • 我确实在 OP 中链接到它。
  • @JeroenBollen 该链接未提供有关精度损失的任何信息。
  • @JeroenBollen 也许这只是f32 的情况?我无法重现 f64 的任何差异,至少不足以破坏 assert_eq!(2.0, integer_encode(integer_decode(2.0)));,就像您链接的文档中的示例一样。
【解决方案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(&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。如果您不想要整个框架而只想序列化浮点数,可以使用dtoa(由 serde_json 使用),但我不确定它是否提供了强大的精度保证。

【讨论】:

    【解决方案4】:

    较新版本的 Rust 提供的选项比其他一些答案建议的更安全:

    • 从 Rust 1.20 开始,您可以使用 to_bitsfrom_bitsu64 二进制表示进行转换。
    • 从Rust 1.40开始,你可以使用to_be_bytesfrom_be_bytes来处理[u8; 8]。 (还有小端字节序和原生字节序的方法。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多