【发布时间】:2017-05-05 19:20:00
【问题描述】:
是否存在字节序列,当转换为 f32 或 f64 时,会在 Rust 中产生未定义的行为?我将非有限值(例如 NaN、Infinity 等)计为有效浮点值。
this answer 的 cmets 提示从原始字节转换浮点数可能存在一些问题。
【问题讨论】:
-
可能是信号 NaN。
标签: floating-point rust undefined-behavior
是否存在字节序列,当转换为 f32 或 f64 时,会在 Rust 中产生未定义的行为?我将非有限值(例如 NaN、Infinity 等)计为有效浮点值。
this answer 的 cmets 提示从原始字节转换浮点数可能存在一些问题。
【问题讨论】:
标签: floating-point rust undefined-behavior
The Rust reference 提供了一个很好的列表,其中列出了发生未定义行为的情况。其中,与该问题最密切相关的是:
原始类型中的无效值,即使在私有字段/本地中也是如此:
- 悬空/空引用或框
- bool 中的值不是 false (0) 或 true (1)
- 枚举中的判别式未包含在类型定义中
- char 中的一个值,它是 char::MAX 的替代值或更高
- str 中的非 UTF-8 字节序列
仍然没有列出浮点类型。这是因为根据 IEEE 754-2008 binary32 和 binary64 浮点类型,任何位序列(f32 为 32 位;f64 为 64 位)都是浮点值的有效状态。它们可能不是 normal(其他类是 zero、subnormal、infinite 或 不是数字),但仍然有效。
最后,Another Way 应该总是在 transmute 周围。特别是,byteorder crate 提供了一种从字节流中读取数字的安全且直观的方法。
use byteorder::{ByteOrder, LittleEndian}; // or NativeEndian
let bytes = [0x00u8, 0x00, 0x80, 0x7F];
let number = LittleEndian::read_f32(&bytes);
println!("{}", number);
好的,实际上有一个非常特殊的边缘情况,将位转换为浮点数会导致信号 NaN,这在某些 CPU 架构和配置中会触发低级异常。有关详细信息,请参阅rust#39271 中的讨论。目前已知,具体化信号 NaN 并不是未定义的行为,如果启用浮点异常(默认情况下不是这样),这不太可能成为问题。
Rust 库团队已经实施的决定是转换为浮点数是安全的,即使没有任何掩码。 f32::from_bits 的文档中很好地描述了推理:
目前在所有平台上都与
transmute::<u32, f32>(v)相同。事实证明这是非常便携的,原因有两个:
- 浮点数和整数在所有支持的平台上都具有相同的字节顺序。
- IEEE-754 非常精确地指定了浮点数的位布局。
但是有一个警告:在 2008 版 IEEE-754 之前,实际上并未指定如何解释 NaN 信号位。大多数平台(特别是 x86 和 ARM)选择了最终在 2008 年标准化的解释,但有些没有(特别是 MIPS)。因此,MIPS 上的所有信令 NaN 在 x86 上都是安静的 NaN,反之亦然。
与其尝试保留跨平台的信令特性,此实现更倾向于保留确切的位。这意味着任何以 NaN 编码的有效载荷都将被保留,即使此方法的结果是通过网络从 x86 机器发送到 MIPS 机器。
如果此方法的结果仅由产生它们的同一架构操作,则不存在可移植性问题。
如果输入不是 NaN,则不存在可移植性问题。
如果您不关心信令(很可能),那么就没有可移植性问题。
一些解析/编码库可能仍在将各种 NaN 转换为绝对安静的 NaN,因为这在 Rust 的历史上曾有一段时间是不确定的。
【讨论】: