运行期间最终的非规范NaN双值更改

时间:2011-06-16 12:41:32

标签: java jvm-crash

我正在编写与R交互的Java代码,其中“NA”值与NaN值区分开来。 NA表示值“统计上缺失”,即无法收集或无法获得。

class DoubleVector {
     public static final double NA = Double.longBitsToDouble(0x7ff0000000001954L);

     public static boolean isNA(double input) {
         return Double.doubleToRawLongBits(input) == Double.doubleToRawLongBits(NA);
     }

     /// ... 
}

以下单元测试演示了NaN和NA之间的关系,并在我的Windows笔记本电脑上正常运行,但“isNA(NA)#2”在我的ubuntu工作站上有时

@Test
public void test() {

    assertFalse("isNA(NaN) #1", DoubleVector.isNA(DoubleVector.NaN));
    assertTrue("isNaN(NaN)", Double.isNaN(DoubleVector.NaN));
    assertTrue("isNaN(NA)", Double.isNaN(DoubleVector.NA));
    assertTrue("isNA(NA) #2", DoubleVector.isNA(DoubleVector.NA));
    assertFalse("isNA(NaN)", DoubleVector.isNA(DoubleVector.NaN));
}

从调试开始,看起来DoubleVector.NA被更改为规范的NaN值7ff8000000000000L,但很难分辨,因为将它打印到stdout会给出与调试器不同的值。

此外,如果测试在许多其他先前的测试之后运行,则测试仅失败;如果我单独进行这项测试,它总会过去。

这是一个JVM错误吗?优化的副作用?

测试总是传递:

java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)

测试有时会失败:

java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02, mixed mode)

1 个答案:

答案 0 :(得分:6)

你正在这里非常危险的水域,这是Java VM行为 未确切指定的少数几个领域之一。

根据JVM规范,double范围内只有“NaN值”。对双精度数没有算术运算可以区分两个不同的NaN值。

The documentation of longBitsToDouble()有这样的说明:

  

请注意,此方法可能无法返回与long参数具有完全相同位模式的double NaN。 IEEE 754区分了两种NaN,即安静的NaN和信号NaN。两种NaN之间的差异通常在Java中不可见。对信令NaN的算术运算将它们变成具有不同但通常类似的位模式的安静NaN。然而,在一些处理器上,仅复制信令NaN也执行该转换。特别地,复制信令NaN以将其返回到调用方法可以执行该转换。因此longBitsToDouble可能无法返回带有信令NaN位模式的double。因此,对于某些较长的值,doubleToRawLongBits(longBitsToDouble(start))可能不等于start。此外,哪些特定位模式表示信令NaN是平台相关的;虽然所有NaN位模式(安静或信令)必须在上面确定的NaN范围内。

因此,假设处理double值始终保持特定 NaN值不变是一件危险的事情。

最干净解决方案是将您的数据存储在long中,并在检查您的特殊值后转换为double 。然而,这将对性能产生明显的影响。

可能通过在受影响的地方添加strictfp标记来逃避。这不会以任何方式保证它将起作用,但它(可能)会改变JVM处理浮点值的方式,而可能只是帮助提示的必要提示。但是,它仍然不可移植。