Saturday, 24 November 2012

Java Cafe 1 : Never write NaN == NaN (they're not equal)

I see this Java bug time and time again:

NaN == NaN

So what's wrong with this?

The Float and Double classes in java.lang defines a constant holding a Not-a-Number (NaN) value of type float and double respectively. NaN can be used to represent a mathematically undefined number, such as that obtained by dividing zero by zero, or an unrepresentable value, such as the square root of a negative number, which is imaginary so cannot be represented as a real floating-point number. For instance:

System.out.println(0.0f / 0.0f);
System.out.println(Math.sqrt(-1.0f));

prints out:

NaN
NaN

Sometimes programmers initialize a class field to NaN to indicate that it has not been assigned a value. Later on in the program, they check if that field has been assigned a value by checking if it is equal to NaN, using the == operator, e.g.:

public class NaNTest {
    private float value = Float.NaN;

    public void setValue(float newValue) {
        if (value == Float.NaN) // wrong, never do this!
            value = newValue;
    }

    public float getValue() { return value; }
}

Unfortunately, value will never be set to newValue in the setValue() method, because (Float.NaN == Float.NaN) always returns false. In fact, if you look at the JDK implementation of Float.isNaN(), a number is not-a-number if it is not equal to itself (which makes sense because a number should be equal to itself). The same holds for Double.NaN.

This error is easy to make, because the == operator is what you will normally use to compare numbers and primitive types. This bug can go unnoticed for a long time, potentially giving disastrous consequences. For instance, if the code that uses the value returned by getValue() performs the same faulty equality check, and then performs some critical operations:

NaNTest test = new NaNTest();
test.setValue(4.0f);           // does not set it 4.0f
float value = test.getValue(); // returns Float.NaN
float result = 0.0f;
if (value != Float.NaN) {
    result = value;
}
System.out.println(result);    // prints NaN

Although not immediately obvious, the printed value will always be NaN, not 4.0. This is because value has the value Float.NaN, and (Float.NaN != Float.NaN) is always true!

The correct way to check if a number is NaN is to use Float.isNaN() and Double.isNaN(). For example, continuing with the NaNTest class:

public void setValue(float newValue) {
    if (Float.isNaN(value))
        value = newValue;

}

Equivalently, this will also work:

public void setValue(float newValue) {
    // works but don't do this
    if (value != value)    // yes, this check is weird!
        value = newValue;
}

but you should use Float.isNaN() and Double.isNaN() because they make clear the intention of the check, and they will work regardless of any changes to the underlying floating-point implementation of Java.

Finally, the same applies to checking for positive and negative infinity: always use Float.isInfinite() and Double.isInfinite().

No comments:

Post a Comment