why should I not use the == operator?
because it might not work. But it is not the ==
operator that is a problem. The problem are numbers itself. Some of floating point numbers don't have exact binary representation and floating point math is not exact. For example simple values like 0.2
cannot be precisely represented using binary floating point numbers, and the limited precision of floating point numbers means that slight changes in the order of operations can change the result. Different compilers and CPU architectures store temporary results at different precisions, so results will differ depending on the details of your environment.
If you do a calculation and then compare the results against some expected value it is highly unlikely that you will get exactly the result you intended.
In other words, if you do a calculation and then do this comparison:
if (result == expectedResult)
then it is unlikely that the comparison will be true. If the comparison is true then it is probably unstable – tiny changes in the input values, compiler, or CPU may change the result and make the comparison be false.
Therefore comparing floating point numbers depends on the context. Since even changing the order of operations can produce different results, it is important to know how "equal" you want the numbers to be. This is called epsilon.
There are multiple things to consider:
- What is your tolerance for errors already present in the values being compared?
- H0: For what cases in which the exact values would differ is it
acceptable that the comparison report true?
- H1: For what cases in which the exact values would be equal is it
acceptable that the comparison report false?
The source of confusion and error are the numbers itself that are being compared, not the comparison. In fact ==
operator is reliable - it always returns correct answer taking actual arguments.
Comparing floating point numbers by Bruce Dawson is a good place to start when looking at floating point comparison.
What Every Programmer Should Know About Floating-Point Arithmetic is another very nice article.
Rule of thumb.
The following definitions are from The art of computer programming by Knuth:
bool approximatelyEqual(float a, float b, float epsilon)
{
return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool essentiallyEqual(float a, float b, float epsilon)
{
return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool definitelyGreaterThan(float a, float b, float epsilon)
{
return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool definitelyLessThan(float a, float b, float epsilon)
{
return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
Choosing epsilon depends on the context, and determines how equal you want the numbers to be.
Coercion
it is implicit cast, so happens there when you not specify it explicitly (directly). It is automatic
double f(int i){
return i;
} <- coersion int to double
double d;
long l;
int i;
if (d > i) d = i; // <-coersion
if (i > l) l = i; // <-coersion
if (d == l) d *= 2; // <-coersion
Casting
you use it explicitly, you say
static_cast<>()
dynamic_cast<>()
const_cast<>()
reinterpret_cast<>()
and each of these has different, specialized meaning, i.e dynamic_cast
is appropriate for polymorphic types, and type safe, so you use it to convert Base*
pointer (or Base& reference) to Derived*
(or Derived&) safely - testing if the actual object is exactly what you are expecting it to be. Target of dynamic_cast
doesn't have to be polymorphic - this allows for a special type of transmission (wrap concrete object in a polymorphic type and then unwrap to concrete again later, [see Bjarne Stroustrup C++..., 15.4.1 Dynamic_cast, p. 408]). reinterpret_cast
is used for pointers conversion and pointers don't have to point to polymorphic type (that is class with virtual function), static_cast
doesn't examine type in runtime - so it doesn't introduce a little, little overhead related to dynamic_cast
which has to check type_info
associated with an object being casted.
However only static_cast
might cast from void*
because it doesn't need additional information about memory being pointed to. Also very important is that static_cast
failure results in run time error but dynamic_cast
will return 0
for pointer or throw bad_cast
exception in case of references being casted. const_cast
is self explanatory, and is needed: you cannot cast constness with dynamic_cast
or static_cast
, so it is said that they both respect constness. They both respect access controls also (it is not possible to cast to a private base [because only derived class methods might do Derived* -> Base*
and methods of classes being friends to this {friend declaration is in Base}])