2

We know that floating point values cannot be compared with the == operator, due to precision issues. However, the following code, which initializes a double variable to an integer 0, successfully compares its value to zero:

#include <iostream> using namespace std; int main() { double d = 0; // d == 0 // d = 2.0f - 2.0f; // d == 0 // d = 2.0f - 2.0f + 1.0e-320; // d != 0 cout << "d = " << d << endl; if(d == 0) cout << "d == 0" << endl; else cout << "d != 0" << endl; if(d == 0.0f) cout << "d == 0.0f" << endl; else cout << "d != 0.0f" << endl; } 

Is this a compiler dependent behaviour, or is it what the Standard specifies?

17
  • 15
    You can compare floating-point numbers for exact equality, and there's a large class of values for which it works exactly as you'd expect. The value 0.0 (as well as many integers and simple fractions) can be represented exactly. What's true in general is that you often don't want to compare floating-point values for exact equality; a slightly strongly statement is that you usually shouldn't. But to say that "floating point values cannot be compared with the == operator" is false. Commented Jun 16 at 11:40
  • 2
    "Is this a compiler dependent behaviour, or is it what the Standard specifies?" What is this? The comparison operation is defined. The floating-point representation is implementation defined, but usually IEEE 754. d = 2.0f - 2.0f + 1.0e-320; the result depends on the order of evaluation (or grouping). d = 2.0f - (2.0f + 1.0e-320); is different to d = (2.0f - 2.0f) + 1.0e-320; Commented Jun 16 at 11:44
  • 4
    What Every Computer Scientist Should Know About Floating-Point Arithmetic Commented Jun 16 at 12:04
  • 3
    One thing that strikes me as little odd in your posted code is double d = 0; ... if(d == 0.0f) why are you comparing a double to a float? Everything else aside, you should generally strive to use the same types in comparisons - not doing so may introduce inaccuracies that wouldn't otherwise happen. I'd say that if(d == 0.0) or if(d == 0.) would be more natural (not that it really matters here since zero can be accurately represented by both double and float (as well as int and other types)) but in some cases it matters and its just good habit to always use the same types. Commented Jun 16 at 12:18
  • 3
    @SteveSummit "i++ + i++" - That's not "borderline", that's just straight up undefined behaviour. Commented Jun 16 at 12:27

1 Answer 1

6

We know that floating point values cannot be compared with the == operator, due to precision issues.

That's an overstatement.

Performing floating-point manipulations and then comparing results using the == operator is not guaranteed to work as you expect.
But that does not mean that it is guaranteed not to work.
Sometimes, it works just fine.

These tests all print "okay":

double d = 0; if(d == 0) cout << "okay 1" << endl; else cout << "surprise 1" << endl; d = 100; if(d == 100) cout << "okay 2" << endl; else cout << "surprise 2" << endl; d = 12.25; if(d == 12.25) cout << "okay 3" << endl; else cout << "surprise 3" << endl; d = 24; d /= 32; if(d == 0.75) cout << "okay 4" << endl; else cout << "surprise 4" << endl; 

They all work because all the numbers involved are exactly representable.

These next two tests both print "okay":

d = 1; d /= 3; d *= 3; if(d == 1.0) cout << "okay 5" << endl; else cout << "surprise 5" << endl; d = 1. / 3; double d1 = d * 3; double d2 = d + d + d; if(d1 == d2) cout << "okay 6" << endl; else cout << "surprise 6" << endl; 

They work because although the number 1/3 is not exactly representable, the results round off correctly in the end.

This one prints "surprise", because it doesn't quite round correctly:

d = 1. / 11; d1 = d * 11; d2 = d + d + d + d + d + d + d + d + d + d + d; if(d1 == d2) cout << "okay 7" << endl; else cout << "surprise 7" << endl; 

This one prints "surprise", because there isn't enough precision in type double to express the exact result:

d1 = 10000000000000000.0; d2 = d1 + 0.01; if(d1 != d2) cout << "okay 8" << endl; else cout << "surprise 8" << endl; 

This one prints "surprise", because a little bit of precision gets lost along the way:

d = 10000000000000000.0; d1 = d + 1.25; d1 += 1.25; d2 = d1 - 2.5; if(d == d2) cout << "okay 9" << endl; else cout << "surprise 9" << endl; 

These next three are extra special and confusing:

