5

I am running this code in Unity (Mono backend with .Net 4.x)

float a = 0.42434249394294f;
float b = 1 - a;
float sum = a + b;
bool compare1 = (a + b) >= 1f;
bool compare2 = sum >= 1f;

In Debugging (with Visual Studio), compare1 is false while compare2 is true.

How is this happening? Why are the last two lines different? I would think that sum == a + b.

Leander
  • 508
  • 6
  • 21
  • They are floats. Read up on the nature of floating point mathematics. Most floats are approximations of the number they are intended to represent. Never check floats or doubles for equality. Here you are checking for `>= 1f` but in reality, you are checking for `== 1f` – Flydog57 Mar 01 '20 at 02:43
  • I have theoritical knowledge on floating point arithmethics. But I would have expected the last to lines to result in the same. I'll clarify the question. – Leander Mar 01 '20 at 02:46
  • `(a+b)` is not *really* a float when it get compared to 1... you can force it to be `float` to get true/true: `((float)(a + b)) >= 1f;`... – Alexei Levenkov Mar 01 '20 at 02:50
  • @Alexei Care to add that as an answer? What is the `a+b` which is being compared if it is not a float? – Leander Mar 01 '20 at 02:55
  • I think the error stems from your first line - you have exceeded the number of digits of precision for a float. So math operations that need the 7th or 8th digit to be correct will not be deterministic - basically they have a very tiny bit of randomness. – dodgy_coder Mar 01 '20 at 02:59
  • 1
    [Q: Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken?r=SearchResults&s=1|2760.3140) – Ken White Mar 01 '20 at 03:02
  • 1
    @dodgy_coder I have found [this](https://stackoverflow.com/a/328644/6879283): "floating point intermediate results often use 80 bit precision in register, but only 64 bit in memory" – Leander Mar 01 '20 at 03:04
  • @Leander right, yes that is the reason – dodgy_coder Mar 01 '20 at 03:06
  • @Leander yes - that is the explanation of my comment - without cast comparison may run using internal representation of (a+b) which may be more or less than 1. – Alexei Levenkov Mar 01 '20 at 03:06
  • @Leander - What's probably the most interesting here, and maybe why the duplicate is not a duplicate, is that the output is different if you have compiler optimisation turned on or not. When it's on I get true and true. When it is off I get false and true. – Enigmativity Mar 01 '20 at 03:15
  • 1
    It would be great if you could link to another duplicate. Even the answer I linked (https://stackoverflow.com/a/328644/6879283) is much clearer than the current duplicate. – Leander Mar 01 '20 at 03:26

2 Answers2

0

You have encountered a very common numerical precision error called a round-off error. When summing floating point values, you need to include an error tolerance. This kind of error is inherent to floating point math operations in all programming languages.

Your code should be changed to something like the below:

const float errorTolerance = 0.000001f;
float target1 = a + b;
float target2 = sum;
bool compare1 = Math.Abs(target1 - 1f) <= errorTolerance;
bool compare2 = Math.Abs(target2 - 1f) <= errorTolerance;

Note also with a float in c#, it is a single precision floating point number and has only 6-7 significant digits of accuracy.

dodgy_coder
  • 12,407
  • 10
  • 54
  • 67
0

From this answer:

2.) Floating point intermediate results often use 80 bit precision in register, but only 64 bit in memory.

I believe, that sum = a + b generates an instruction to store the result in memory, as a float with a maximum of 64 bits.

Due to a compiler optimization, the machine code of (a + b) >= 1f doesn't seem to cast to the limited float type and apparently uses a higher bit depth, where it can be observed that the numbers don't add up to 1.

We can force memory storage by casting (float)(a+b).

From Enigmativity's comment:

[...] the output is different if you have compiler optimisation turned on or not. When it's on I get true and true. When it is off I get false and true.

Leander
  • 508
  • 6
  • 21