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