Why does divison by 0 always result in negative NaN?

Started by
45 comments, last by Sik_the_hedgehog 9 years, 7 months ago

I certainly don't disagree on your logic based on the C++ standard. Perhaps I'm arguing a completely different point, my apologies if I've derailed this...

What I still feel is that, since floating point arithmetic in itself is implementation-defined, any implementation that defines division by zero must also override that statement for unknown floating point values. A compiler that uses that statement to make assumptions that contradict its floating point implementation does not make sense.

It would have to specifically generate valid working code for division by zero, and in addition to that also treat the later conditional as if the previous valid working code was actually not valid. It's just not possible. I honestly think common complex programs would stop working if such assumptions were added to C++ compilers.

Advertisement

Wasn't my explanation straight to the point and simple enough that you didn't have to go into this long discussion about standards and whatnot? :)

Btw, in that assembly example, it's not the FPU producing the negative qNaN. It's the SSE SIMD processor. If you disable SSE, the FPU might give you a positive qNaN.

I think SSE produces negative qNaNs because it's faster for it to just set all bits to 1 (although I haven't checked if this is the case) than to fiddle with individual bits to produce a positive qNaN.

My point is that it was the processor doing it, not the compiler =P

And yeah, setting all bits was my first assumption, but the resulting value is 0xFFC00000 and not 0xFFFFFFFF, so using both ones and zeroes probably wasn't the issue. All the 1s are grouped at the top of the dword though, so maybe there's indeed something along those lines.

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.

The standard also says (5-4):

If during the evaluation of an expression, the result is not mathematically defined or not in the range of
representable values for its type, the behavior is undefined. [ Note: most existing implementations of C ++
ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all
floating point exceptions vary among machines, and is usually adjustable by a library function
. — end note ]

That note certainly leaves room to interpret that floating-point division by 0 might be an OK thing to do, depending on the compiler. If IEEE 754 specifies the behavior of some operation and my compiler follows that standard, I expect using that operation is A-OK.

I certainly don't disagree on your logic based on the C++ standard. Perhaps I'm arguing a completely different point, my apologies if I've derailed this...
What I still feel is that, since floating point arithmetic in itself is implementation-defined, any implementation that defines division by zero must also override that statement for unknown floating point values. A compiler that uses that statement to make assumptions that contradict its floating point implementation does not make sense.

It would have to specifically generate valid working code for division by zero, and in addition to that also treat the later conditional as if the previous valid working code was actually not valid. It's just not possible. I honestly think common complex programs would stop working if such assumptions were added to C++ compilers.


You aren't writing IEEE code.

You're writing C++ code.

The compiler implements C++ code.

Therefore the compiler follows the C++ standards, which says it can do whatever it wants to divide by zero, even if the machine you're building for follows a IEEE standard that defines division by zero.

Edit: If division by zero was implementation defined in the C++ standard, then you'd have a point. Implementation defined behavior can be whatever the compiler wants, but the compiler has to document it and state exactly what happens (like following the IEEE floating point standard). But it's not - it's undefined behavior, so the compiler neither has to document the results, nor be consistent. This is usually because it opens up additional optimization opportunities.

You aren't writing IEEE code.

You're writing C++ code.

The compiler implements C++ code.

Therefore the compiler follows the C++ standards, which says it can do whatever it wants to divide by zero, even if the machine you're building for follows a IEEE standard that defines division by zero.

Not sure if I can agree entirely with any of those points. When considering this type of floating point math it's just not that simple.. there's not really such a thing. Just because the standard allows for something to be undefined, it's not necessarily actually undefined in implementations.

There is a defined flag in numerical_limits for example that tells us if we have IEEE.. so say that I add assert(IEEE) to the top of my program.. is it then well-defined according to the C++ standard?

I really think that the division by zero being undefined is a technicality for floating point.. if we have a certain floating point implementation it should most certainly override it.

Also, the C++ standard is incredibly incomplete in itself when it comes to floating point.. but it also specifically states that it's not meant to be read by itself.

The following referenced documents are indispensable for the application of this document.

ISO/IEC 9899:1999, Programming languages – C

Now that C-standard actually devotes chapters to floating point math, and makes much stricter guarantees than I expected. I'm not sure how much of it is optional, and in either case much of it goes out the window when accepting compiler-specific shortcuts and unsafe optimizations such as -ffast-math.. as it makes very strict guarantees for a certain floating point model, even specifying rounding-modes.

I would consider the very existence of standard floating point code dubious, if we don't accept one specific floating-point definition (such as IEEE)

The C standard floating point appendix even defines functions for setting exception-handling as well as a constant for the specific division by zero exception..

Not sure if I can agree entirely with any of those points. When considering this type of floating point math it's just not that simple.. there's not really such a thing. Just because the standard allows for something to be undefined, it's not necessarily actually undefined in implementations.
There is a defined flag in numerical_limits for example that tells us if we have IEEE.. so say that I add assert(IEEE) to the top of my program.. is it then well-defined according to the C++ standard?

I really think that the division by zero being undefined is a technicality for floating point.. if we have a certain floating point implementation it should most certainly override it.

Also, the C++ standard is incredibly incomplete in itself when it comes to floating point.. but it also specifically states that it's not meant to be read by itself.


I think you're still missing the point. The C++ standard says "divide by 0 is undefined". If your compiler happens to assume IEEE floating point and implements the code such that divide by 0 in a floating point context is "safe" and produces the IEEE mandated response and doesn't assume that later code that checks the devisor against 0 as trivially removable... that's perfectly acceptable "undefined behavior".

Having the compiler always put a jump-if-non-zero command before any division to assign 0 to your result if the divisor is 0 is also acceptable "undefined behavior".

Having the compiler insert exception throwing if devisor is zero is also acceptable "undefined behavior".

Having the compiler assume that the devisor cannot be 0 and remove later checks against 0 is also perfectly acceptable "undefined behavior" as well. And that compiler will probably produce better optimized code.

If you're writing a compiler, you're probably going to pick the last option. Because then you don't have to do any fancy checks or slow the user's code down with "if the devisor is 0 then assign a special signal value to the result - or throw an exception" code and can just emit a single divide instruction (which is what you would have to do if the standard mandated IEEE floating point, which it doesn't). And it also lets you make assumptions that cleans up and optimizes code that it inlined/templated five levels deep before it even got to code generation.

I think you're still missing the point. The C++ standard says "divide by 0 is undefined". If your compiler happens to assume IEEE floating point and implements the code such that divide by 0 in a floating point context is "safe" and produces the IEEE mandated response and doesn't assume that later code that checks the devisor against 0 as trivially removable... that's perfectly acceptable "undefined behavior".

Having the compiler always put a jump-if-non-zero command before any division to assign 0 to your result if the divisor is 0 is also acceptable "undefined behavior".

Having the compiler insert exception throwing if devisor is zero is also acceptable "undefined behavior".

Having the compiler assume that the devisor cannot be 0 and remove later checks against 0 is also perfectly acceptable "undefined behavior" as well. And that compiler will probably produce better optimized code.

If you're writing a compiler, you're probably going to pick the last option. Because then you don't have to do any fancy checks or slow the user's code down with "if the devisor is 0 then assign a special signal value to the result - or throw an exception" code and can just emit a single divide instruction (which is what you would have to do if the standard mandated IEEE floating point, which it doesn't). And it also lets you make assumptions that cleans up and optimizes code that it inlined/templated five levels deep before it even got to code generation.

I believe this is incorrect, for reasons outlined in my post above among others.

EDIT: Though I accept the possibility that an implementation could derive from the standard and do what you suggest without being logically inconsistent...

Whether that possibility is a reality I can't know unless I read through the entire thing, assuming I'll understand it good enough to make a correct assessment.. which I doubt I will have time to try in the coming months, so I guess I'll cease and desist smile.png

EDIT2: There is one important part of my last message that you seem to have missed though... which is the standardized way of determining that we have a specific floating point implementation (a case that has been my assumption all along, apologies if that didn't come through).

So, given an assert() that guarantees such a floating point model, do you still consider it reasonable for a compiler to do what you suggest?

http://en.cppreference.com/w/cpp/types/numeric_limits/is_iec559

The C++ standard does not mandate that the IEEE standard should be used for floating point.

Case in point, the PS2 did not follow it and anything divided by 0 returned 0, even 0 / 0 (which was a nice property for normalizing vectors and quaternions without having to check for null length).

Perhaps it's unfortunate that the C++ std says "undefined behavior", instead of "implementation defined". But that's how it is.

If it were implementation defined, I would rest assured MSVC, GCC & Clang would compile my code fine in an x86 machine, because it follows the IEEE. But unfortunately, it's UB, not ID.

In real world though, I would be very mad if the compiler optimizes my UB code away without any warning because the amount of UB scenarios the C++ standard can have are gigantic, and mistakes like this happen every time.

The ever lasting struggle of compiler writers who want to take advantage of the UB for optimization and are very picky/elitist about following the Std; vs the average programmer who wants to develop a program that isn't broken by technicalities like this.

EDIT2: There is one important part of my last message that you seem to have missed though... which is the standardized way of determining that we have a specific floating point implementation (a case that has been my assumption all along, apologies if that didn't come through).
So, given an assert() that guarantees such a floating point model, do you still consider it reasonable for a compiler to do what you suggest?
http://en.cppreference.com/w/cpp/types/numeric_limits/is_iec559


We're mostly going in circles at this point - but all that your assert is doing is checking a (potentially runtime) value. Just because the standard library might return true for that doesn't mean the compiler is forced to use it.

As has been repeatedly pointed out, IEEE floating point standard has nothing to do with the C++ standard. That the standard library has various functions to check for IEEE floating point support does not change this.

In real world though, I would be very mad if the compiler optimizes my UB code away without any warning because the amount of UB scenarios the C++ standard can have are gigantic, and mistakes like this happen every time.
The ever lasting struggle of compiler writers who want to take advantage of the UB for optimization and are very picky/elitist about following the Std; vs the average programmer who wants to develop a program that isn't broken by technicalities like this.


Unfortunately warning on UB is pretty much impossible. The last section of this article page illustrates why, as well as ways people are trying to solve the issue.

I mean, just look at our sample case of divide by zero. Most of the time you're dividing by a variable which may or may not be 0. That variable may be aliased by other pointers that could change the value to 0 between when you check for 0 and do the division, even in single-threaded apps should you be passing said pointers around. And we're not even getting into how inlining and templates take advantage of UB to provide some guarantees and optimizations that you otherwise wouldn't have available.

This topic is closed to new replies.

Advertisement