Boa*_*ann 55 java floating-point nan ieee-754
我正在阅读Java语言规范中的浮点NaN值(我很无聊).32位float具有此位格式:
seee eeee emmm mmmm mmmm mmmm mmmm mmmm
Run Code Online (Sandbox Code Playgroud)
s是符号位,e是指数位,m是尾数位.NaN值被编码为所有1的指数,并且尾数位不是全0(其将是+/-无穷大).这意味着存在许多不同的可能NaN值(具有不同的s和m位值).
在此,JLS§4.2.3说:
IEEE 754为其单浮点格式和双浮点格式提供了多个不同的NaN值.虽然每个硬件架构在生成新的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)));
Run Code Online (Sandbox Code Playgroud)
我机器上的输出是:
7fc00000
7fc00000
7ff8000000000000
7ff8000000000000
Run Code Online (Sandbox Code Playgroud)
输出显示没有超出预期.指数位都是1.尾数的高位也是1,对于NaN,它显然表示"安静的NaN"而不是"信号NaN"(https://en.wikipedia.org/wiki/NaN# Floating_point).符号位和尾数位的其余部分为0.输出还显示我的机器上生成的NaN与Float和Double类的常量NaN之间没有差异.
我的问题是,无论编译器或虚拟机的CPU是什么,在Java中都能保证输出,还是真的无法预测?JLS对此很神秘.
如果保证输出0.0/0.0,是否有任何算法生成具有其他(可能依赖于硬件?)位模式的NaN?(我知道intBitsToFloat/ longBitsToDouble可以编码其他NaN,但我想知道其他值是否可以从正常算术中发生.)
后续要点:我注意到Float.NaN和Double.NaN指定了它们的确切位模式,但是在源(Float,Double)中它们是由它们生成的0.0/0.0.如果该划分的结果实际上取决于编译器的硬件,那么在规范或实现中似乎存在缺陷.
jmi*_*rez 36
这就是JVM 7规范的§2.3.2所说的:
双值集的元素恰好是可以使用IEEE 754标准中定义的双浮点格式表示的值,除了只有一个NaN值(IEEE 754指定2 53 -2个不同的NaN值).
和§2.8.1:
Java虚拟机没有信令NaN值.
所以从技术上讲,只有一个NaN.但是JLS的§4.2.3也说(在你的引用之后):
在大多数情况下,Java SE平台将给定类型的NaN值视为折叠为单个规范值,因此该规范通常将任意NaN称为规范值.
但是,Java SE平台的1.3版引入了使程序员能够区分NaN值的方法:Float.floatToRawIntBits和Double.doubleToRawLongBits方法.有兴趣的读者可以参考Float和Double类的规范以获取更多信息.
我明确指出你和CandiedOrange提出的建议:它取决于底层处理器,但Java对它们的处理方式完全相同.
但它变得更好:显然,你的NaN值完全有可能被静默地转换为不同的NaN,如下所述Double.longBitsToDouble():
请注意,此方法可能无法返回具有与long参数完全相同的位模式的双NaN.IEEE 754区分了两种NaN,即安静的NaN和信号NaN.两种NaN之间的差异通常在Java中不可见.对信令NaN的算术运算将它们变成具有不同但通常类似的位模式的安静NaN.然而,在一些处理器上,仅复制信令NaN也执行该转换.特别地,复制信令NaN以将其返回到调用方法可以执行该转换.因此longBitsToDouble可能无法返回带有信号NaN位模式的double.因此,对于某些长值,doubleToRawLongBits(longBitsToDouble(start))可能不等于start.此外,哪些特定位模式表示信令NaN是平台相关的; 虽然所有NaN位模式,安静或信令,必须在上面确定的NaN范围内.
作为参考,有硬件相关的NaN的表在这里.综上所述:
- x86:
quiet: Sign=0 Exp=0x7ff Frac=0x80000
signalling: Sign=0 Exp=0x7ff Frac=0x40000
- PA-RISC:
quiet: Sign=0 Exp=0x7ff Frac=0x40000
signalling: Sign=0 Exp=0x7ff Frac=0x80000
- Power:
quiet: Sign=0 Exp=0x7ff Frac=0x80000
signalling: Sign=0 Exp=0x7ff Frac=0x5555555500055555
- Alpha:
quiet: Sign=0 Exp=0 Frac=0xfff8000000000000
signalling: Sign=1 Exp=0x2aa Frac=0x7ff5555500055555
Run Code Online (Sandbox Code Playgroud)
所以,要验证这一点,你真的需要这些处理器中的一个并试一试.此外,欢迎任何有关如何解释Power和Alpha架构的较长值的见解.
can*_*nge 14
我在这里读取JLS的方式是NaN的确切位值取决于是谁/什么造成它,而且由于JVM没有成功,所以不要问它们.您也可以问他们"错误代码4"字符串的含义.
硬件产生不同的位模式,用于表示不同种类的NaN.不幸的是,不同类型的硬件为相同类型的NaN产生不同的位模式.幸运的是,Java可以使用一种标准模式来至少告诉它它是某种NaN.
这就像Java查看"错误代码4"字符串并说"我们不知道'代码4'在您的硬件上意味着什么,但是该字符串中有'错误'这个词,所以我们认为这是一个错误. "
JLS试图让你有机会自己解决这个问题:
"但是,Java SE平台的1.3版引入了一些方法,使程序员能够区分NaN值:Float.floatToRawIntBits和Double.doubleToRawLongBits方法.感兴趣的读者可以参考Float和Double类的规范以获取更多信息."
这对我来说就像一个C++ reinterpret_cast.这是Java让你有机会自己分析NaN,以防你碰巧知道它的信号是如何编码的.如果您想要追踪硬件规格,那么您可以预测哪些不同的事件应该产生哪些NaN位模式,您可以自由地这样做,但是您不在JVM意图给我们的统一性之外.所以期望它可能会从硬件变为硬件.
当测试一个数字是否为NaN时,我们检查它是否等于它自己,因为它是唯一不是的数字.这并不是说比特是不同的.在比较这些位之前,JVM测试了许多位模式,即它是一个NaN.如果它是这些模式中的任何一个,那么即使两个操作数的位真的相同(即使它们不同),它也会报告它不相等.
早在1964年,美国最高法院大法官斯图尔特(Stewart)就着名地说:"当我看到它时,我就知道了".我认为Java与NaN的做法是一样的.Java无法告诉你"信号"NaN可能发出信号的任何信息,因为它不知道该信号是如何被编码的.但它可以查看这些位并告诉它是某种类型的NaN,因为该模式遵循一个标准.
如果您碰巧使用的是使用统一位编码所有NaN的硬件,那么您将永远不会证明Java正在做任何事情来使NaN具有统一的位.再一次,我阅读JLS的方式,他们直截了当地说你自己在这里.
我可以看出为什么这种感觉很脆弱.它很脆弱.这不是Java的错.我想知道,有些有进取心的硬件制造商在那里提出了奇妙的表达信号NaN位模式,但他们未能将其作为标准广泛采用,Java可以依靠它.那就是片状.我们保留所有这些位用于发信号通知我们拥有哪种NaN并且不能使用它们,因为我们不同意它们的含义.让Java在硬件制造之后将NaN's变成统一的价值只会破坏这些信息,损害性能,而唯一的收益就是看起来不那么糟糕.鉴于这种情况,我很高兴他们意识到他们可以欺骗他们解决问题并将NaN定义为不等于任何事情.
Pat*_*han 14
这是一个演示不同NaN位模式的程序:
public class Test {
public static void main(String[] arg) {
double myNaN = Double.longBitsToDouble(0x7ff1234512345678L);
System.out.println(Double.isNaN(myNaN));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(myNaN)));
final double zeroDivNaN = 0.0 / 0.0;
System.out.println(Double.isNaN(zeroDivNaN));
System.out
.println(Long.toHexString(Double.doubleToRawLongBits(zeroDivNaN)));
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
true
7ff1234512345678
true
7ff8000000000000
Run Code Online (Sandbox Code Playgroud)
无论硬件做什么,程序本身都可以创建可能与例如不同的NaN 0.0/0.0,并且可能在程序中具有一些含义.
到目前为止,我可以通过正常算术运算生成的唯一其他NaN值是相同的,但符号发生了变化:
public static void main(String []args) {
Double tentative1 = 0d/0d;
Double tentative2 = Math.sqrt(-1d);
System.out.println(tentative1);
System.out.println(tentative2);
System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative1)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative2)));
System.out.println(tentative1 == tentative2);
System.out.println(tentative1.equals(tentative2));
}
Run Code Online (Sandbox Code Playgroud)
输出:
南
南
7ff8000000000000
fff8000000000000
错误的
真的
| 归档时间: |
|
| 查看次数: |
3178 次 |
| 最近记录: |