Why does divison by 0 always result in negative NaN?

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

0.0 / 0.0 = -NaN

-0.0 / 0.0 = -NaN

0.0 / -0.0 = -NaN

-0.0 / -0.0 = -NaN

Huh? Why division by zero always gives negative NaN (as opposed to positive NaN)? Does anybody know? I imagine it may have something to do with the NaN exception (negative NaN is quiet NaN), but I want to know if somebody else knows for sure.

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.
Advertisement

According to Wikipedia:

For example, a bit-wise IEEE floating-point standard single precision (32-bit) NaN would be: s111 1111 1axx xxxx xxxx xxxx xxxx xxxx where s is the sign (most often ignored in applications), a determines the type of NaN, and x is an extra payload (most often ignored in applications). If a = 1, it is a quiet NaN; if a is zero and the payload is nonzero, then it is a signaling NaN.[3]

So the sign bit does not mean that it's a qNaN, and there is no guarantee that all divisions by 0 will give you a signed NaN. So no matter the reason you're getting signed NaN, you have to (should) ignore it.

No, the sign of a NaN is separate from whether it's quiet or signaling. I believe whether you get NaN or -NaN is implementation defined. x86 hardware happens to set the sign bit for all those 0/0 divisions.

Personally, I think it's annoying and I would have preferred if the only string you get by printing any NaN would be "NaN". The way things are now, some testing scripts have to accept either "NaN" or "-NaN" as output of some operations.

Oi, for some reason I thought the sign bit was used for the signalling toggle ????? It makes even less sense that it returns -NaN though. Maybe it's just setting all the bits?

And yeah, the NaN and -NaN thing is annoying, much like having separate 0 and -0 (let's face it, how often is a signed 0 useful?).

EDIT: checked now, the value returned is 0xFFC00000. OK huh, I'm completely clueless what's the logic behind this now. I know that in practice this doesn't matter at all (a NaN is still a NaN in the end) but still, I'm curious.

EDIT 2: of course I should have remembered that the C and C++ standards would get in the way. What happens when I build normally:


+0 / +0 = -NaN
+0 / -0 = -NaN
-0 / +0 = -NaN
-0 / -0 = -NaN

What happens when I tell the compiler to ignore trying to preserve floating point ordering and such (i.e. -ffast-math in GCC):


+0 / +0 = 1
+0 / -0 = -NaN
-0 / +0 = -NaN
-0 / -0 = 1

For those who wonder, this is the test program (assumes that both int and float are 4 bytes):


#include <stdio.h>
#include <string.h>

int main() {
   float z1, z2;
   float f;
   unsigned i;
   
   z1 = 0.0f;
   z2 = 0.0f;
   z2 = -z2;
   
   memcpy(&i, &z1, 4);
   printf("+0 = %08X\n", i);
   memcpy(&i, &z2, 4);
   printf("-0 = %08X\n", i);
   
   f = z1 / z1;
   memcpy(&i, &f, 4);
   printf("+0 / +0 = %08X\n", i);
   
   f = z1 / z2;
   memcpy(&i, &f, 4);
   printf("+0 / -0 = %08X\n", i);
   
   f = z2 / z1;
   memcpy(&i, &f, 4);
   printf("-0 / +0 = %08X\n", i);
   
   f = z2 / z2;
   memcpy(&i, &f, 4);
   printf("-0 / -0 = %08X\n", i);
   
   return 0;
}

EDIT 3: OK nevermind it was an optimization getting in the way, dividing by itself gets optimized to 1 >_> Though for some reason this happens even with -O0 (which literally should generate the most unoptimized code it could ever attempt to make), so I guess this may be happening in the processor itself. Using separate variables makes it work as intended (gives -NaN in all four cases).

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.
In C++, division by zero is classified as "Undefined behavior"

C++03 5.6.4 - The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined.


This not only means you cannot rely on the result making sense, but the compiler can do whatever it wants with it (under the "division by 0 can't happen, so this branch of code can't execute" logic). This is why "division by itself" results in 1. Because you aren't allowed to divide by 0, and the result of any other number divided by itself is 1.

For more information on Undefined Behavior, I recommend this article and the ones it links to.

If you're not using C++, then you'll have to see what guarantees (if any) your language makes.

This not only means you cannot rely on the result making sense, but the compiler can do whatever it wants with it (under the "division by 0 can't happen, so this branch of code can't execute" logic).
That's true for dividing by the compile-time constant zero, not otherwise. You are of course perfectly right in this example, since division by zero is trivial for the compiler to prove in the above sample code.

However, in general it's not true. At the risk of beating a dead horse by bringing up the same topic again as two weeks ago: No, the compiler, in general, cannot just do whatever it wants. If you divide some float by some other float, the compiler has to, and will, emit code that does just that.

Although it may, of course, add supplementary code to every division which checks whether the denominator is zero and calls an error handler (or similar), but it may not just do just about anything. That includes eliminating branches. Unless it can prove that the denominator will be zero at compile-time already, it may not optimize out code (or... format your hard disk tongue.png ).

Manually checking the denominator before every operation is quite a bit of overhead, so what usually happens is that the C++ compiler simply emits a "divide" assembly instruction and lets the hardware deal with whatever comes around, which either gives a valid result, or generates a trap (for which the compiler usually installs a handler at program start, so an error function and finally abort is called) or just a silent NaN, like here.


In C++, division by zero is classified as "Undefined behavior"

Doubt that applies to floating point... at the least not on platforms that use IEEE that as mentioned in previous posts explicitly states what happens on division by zero.

EDIT: The standard also explicitly states that floating point in itself is implementation-defined and that division by zero etc. can vary by platform. Either way this case is determined by the float specification, not the C++ specification.

This not only means you cannot rely on the result making sense, but the compiler can do whatever it wants with it (under the "division by 0 can't happen, so this branch of code can't execute" logic).

That's true for dividing by the compile-time constant zero, not otherwise. You are of course perfectly right in this example, since division by zero is trivial for the compiler to prove in the above sample code.

However, in general it's not true. At the risk of beating a dead horse by bringing up the same topic again as two weeks ago: No, the compiler, in general, cannot just do whatever it wants. If you divide some float by some other float, the compiler has to, and will, emit code that does just that.
Although it may, of course, add supplementary code to every division which checks whether the denominator is zero and calls an error handler (or similar), but it may not just do just about anything. That includes eliminating branches. Unless it can prove that the denominator will be zero at compile-time already, it may not optimize out code (or... format your hard disk tongue.png ).

Manually checking the denominator before every operation is quite a bit of overhead, so what usually happens is that the C++ compiler simply emits a "divide" assembly instruction and lets the hardware deal with whatever comes around, which either gives a valid result, or generates a trap (for which the compiler usually installs a handler at program start, so an error function and finally abort is called) or just a silent NaN, like here.


Which is kind of what this whole thing was about. The OP asked for why divide by zero worked a certain way, then provided example code that the compiler could very easily determine when a divide by zero took place. Therefore all bets are off.

The compiler can do anything - from your perspective. You have no way to predict what it will do. It's allowed to make assumptions where "x/y" means that y will never be 0, so later on it could eliminate a "y == 0" test. If you want to play around with undefined behavior, that's your prerogative, but let me know what you make so I can avoid it ;)

I suggest you read the second page to the article I posted, written by a Clang developer, that shows exactly how undefined behavior can lead to eliminated test code (in this case, based on a "clearly unneeded" null check)

In C++, division by zero is classified as "Undefined behavior"


Doubt that applies to floating point... at the least not on platforms that use IEEE that as mentioned in previous posts explicitly states what happens on division by zero.

EDIT: The standard also explicitly states that floating point in itself is implementation-defined and that division by zero etc. can vary by platform. Either way this case is determined by the float specification, not the C++ specification.


Was unable to find anything in the standard regarding math and floating point values. Only that the value representation of floating point values is implementation-defined (3.9.1.8 of C++14 working draft - since it's free), and that the result of division by zero is undefined (5.6.4 of same standard - which simply refers to "arithmetic types", not integer or floating point - see modulus where it's specified it must be an integral type).

The standard does not mandate any floating point specification (of which there are more then one, btw).

Will the compiler emit a divide opcode when it has no way to tell what the values are? Of course. But, again, it's allowed to make assumptions about the denominator. (See the OP's example where he got 1, even when dividing by zero)

However, in general it's not true. At the risk of beating a dead horse by bringing up the same topic again as two weeks ago: No, the compiler, in general, cannot just do whatever it wants. If you divide some float by some other float, the compiler has to, and will, emit code that does just that.
Although it may, of course, add supplementary code to every division which checks whether the denominator is zero and calls an error handler (or similar), but it may not just do just about anything. That includes eliminating branches. Unless it can prove that the denominator will be zero at compile-time already, it may not optimize out code (or... format your hard disk tongue.png ).

I'm not sure what you mean.
The following snippet triggers UB:


int result = numerator / denominator;
if( denominator == 0 ) //UB: Denominator couldn't be 0 by now. Branch can be left out.
{
    result = 0;
    doSomething();
}


float result = numerator / denominator;
if( denominator == 0 ) //UB: On architectures that don't follow IEEE, Denominator couldn't be 0 by now. Branch can be left out.
{
    result = 0.0f;
    doSomething();
}
There has been way too many security bugs in real world code caused by optimizing compilers removing important pieces of code due to them being executed after invoking undefined behavior instead of being executed earlier.

I'm sure there are cases where bad things occur, and I certainly don't mean to disagree with those points, but it's often a clearly defined operation, and Infinity and NaN can both be acceptable floating point values. For game development I would consider the assumption of division by zero being OK perfectly reasonable, unless targeting a specific platform where it's known to be a problem.

Under that assumption, '0 / 0' is also an exceptionally exceptional case, whereas 'anything else / 0' is infinity, and identical to 'very large / very small' which is also infinity even though both the numerator and denominator are valid finite values. Division by zero is not the only way to get Infinity or NaN, and several standard math functions often return these values (log / sqrt etc).

When doing sequential operations on floats that may or may not end up as zero, checking for edge-cases between every single operation is not really desirable.. and checking the final result for Inf/NaN is probably better. If aiming for SIMD it's very desirable to not have to worry about it.

This topic is closed to new replies.

Advertisement