Is D:s floating point handling too advanced?
The floating point support in the D Programming Language is more advanced than that of standard C and C++. I’m not sure I like every aspect of it though. The thing I’m having problem with is how D handles the special value NAN (Not A Value).
For instance, a simple comparison will always return false if one or both of it’s operands are uninitialized (having the value of NAN). This produces the following unintuitive behavior:
if (real.nan != real.nan) { writefln("It's strange, but you'll always see this text!"); }
I can see the reason why they chose the always-fail-if-NAN semantics for the other comparison operators, but not for equal-to and not-equal-to. In this case I think they went too far in their strive for consistency.
The consequence of the D floating point semantics is this: If you want to assert that a float value is initialized, for example in an invariant block, this won’t work:
class SomeClass { real someFieldVar; invariant { assert(someFieldVar != real.nan) {...} } }
Neither will this:
assert(someFieldVar);
As a side note, if the value of someFieldVar is zero, the above expression would evaluate to false! This is why I never use non-boolean expressions where true or false are expected. I think that should be prohibited by the language.
Anyway, you have two options when checking for an uninitialized value. Neither is – in my humble opinion – particularly beautiful.
First, you could use one of the new NAN-aware binary operators that D introduces, like !<>=, !<, !>=, or !<>. These new operators just give me a headache, they don’t come naturally and I can’t seem to learn them. Here’s the best way I found to check for the uninitialized value using the new floating point operators:
assert(0 !<>= value);
That operator returns true if one or both operands are uninitialized. I don’t feel comfortable with that solution. It’s not obvious what the code does. In that sense, the only remaining alternative is better: using the std.math.isnan function.
import std.math; : assert(isnan(someFieldVar));
This is okay, but it feels a little clumsy and backwards using a function. It would have been much better if Walter Bright had used the more intuitive semantics for the normal equality operator. So that I didn’t have to depend upon the std.math namespace, and could write:
assert(someFieldVar != real.nan);
or even better, added a property to the floating point value:
assert(someFieldVar.isnan);
The thing to keep in mind here is that this is how floating point is supposed to work. NaN is explicitly supposed to compare as false to everything else including itself using standard operators.
If you were to use C or C++, they should have the exact same behaviour! The floating point comparisons are done by the CPU, not by the programming language itself, so it’s a little unfair to blame D for this.
The way I look for NaN is to use the `x != x` idiom; saves me having to remember those weird extra operators. If you want to assert that a real has been initialised, use `assert(x==x);` which is true unless x is NaN.
IEEE 754 is a complex but necessary part of programming with real numbers. It might be worth reading up on it so you know about all the various corner-cases (such as `-0` being different from `0-0`.)
Thanks Daniel, obviously I haven’t had much experience with floating point arithmetics (despite nearly two decades in the business). I can see the logic that works behind this, although to me it still feels unintuitive. I will not fight it anymore though, I shall go with the flow :-).
About the ‘someFieldVar.isnan’ property, you’re asking, something like that may work in the future, as Walter’s have said in the future D improvements that a.f(…) will be equivalent to f(a ….)
So I would expect ‘someFieldVar.isnan()’ to work when this is implemented.