d1 = 0.1; d2 = 0.2; if(d1 + d2 == 0.3) cout << "okay 10" << endl; else cout << "surprise 10" << endl; float f1 = 0.1; float f2 = 0.2; if(f1 + f2 == 0.3) cout << "okay 11" << endl; else cout << "surprise 11" << endl; if(f1 + f2 == 0.3f) cout << "okay 12" << endl; else cout << "surprise 12" << endl; 

On most machines, two of them print "surprise" and one of them prints "okay". There is a whole question on Stack Overflow analyzing the behavior of 0.1 + 0.2 == 0.3.


What does this all mean? The bottom line is that the "precision issues" which are inherent in computer floating-point arithmetic are not always as bad as they may seem. Yes, it's true, having finite precision means that many numbers cannot be represented exactly. Yes, precision loss can accumulate through compounded calculations. Correct rounding often helps, but it can't always.

Remember, too, that the problem really isn't the comparison itself. The comparison x == y reliably tells you whether two values x and y are exactly equal or not. The real problem is where x and y came from. If one or both are the result of some series of calculations, during which some roundoff error might have crept in, then depending on the nature of the error, x and y might compare unequal even though you thought they should be equal, or vice versa.

And although we can distinguish between numbers that are "exactly representable" in a particular format versus those that aren't, it's not correct or meaningful to say that the == operator "works" on the exactly-representable ones but not otherwise. An exact-equality test can fail surprisingly even for exactly-representable numbers, and it can work as expected even for inexactly-representable numbers. (See examples 9 and 5 above, respectively.)

At any rate, if you're careful and you know what you're doing, under the right circumstances using == to check the value of a floating-point quantity can be perfectly meaningful and well-defined. But, under other circumstances, it can also be a supremely bad idea.

So the rule is not, "You can't compare floating-point values with the == operator." A better rule is, "You usually don't want to compare floating-point values with the == operator." And the reason is not just that it might not work — a better reason is that it really might not be what you want. In the real world, you usually don't want to compare real numbers for exact equality, either, because there are lots of ways that inaccuracies and imprecisions can creep in.

See also How dangerous is it to compare floating point values?.


Finally, you asked about Standards compliance. The C and C++ Standards don't try to define floating-point performance in every detail; that's too big a job, and the C/C++ definition is basically, "however the machine's underlying floating-point instructions work." But with that said, the C and C++ Standards do reference the IEEE 754 floating-point standard, and that standard does do the hard work of specifying floating-point behavior as precisely as it can. And under IEEE 754 rules, the code fragments I've presented here do all have well-defined behavior: 1-6 and 12 should print "okay", while it turns out that 7-11 are guaranteed to yield surprises.

Sign up to request clarification or add additional context in comments.

7 Comments

"We know that floating point values cannot be compared with the == operator, due to precision issues. That's an overstatement. Comparing floating-point values with the == operator is not guaranteed to work." - False. Comparing floating point values with == works just fine. It does exactly what == promises; it compares the two values and checks if they are exactly equal. The fact that not all values can be represented exactly and that there may be rounding errors etc in calculations does not mean that == does not work. It works just fine, but your expectations may be wrong.
@Pietro While it's true that you can compare against the 0.0 value, it's also true that sometimes things you expect to end up as 0.0, don't. Which goes back to the point that you need to really understand what you're doing before you make such a comparison. e.g. if it's because a function result vanishes at 0.0 but is exactly defined everywhere else, then by all means compare against 0.0; but if you're checking to see if the result of a long series of operations is 0.0, maybe you want to think about what it would mean if the result were, say, 1.0e-15 instead.
Relevant/related, mch's answer that lists out Knuth's approximatelyEqual essentiallyEqual definitelyGreaterThan and definitelyLessThan functions.
@Pietro: There is no “subset of numbers which can be compared exactly.” The == operator always evaluates as true if its two operands are equal numbers and false otherwise. If you compare x to 0, and x is 0, then the result is true. Otherwise, it is false. If you compare x to 1, and x is 1, then the result is true. If you compare x to 2.25, and x is 2.25, then the result is true. There is no mysticism, fluctuation, or approximation in the comparison…
… What there is is, if you have an x you have calculated using approximations, and you compare it to another number, your x may differ from the result you would have liked to get, if you had calculated x using exact arithmetic. This is entirely a consequence of the prior operations you did on x and has nothing to do with the == operator.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.