I am writing Java code that interacts with R, where “NA” values are distinguished from NaN values. NA indicates that a value is “statistically missing”, that is it could not collected or is otherwise not available.
class DoubleVector {
public static final double NA = Double.longBitsToDouble(0x7ff0000000001954L);
public static boolean isNA(double input) {
return Double.doubleToRawLongBits(input) == Double.doubleToRawLongBits(NA);
}
/// ...
}
The following unit test demonstrates the relationship between NaN and NA and runs fine on my windows laptop but “isNA(NA) #2” fails sometimes on my ubuntu workstation.
@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));
}
From debugging, it appears that DoubleVector.NA is changed to the canonical NaN value 7ff8000000000000L, but it’s hard to tell because printing it to stdout gives different values than the debugger.
Also, the test only fails if it runs after a number of other previous tests; if I run this test alone, it always passes.
Is this a JVM bug? A side effect of optimization?
Tests always pass on:
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)
Tests sometimes fail on:
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)
You are treading in very dangerous water here, one of the few areas where the Java VM behaviour is not exactly specified.
According to the JVM spec, there is only “a NaN value” in the
doublerange. No arithmetic operation on doubles could distinguish between two differentNaNvalues.The documentation of
longBitsToDouble()has this note:So assuming that handling a
doublevalue will always keep the specificNaNvalue intact is a dangerous thing.The cleanest solution would be to store your data in
longand convert todoubleafter checking for your special value. This will impose a quite noticeable performance impact, however.You might get away by adding the
strictfpflag at the affected places. This doesn’t in any way guarantee that it will work, but it will (possibly) change how your JVM handles floating point values and might just be the necessary hint that helps. It will still not be portable, however.