Coding curiosity 3: When is a number not a number?

The following code snippet shows two ways of accepting an amount and returning the same amount plus sales tax (think VAT if you're in Europe). On first examination, both methods do exactly the same thing, and you might be hard-pressed to find a good reason for selecting one way over the other.

C# supports 2 floating-point types: float (Single) and double (Double). The issue that you always need to bear in mind when dealing with a floating-point variable is that it can contain strange creatures such as PositiveInfinity, NegativeInfinity, and Not-a-Number (abbreviated as NaN). Under the IEEE 754 arithmetic rules, any of these non-finite floating-point values can be generated by certain operations. For example, an invalid floating-point operation such as dividing zero by zero results in NaN.

Now try passing a variable containing Single.NaN into the two methods shown above. The first method AddSalesTaxOne returns NaN, while the second method AddSalesTaxTwo throws the exception that you would expect from a casual reading of the code.

The reason for this behaviour is rather subtle - comparing any number to NaN always returns false. So in both of the methods shown above, NaN is neither less than, equal to, or greater than zero. Even comparing NaN with NaN returns false. If you do any SQL programming, using Null (which means unknown in SQL terms) results in similar behaviour.

The effect in the first method is that the comparison with zero returns false and therefore the exception is not thrown. The effect in the second method is that the comparison with zero returns false and therefore the exception is thrown. The second method is what developers would normally expect and code for, but you might want to use the first method if you regard NaN as a legitimate value in some specific context.

If you don't expect or want numbers containing NaN, you should use an assertion to validate floating-point parameters passed by your own code, or some defensive programming to validate floating-point parameters passed by code that you don't trust.