Tuesday, October 27, 2009

NaN








NaN


If no test scores exist, the average for a Performance should be zero. You haven't yet written a test for this scenario. Add the following brief test to PerformanceTest.




public void testAverageForNoScores() {
Performance performance = new Performance();
assertEquals(0.0, performance.average());
}


If you run this, oops! You get a NullPointerException. That can be solved by initializing the integer array of tests defined in Performance to an empty array:



private int[] tests = {};


When rerunning the tests, you get a different exception:



junit.framework.AssertionFailedError: expected:<0.0> but was:<NaN>


NaN is a constant defined on both the java.lang.Float and java.lang.Double classes that means "Not a Number."


If there are no scores, tests.length() returns 0, meaning you are dividing by zero in this line of code:



return total / tests.length;


When it works with integers, Java throws an ArithmeticException to indicate that you are dividing by zero. With floating-point numbers, you get NaN, which is a legitimate floating-point number.


Fix the problem by checking the number of test scores as the very first thing in average.



public double average() {
if (tests.length == 0)
return 0.0;
int total = 0;
for (int score: tests)
total += score;
return (double)total / tests.length;
}


NaN has some interesting characteristics. Any boolean comparisons against NaN always result in false, as these language tests demonstrate:



assertFalse(Double.NaN > 0.0);
assertFalse(Double.NaN < 1.0);
assertFalse(Double.NaN == 1.0);


You might want the average method to return NaN. But how would you write the test, since you cannot compare NaN to any other floating-point number? Java supplies the isNaN static method, defined on both Float and Double, for this purpose:



public void testAverageForNoScores() {
Performance performance = new Performance();
assertTrue(Double.isNaN(performance.average()));
}








    No comments: