0 == 0 … well, usually it does

So when does 0 not equal 0? When it is -0 and even then it depends on who you ask. According the C# specification, -0 is an acceptable value and is treated the same as positive zero in most situations and they seem to be correct because it is very hard to tell that you have a negative 0 value. Take a look at the following set of comparisons:

double zero = 0.0;

double negZero = -0.0;

 

// following all result in true values

bool areSame = zero.Equals(negZero);

bool areEqual = zero == negZero;

bool sameSign = Math.Sign(zero) == Math.Sign(negZero);

bool sameString = zero.ToString() == negZero.ToString();

When you execute the code above, the results of all the tests is true – that for these tests 0 and -0 are considered the same. However, there are cases where they are not treated the same. For example:

bool sameResult =

    Math.Atan2(zero, -1.0) == Math.Atan2(negZero, -1.0);

This is the scenario that Mike, a friend of mine at work, came across the other day. It was a bit more difficult to detect though because the values were coming from variables that were part of a series of calculations. We looked at the value of the variables in the watch window and executed the same method (Math.Atan2) in the watch window explicitly with those values (instead of the variables) and got a different result.

In this particular case, the issue may actually be a bug in that method. We’ve logged it with Microsoft so you can check the status if you’re interested. The point though is that the CLR does support this and that the values could be treated differently. So how can you tell that you are in this situation? One way is to use the BitConverter.GetBytes. If you check the bytes, you will see that the sign bit for the value is actually set indicating that its negative.

byte[] zeroBytes = BitConverter.GetBytes(zero);

byte[] negZeroBytes = BitConverter.GetBytes(negZero);

 

bool sameBytes = zeroBytes[7] == negZeroBytes[7];

One other interesting point is how you can arrive at this value without explicitly creating a negative zero as I did in the tests above. If you do calculations using doubles, it does not appear that the value will result in a -0. For example:

double one = 1.0d;

double negOne = -one + one;

double posOne = one – one;

 

byte[] negOneDBytes = BitConverter.GetBytes(negOne);

byte[] posOneDBytes = BitConverter.GetBytes(posOne);

 

// these both result in positive 0

bool sameSignBit = negOneDBytes[7] == posOneDBytes[7];

In this case, both values are positive 0. However, if you perform the same test with decimal values:

decimal one = 1.0m;

decimal negOne = -one + one;

decimal posOne = one – one;

 

double dblNegOne = Convert.ToDouble(negOne);

double dblPosOne = Convert.ToDouble(posOne);

 

byte[] negOneDBytes = BitConverter.GetBytes(dblNegOne);

byte[] posOneDBytes = BitConverter.GetBytes(dblPosOne);

 

// these have different sign bits

bool sameSignBit = negOneDBytes[7] == posOneDBytes[7];

The sign bit is set for the value that moved from a negative value towards zero and that sign bit is maintained when the value is converted to a double.

Advertisements

6 Responses to “0 == 0 … well, usually it does”

  1. Karl Shifflett Says:

    Deep Andrew, Deep.

    May need to consider adding another attribute to your handle.

    Sniper / SCUBA Diver

    I need to dig into this in the “other” .NET language and see where my financial applications stand.

    Thank you for the post.

    Cheers,

    Karl

  2. drj11 Says:

    The atan2 behaviour is most certainly not a bug, you need to learn about branch-cuts in trigonometric functions. Points above the x-axis (quadrants I and II) should return positive angles, points below the x-axis (quadrants III and IV) should return negative angles. Consider that the point (x=-1, y=-0) can be considered as being “just below” the x-axis (for example, it could be the result of a computation that approaches the x-axis from below).

    Every sensible language that has both -0.0 and atan2 defines it like this. There’s some discussion of this in the relevant section of the common lisp hyperspec, but it takes a good background in complex number theory to follow. Good luck!

  3. agsmith Says:

    Hi drj11. Thanks for the information on atan2. I wasn’t sure that it was a bug and I do see the usefulness of having a concept of -0 but that wasn’t really the point of the article . The point was more that -0 is supported by .Net but is very difficult to detect. The debugger/vs and the framework do just about everything they can to indicate that there is no difference between 0 and -0.

  4. drj11 Says:

    Well, these zeroes are tricky. Lots of language systems provide a function like copysign to reliably detect these funny cases, but I see C# doesn’t do that.

    Unless C# is completely off the rails, 1/x should be +Inf when x is +0.0 and -Inf when x is -0.0. Therefore if x == 0.0 then 1/x &lt 0 should be true if and only if x is -0.0.

    Python has (had?) a bug in this area that I wrote up in a blog article.

  5. Stephen Russell Says:

    So is -0 the anti null?

  6. Does 0=0 In VB.NET « Karl On WPF - .Net Says:

    […] 0=0 In VB.NET Recently Andrew “Sniper” Smith posted this entry, 0 == 0 … well, usually it does on his blog.  Very good post on the subject as are all his deep blog […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: