5

When comparing two "real" numbers for equality, why should I not use the == operator, and what should I use instead?

What is the difference between coersion and casting? It is my general assumption that casting is when you force a value to be of another type like so:

int n = 9;
return double(n)/5;
Yuushi
  • 25,132
  • 7
  • 63
  • 81
user2105982
  • 187
  • 2
  • 6
  • 19
  • 4
    There is no rule that says you shouldn't use `==`. Occasionally you do actually want to check the number is exactly equal to some value. Most times, however, you want to check that it is equal within some tolerance level. – paddy May 13 '13 at 01:57
  • 1
    Relevant: http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison/3423299#3423299 – chris May 13 '13 at 02:03
  • 5
    Your questions read as being from someone that wants to understand what he is doing. That's the core attitude that makes for a good computer scientist (or any scientist). – Charles Burns May 13 '13 at 02:28
  • 5
    This should be two separate questions. – Michael Burr May 13 '13 at 02:41
  • 1
    "When comparing two "real" numbers for equality, why should I not use the == operator" Well, why do _you_ think you shouldn't use the equality operator when comparing for _equality_? – Daniel Daranas May 13 '13 at 12:39

5 Answers5

4

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}])

user207421
  • 305,947
  • 44
  • 307
  • 483
4pie0
  • 29,204
  • 9
  • 82
  • 118
  • 2
    Comparison of floating point numbers (almost) always has exactly the obvious meaning. The source of confusion and error is the numbers that are being compared, not the comparison. – Stephen Canon May 13 '13 at 04:29
4

To answer the first question directly: “[Why] should I not use the == operator”? The answer is because earlier operations have produced rounding errors, and it is, in general, impossible to compute a correct result of a function applied to incorrect data. If you have computed values x and y that model exact mathematical values x and y, but x and y have been affected by rounding errors, then there is no function of x and y that tells us whether x equals y.

This means it is impossible to compute whether x equals y in these circumstances. It is not the == operator that is a problem. (== is in fact one of the few floating-point operations that is always computed with no error at all; it always returns the exactly correct result given its inputs.) The problem is that there is no function that gives a correct answer from this incorrect input. (And this is not just a problem with ==. If you have an x with rounding errors, then almost any function computed with it will contain errors: sqrt(1-x*x) will have errors, and acos(x) will have errors. Worse, they might signal exceptions because 1-x*x might be incorrectly negative or x might be incorrectly greater than one.)

Then the question becomes “What do we do instead?”

“Fudging” whether a comparison reports true or false introduces new errors to a program. So the question is, what errors are acceptable in your application? If the comparison reports that two numbers are equal when they would be unequal with exact mathematics, is that acceptable or unacceptable? If the comparison reports that two numbers are unequal when they would be equal, is that acceptable or unacceptable?

The answers to these questions differ from program to program. Generally, there are multiple things to consider:

  • How much error may already be present in the values being compared?
  • For what cases in which the exact values would differ is it acceptable that the comparison report true?
  • For what cases in which the exact values would be equal is it acceptable that the comparison report false?

The answers to the above question depend on each application, so there is no general answer about what to use instead of ==. Some applications may be able to use a relative tolerance in comparison, some may be able to use an absolute tolerance, some may need something else. And some applications might not find any comparison acceptable given the errors in the values. In such cases, they need to redesign the calculations to reduce errors, or find other solutions.

So:

  • Beware of any advice to compare with a tolerance, either relative or absolute. Whether using a tolerance is acceptable depends on your application; there is no general rule. How much tolerance is acceptable depends on your application; there is no general rule.
  • Whether there is any acceptable solution depends on the errors in your computed values. The errors may be too large to allow a solution. Beware of any general recommendations about magnitude of tolerance. Floating-point errors can vary from zero to infinity and are dependent on circumstances, so there is no general tolerance that works.
  • Be aware that all functions computed from data with errors result in errors, not just ==.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
2

This article Comparing floating point numbers goes into floating point comparison in depth and this What Every Programmer Should Know About Floating-Point Arithmetic is a good read too.

With respect to the difference between coercion and casting this SO thread What is the difference between casting and coercing? while although not specific to C++ cover the question well. Basically coercion is implicit while casting is explicit.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
-1

because floating point types are not 'precise'. some values are not even possible to store and errors may accumulate during operations. so you need to decide, what precision you need. do you care if value 12.345678 is different from 12.34567799999? pick decired precision and use it for comparision.

Sergi0
  • 1,084
  • 14
  • 28
-1

Not an answer, just some background on why (won't fit in a comment):

Floats and Doubles are stored internally as Binary. Just like 1's to the left of the binary point are 1,2,4,8,16,... numbers to the right of the binary point are worth 1/2, 1/4, 1/8, 1/16, ... Decimal 1.5 is binary 1.1, decimal 1.25 is binary 1.01.

But numbers like 1.1 in decimal are actually irrational numbers in binary--so you just can't come up with any binary number that converts back to 1.1 in decimal. This means that any tiny change in how the number was calculated will give a slightly different result.

If you want exact answers, you can use BigDecimal--it doesn't use binary to store the number and will give you exact answers every time, so you can use .equals() with confidence.

Bill K
  • 62,186
  • 18
  • 105
  • 157