... until the collector arrives ...

This "blog" is really just a scratchpad of mine. There is not much of general interest here. Most of the content is scribbled down "live" as I discover things I want to remember. I rarely go back to correct mistakes in older entries. You have been warned :)

2012-06-05

Non-deterministic Floating Point Arithmetic in Java

I came across a very puzzling example of non-deterministic floating point arithmetic in 64-bit Java (jdk1.6.0_26, Windows 7). Consider the following function:

boolean ok() {
    double x = 0.1;
    return 1.0 - x == 0.9;
}

Under normal circumstances, this function returns true. However, 64-bit Java sometimes returns false. In those circumstances, there is a small round-off error. The hexadecimal representation of 0.1 is:

0x1.999999999999ap-4

and of 0.9 is:

0x1.ccccccccccccdp-1

Normally, evaluating 1.0 - 0.1 yields this nearest representable value of 0.9. Unfortunately, the 64-bit (but not 32-bit) JVM sometimes returns a slightly different value:

0x1.cccccccccccccp-1

Note that the last hexadecimal digit differs by one bit, C instead of D. This floating-point value is further away from 0.9 than the former value. That is not the issue, though. The issue is that the 64-bit JVM seems to flip back and forth between these two values non-deterministically.

I observed this behaviour in the context of a large desktop application. I scattered calls to ok() throughout the codebase, printing out the value each time. What I saw was that the first few hundred calls returned true, indicating that the correct rounding behaviour was occurring. After that, the calls would start returning false. For the most part, it seemed that once the rounding errors started occurring, they would not revert back to the correct behaviour. What makes this puzzling is that I did discover circumstances where calls would once again start rounding properly. Except for this last point, I would have guessed that some kind of JIT problem was responsible for this bug. Now, I have no idea.

I spent hours trying to narrow down exactly where ok() started failing by printing out its result in strategic locations. Ultimately I failed: the transition from proper to improper rounding seemed to occur in framework code that I could not easily instrument (i.e. Eclipse infrastructure). I was unable to create a free-standing unit test that exhibited the behaviour. I may redouble my efforts to find such a test, but until then this one will have to be filed under Mysterious Behaviour.

Incidentally, applying the strictfp modifier to a class that performs this calculation has no effect.

A bit of Googling reveals at least one person who has encountered non-determinism in Java floating-point:

Nondeterministic Floating-Point Conversions in Java

The problem described there is somewhat different, but the root cause may be the same.

Blog Archive