我在阅读Java语言规范中关于浮点NaN值的部分(我很无聊)。一个32位的float
具有以下比特格式:
seee eeee emmm mmmm mmmm mmmm mmmm mmmm
s
代表符号位,e
代表指数位,m
代表尾数位。NaN值被编码为全1的指数位和不全是0的尾数位(否则将为+/-无穷大)。这意味着有很多可能的不同的NaN值(具有不同的s
和m
位值)。
在此,JLS §4.2.3表示:
IEEE 754 允许其单精度和双精度浮点格式中的每个 NaN 值有多个不同的值。虽然每个硬件架构在生成新 NaN 时返回特定的位模式,但程序员也可以创建具有不同位模式的 NaN 来编码例如回顾性诊断信息等内容。
JLS 中的文本似乎暗示了例如 0.0/0.0
的结果具有依赖于硬件的位模式,并且取决于该表达式是否计算为编译时常量,它所依赖的硬件可能是 Java 程序编译所在的硬件或运行程序所在的硬件。如果这是真的,那么所有这些都似乎非常不靠谱。
我运行了以下测试:
System.out.println(Integer.toHexString(Float.floatToRawIntBits(0.0f/0.0f)));
System.out.println(Integer.toHexString(Float.floatToRawIntBits(Float.NaN)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(0.0d/0.0d)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(Double.NaN)));
我机器上的输出是:7fc00000
7fc00000
7ff8000000000000
7ff8000000000000
输出结果符合预期。指数位全部为1。尾数的最高位也为1,对于NaN来说显然表示“静默NaN”,而不是“信号NaN” (https://en.wikipedia.org/wiki/NaN#Floating_point)。符号位和剩余的尾数位都为0。输出还显示,在我的计算机上生成的NaN和Float和Double类中的常量NaN之间没有区别。
我的问题是,在Java中,无论编译器或VM的CPU如何,这种输出是否得到保证,还是完全不可预测? JLS 对此很神秘。
如果对于0.0/0.0
,这种输出是有保证的话,是否有任何算术方法可以产生具有其他(可能依赖于硬件)比特模式的NaN?(我知道intBitsToFloat
/longBitsToDouble
可以编码其他NaN,但我想知道是否可以从正常的算术运算中出现其他值。)
后续观点:我注意到Float.NaN和Double.NaN指定了它们的确切比特模式,但在源代码(Float,Double)中,它们是通过0.0/0.0
生成的。如果该除法的结果确实取决于编译器的硬件,那么规范或实现中可能存在缺陷。