When Good Code Goes NaN: How mismanaging Java's Unorderable NaN Value led to a bug

I recently encountered a bug which was caused by a NaN value in my Java code. NaN, short for "Not a Number", is a valid value for a float or a double. NaN is a tricky value because (1) it spreads like a virus and (2) it is unorderable.

NaN is a virus

NaN spreads like a virus because every operations that involves a NaN will result in more NaN.

float f1 = 42f + Float.NaN; // f1 = Float.NaN
float f2 = 0.5 * f1; // f2 = Float.NaN

Once a NaN, always a NaN. So if you are not careful, a NaN will spread throughout your program and may cause unexpected bugs.

The Unorderable Nature of NaN

NaN is unorderable, which means that x > Float.NaN and x < Float.NaN are both false. Imagine you are a cautious programmer who uses defensive programming, you put post-condition in your code to ensure that the value your computing is always within a valid range.

// compute a value

if (value < lowLimit || value > highLimit) {
    throw IllegalArgumentException("Incorrect value " + value + " out of range");
} else {
    return value;
}

You may think you're fine, I certainly did thought that. But you're not! Since NaN is unorderable, NaN < lowLimit is false and so is NaN > highLimit, so the exception is not raised and the NaN is returned. NaN keeps spreading.

The bug

In my application, a NaN value escaped the post-condition check and was then converted to an int using Math.round. And what does Math.round() with NaN? It returns 0!

So when the application displayed the result on screen, it appeared as a 0, which in that particular context made no sense.

Solutions

So what are the solutions? So far I have identified the following:

Check the Javadoc

Check the javadoc of the Math functions you are using to find out when you could get a NaN (for example sqrt, log, pow, sin, …) or what they do with NaN (for example round).

Use Float.isNaN()

Check explicitely for NaN with Float.isNaN() before checking the value.

// compute a value

if (Float.isNaN(value) || value < lowLimit || value > highLimit) {
    throw IllegalArgumentException("Incorrect value " + value + " out of range");
} else {
    return value;
}

Use Float.compare()

Use Float.compare() whose contract says NaN is equal to itself and greater than Float.POSITIVE_INFINITY.

// compute a value

if (Float.compare(value, lowLimit) < 0 || Float.compare(value,highLimit) > 0) {
    throw IllegalArgumentException("Incorrect value " + value + " out of range");
} else {
    return value;
}

A word of cautions on Float.compare(), make sure to check for the high limit. For example, if you only checked that the result was positive you may still let NaN spreads.

// compute a value

if (Float.compare(value, 0f) < 0) { // value is > 0 but can be Float.POSITIVE_INFINITY or Float.NaN.
    throw IllegalArgumentException("Incorrect value " + value + " out of range");
} else {
    return value;
}

Conclusion

I wish there was a way to raise an exception whenever a NaN creeps in. Until then, be careful when you are manipulating float, check for NaN with Float.isNaN() or Float.compare().

Here is the spec on Java float complete behaviors: https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3