-1

I needed a custom float comparison for my project and I ended up using a method provided here: https://stackoverflow.com/a/3877206/393406
Though, knowing that float.GetHashCode() returns the bits as would the unsafe de-referencing variation of code, I skipped both the bit-conversion and de-referencing and am utilizing the GetHashCode() instead.

After finding out about the problem which I am experiencing, I tested if BitConverter would provide different values, but that's not the case.

The problem appears in two different places and coincidentally due to comparing the same values, respectively: 1f and -4f. The values made me curious and I went on to debug the case when the call to Math.Abs(a - b) hits the OverflowException (a - b being equal to Int32.MinValue).

I was quite surprised, when I found out, that the absolute hash codes (bit values) for 1f and -4f are the same, which ultimately, messes up the math.

I went on to debug a little more (also taking in mind some of the flaws provided here), providing different values (float -> GetHashCode()) and witnessing interesting results:

0f             -> 0
-0f            -> -2147483648
float.MinValue -> -8388609
float.MaxValue -> 2139095039
1f             -> 1065353216
-1f            -> -1082130432
2f             -> 1073741824
-2f            -> -1073741824
4f             -> 1082130432
-4f            -> -1065353216

The interesting parts being... given that 2f and -2f have the same, absolute, bit values:

  1. why there are differences for 1f and 4f pairs?
  2. why the 1f and 4f pairs are somewhat inversely related?

I bet that my reasoning is totally illogical, but I'm just curious.

Well, and yes, the relation between 1f and -4f is killing my comparison, because:

int aBits = a.GetHashCode(); // -4 => -1065353216
if (aBits < 0)
    aBits = Int32.MinValue - aBits; // -1082130432

int bBits = b.GetHashCode(); // 1 => 1065353216
if (bBits < 0)
    bBits = Int32.MinValue - bBits; // no changes, 1065353216

int bitDifference = aBits - bBits; // -1082130432 - 1065353216 = -2147483648 = Int32.MinValue
int absoluteBitDifference = Math.Abs(bitDifference); // an obvious OverflowException
  1. How to prevent this problem?
Community
  • 1
  • 1
tomsseisums
  • 13,168
  • 19
  • 83
  • 145
  • It's hard to answer part 3 because it's hard to see what the problem is, because you don't explain what your "custom float comparison" is meant to do. – Jon Hanna Feb 09 '16 at 02:50

2 Answers2

2

For the benefit of those who haven't followed the links, the article which the reply you followed links to (Comparing floating point numbers) starts with this stern warning (highlights in bold are mine):

This article is obsolete. Its replacement - which will fix some errors and better explain the relevant issues - is being crafted as a multi-part series here. Please update your links.

Ultimately this article will go away, once the series of articles is complete.

I mean it. Some of the problems with this code include aliasing problems, integer overflow, and an attempt to extend the ULPs based technique further than really makes sense. The series of articles listed above covers the whole topic, but the key article that demonstrates good techniques for floating-point comparisons can be found here. This article also includes a cool demonstration, using sin(double(pi)), of why the ULPs technique and other relative error techniques breaks down around zero.

In short, stop reading. Click this link.

Your questions 1. and 2. are answered if you read up on IEEE floating point and look at the bit patterns of the floats in question (easier if you print them in binary or hex, rather than decimal).

Question 3. "How to prevent this problem?" can only be answered with "stop fiddling with floating-point bit representations, or become an expert in IEEE-754 formats".

Most likely, there are other, safe and standard ways to do whatever it is you try to accomplish, but that's only speculation since you didn't mention what's the ultimate point of the exercise.

Community
  • 1
  • 1
dxiv
  • 16,984
  • 2
  • 27
  • 49
1

The best answer to Question 3 has already been given. You either need to treat floating point numbers as black boxes, and not even try to look at their bit patterns, or you need to read about what the bit patterns mean. It would be very difficult to reconstruct from just looking at their decimal values.

The hexadecimal values of the bit patterns for the pairs in questions 1 and 2 are:

1f: 3f800000
-1f: bf800000
4f: 40800000
-4f: c0800000

The inverse behavior arises because of a basic difference in the handling of negative numbers. IEEE binary floating point is a sign-and-magnitude system, so -1f is just 1f with the most significant bit changed from 0 to 1. Typical integer representations use 2's complement, so to get the negation of a number you subtract it from 0x100000000. A very large magnitude negative float has a representation that looks like a very small magnitude negative integer.

The bit patterns for 1f and -4f are each other's two's complement, and similarly for -1f and 4f.

Community
  • 1
  • 1
Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75