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;
These next three are extra special and confusing:
d1 = 0.1;
d2 = 0.2;
if(d1 + d2 == 0.3) cout << "okay 8" << endl; else cout << "surprise 8" << endl;
float f1 = 0.1;
float f2 = 0.2;
if(f1 + f2 == 0.3) cout << "okay 9" << endl; else cout << "surprise 9" << endl;
if(f1 + f2 == 0.3f) cout << "okay 10" << endl; else cout << "surprise 10" << endl;
On most machines, two of them print "surprise
" and one of them prints "okay
". There is a whole question on Stack Overflow analyzing these surprises.
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.
So, 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.
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 10-13 should print "okay
", while 7-9 and 14 are the "surprises".