5 ms·
As a programmer it's easier to insert a bug with floats than with integers, because IEEE 754 has various subtle stumbling blocks. For example, remember to thin
by beza1e1 15y ago
As a programmer it's easier to insert a bug with floats than with integers, because IEEE 754 has various subtle stumbling blocks.
For example, remember to think about NaN and that x != x, if x is NaN. And did you know, that there are actually two kinds of NaN? Or did you know, that 1.0/0.0 == +infinity?
- palish 15y agoYou say "1.0/0.0 == +infinity" like it should be surprising, but it makes perfect intuitive sense to me. You assert that it's "easier to insert a bug with floats", but your first example (NaN) is one which programmers shouldn't be worrying about. The only case you'd want to check for NaN is in debug code (an assert).
- pama 15y agoShouldn't a programmer care about 0.0/0.0 ?
- palish 15y agoYes. //---------------------------------------------- void MVec3::normalize() { // calculate length. float len = sqrtf( x*x + y*y + z*z ); // verify length is not zero. assert( len != 0.0f ); if ( len == 0.0f ) len = 1.0f; // divide by length. len = (1.0f / len); x *= len; y *= len; z *= len; }
- pama 15y agoThanks. However, normalization is a rather simple case and the solution you propose works as expected. I posit that most code performing floating point division doesn't check all corner cases correctly; those that do are codes written by people who worry about NaN and the correct type of Inf.
- Someone 15y agoJust curious (and only marginally, if at all, on topic): - Why is that assert there? - Why aren't that "len = (1.0f / len)" and the "= len" statements in an else clause? (are there CPUs where always doing the division and those 3 multiplications is faster? Will compilers optimize that, anyways? Are there IEEE subtleties that make x= 1.0f not a no-op?) - Wouldn't it, on some platforms, be better to use double for intermediate results?
- barrettcolin 15y agoI've had fairly heated arguments about this in the past and I'm curious to know if anyone else holds the same opinion: I contend that a normalize function is a really bad place to put an assert. Chances are the normalize will be called frequently and the assert check, in debug mode, will have an impact on performance (this goes for assert checks inside other frequently called functions too). The cumulative effect is a debug build that becomes unusably slow relative to the optimized build without the asserts, which rather defeats the purpose of having it in the first place: if you can't use it to find problems, then what's the point? (I'm remembering this being the case more often than not when working on games for the Xbox 360 or PS3).
- palish 15y agoI've been bitten by not having asserts (and, conversely, saved by having them) that I don't hold to the philosophy of "don't use asserts out of performance considerations". assert( len != 0.0f ); If that alone is enough to slow down your program by any significant margin, then you're doing something wrong. That said, I've experienced your standpoint --- A debug mode which was unusably slow. However, that results from programmers who rely too much on the optimizer. STL, for example, can perform very slowly in debug mode in certain circumstances.
- barrettcolin 15y agoIt was recollection on my part; not backed by empirical evidence. For this exact case, though: wouldn't you better handle this with floating point exceptions?
- Deestan 15y ago> You say "1.0/0.0 == +infinity" like it should be surprising, but it makes perfect intuitive sense to me. But it isn't correct. 1/0 is undefined. 1/x approaches infinity as x approaches 0 from the positive side. 1/x approaches negative infinity as x approaches 0 from the negative side. If we were to define 1/0 it has to be both positive and negative infinity at the same time.
- palish 15y agoThe goal for floats isn't to resolve equations. It's to perform arithmetic. (1.0 / 0.0) has to be handled somehow. From a mathematical point of view, handling it as "+infinity" has a 50/50 chance of being "correct". As floating-point math is inherently an approximation, this is a reasonable tradeoff. But from a pragmatic point of view, you don't care about the result of "x * foo" if x is +infinity or -infinity. The result isn't usable.
- andos 15y ago1.0/0.0 has to be handled somehow. Yes, by raising an error [1]. Or whistling innocently and pretending it's not my code while leaving the room. handling it as "+infinity" has a 50/50 chance of being "correct" It has zero chance of being correct because infinity can never be the result of any arithmetic computation. Infinity is not a number. Infinity only appears as the result of evaluating limits, an operation which is not arithmetic. [1] http://en.wikipedia.org/wiki/SIGFPE http://en.wikipedia.org/wiki/SIGFPE
- marshray 15y agohttp://en.wikipedia.org/wiki/IEEE_754-1985 http://en.wikipedia.org/wiki/IEEE_754-1985 The spec allows the programmer to decide if the various degenerate cases in arithmetic will raise a signal, or result in a special value which propagates through the rest of the calcuations (INF, NaN, etc.). Certainly there are times to prefer one over the other, but the facilities that were standardized back in the 80's are generally more than adequate for most uses. Whether or not your platform C compiler lets you access these settings in a convenient and portable way is another question however...:-)
- mturmon 15y agoI agree that the parent comment is overly worried by issues that are well-known if you program with IEEE arithmetic. But, about your second paragraph, I disagree. It turns out NaNs are quite useful to hold un-determined values, unknown values, error values, etc. The NaN will propagate through intermediate operations and "contaminate" the result in the appropriate way. If you use this property of NaNs, you can exploit IEEE conformance to do a lot of hard work for free. This is used in image processing all the time. You have an image; it is projected onto a grid; some values were not sampled and are unknown. Fill them with NaNs. Then run whatever operations you wish on the image (scaling, convolution, morphological operations). The NaNs will propagate through to the result image and indicate unknown values in exactly the correct way, without having to do extra work. For example, a convolution will widen a single NaN to take in the whole footprint of the convolution kernel. A scaling will just pass the NaN through to one pixel in the output. There is no analog of this property of the NaN for integers.