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