3

With this question as base, it is well known that we should not apply equals comparison operation to decimal variables, due numeric erros (it is not bound to programming language):

bool CompareDoubles1 (double A, double B)
{
   return A == B;
}

The abouve code it is not right. My questions are:

  1. It is right to round to both numbers and then compare?
  2. It is more efficient?

For instance:

bool CompareDoubles1 (double A, double B)
    {
       double a = round(A,4);
       double b = round(B,4)
       return a == b;
    }

It is correct?

EDIT

I'm considering round is a method that take a double (number) and int (precition):

bool round (float number, int precision);

EDIT I consider that a better idea of what I mean with this question will be expressed with this compare method:

bool CompareDoubles1 (double A, double B, int precision)
        {
           //precition could be the error expected when rounding
           double a = round(A,precision);
           double b = round(B,precision)
           return a == b;
        }
Community
  • 1
  • 1
Raúl Otaño
  • 4,640
  • 3
  • 31
  • 65
  • 4
    What is the definition of `round`? – Erbureth Jan 30 '14 at 16:20
  • The correct way to compare floating point numbers is not to compare them at all. – Griwes Jan 30 '14 at 16:20
  • 1
    The first code _is_ right, even if the comparison is rarely true. You _have_ to use the first code if you want bit-wise equality. The point about using a "trust region" or whatever you name it is that in most cases you don't mean bitwise-equality, but "are these two numbers the same up to some precision?" – stefan Jan 30 '14 at 16:20
  • I would say everything you need to know is in the stackoverflow post you are citing. No need to restart the discussion. – Kit Fisto Jan 30 '14 at 16:36
  • If you want numbers that are close to each other to result in a compare returning true, then rounding is not the way, as two numbers that are close might round to two different numbers, but other numbers that are further from each other might round to the same. Eg: both 1.0 and 1.4999 might round to 1, but 1.500001 might round to 2 – Ebbe M. Pedersen Jan 30 '14 at 16:56

6 Answers6

6

Usually, if you really have to compare floating values, you'd specify a tolerance:

bool CompareDoubles1 (double A, double B, double tolerance)
{
   return std::abs(A - B) < tolerance;
}

Choosing an appropriate tolerance will depend on the nature of the values and the calculations that produce them.

Rounding is not appropriate: two very close values, which you'd want to compare equal, might round in different directions and appear unequal. For example, when rounding to the nearest integer, 0.3 and 0.4 would compare equal, but 0.499999 and 0.500001 wouldn't.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 1
    Or you make the tolerance relative. Or you design your algorithm so that the test for equality is appropriate; there are a lot of cases where `==` can be correctly used on floating point. – James Kanze Jan 30 '14 at 18:55
  • 1
    I think `<=` would be better than `<`. Otherwise `tolerance == 0` does not work (to mimick the traditional way of comparison). – Andreas H. Jul 08 '20 at 12:03
4

A common comparison for doubles is implemented as

bool CompareDoubles2 (double A, double B)
{
   return std::abs(A - B) < 1e-6; // small magic constant here
}

It is clearly not as efficient as the check A == B, because it involves more steps, namely subtraction, calling std::abs and finally comparison with a constant.

The same argument about efficiency holds for you proposed solution:

bool CompareDoubles1 (double A, double B)
{
   double a = round(A,4); // the magic constant hides in the 4
   double b = round(B,4); // and here again
   return a == b;
}

Again, this won't be as efficient as direct comparison, but -- again -- it doesn't even try to do the same.

Whether CompareDoubles2 or CompareDoubles1 is faster depends on your machine and the choice of magic constants. Just measure it. You need to make sure to supply matching magic constants, otherwise you are checking for equality with a different trust region which yields different results.

stefan
  • 10,215
  • 4
  • 49
  • 90
  • It doesn't have to be magic. And actually I think it should not be magic but consciously chosen one that represents the precision of the results =) It can be a very bad and hard to track down surprise that your 15 digits precise 0.000001 and 0.000002 *magically* are equal to each other. – luk32 Jan 30 '14 at 16:37
  • @luk32 it's not magic in the sense of arbitrary. it indeed needs to be carefully chosen. However I refer to them as "magic constants" in the sense that they are hardcoded constants for which the purpose isn't directly obvious and should probably replaced by a meaningful name. (c.f. [wikipedia on magic numbers](http://en.wikipedia.org/wiki/Magic_number_%28programming%29)) – stefan Jan 30 '14 at 16:39
  • I know what you mean. I think one should not use such constant in the comparator itself but rather pass it as a parameter, even optional. It's a bad practice that might backfire horribly. I'm just saying, it should not be magic. In your sense of magic =) Of course your right when it comes to the real matter, you *can* do it like that, whether it *should* is another thing. I mean you just did not say, that this magic is not good in fact. I thought it was worth of pointing out clearly. – luk32 Jan 30 '14 at 16:43
  • @luk32 Yep, you're right. In production code, I wouldn't write this either. But there's already a fairly lengthy question/answer page here, namely the one the OP linked to, which should provide enough information. – stefan Jan 30 '14 at 16:47
  • The function needs to handle both `1.00000E+20 == 1.00001E+20` along with `1.00000E-20 == 1.00001E-20`. Hence, make the epsilon value a parameter. – franji1 Jan 30 '14 at 17:24
1

I think comparing the difference with a fixed tolerance is a bad idea.

Say what happens if you set the tolerance to 1e-6, but the two numbers you compare are 1.11e-9 and 1.19e-9?

These would be considered equal, even if they differ after the second significant digit. This may not what you want.

I think a better way to do the comparison is

equal = ( fabs(A - B) <= tol*max(fabs(A), fabs(B)) )

Note, the <= (and not <), because the above must also work for 0==0. If you set tol=1e-14, two numbers will be considered equal when they are equal up to 14 significant digits.

Sidenote: When you want to test if a number is zero, then the above test might not be ideal and then one indeed should use an absolute threshold.

Andreas H.
  • 5,557
  • 23
  • 32
0

If the round function used in your example means to round to 4th decimal digit, this is not correct at all. For example, if A and B are 0.000003 and 0.000004 they would be rounded to 0.0 and would therefore be compared to be equal.

A general purpose compairison function must not work with a constant tolarance but with a relative one. But it is all explained in the post you cite in your question.

Kit Fisto
  • 4,385
  • 5
  • 26
  • 43
  • 1
    I don't see why 0.000003 and 0.000004 should be an example that shows why rounding is a bad idea. the usual |diff| < abs approach would give the same result. A correct counterexample is as provided in Mikes answer: Two numbers that are actually fairly close, but not even close once rounded. – stefan Jan 30 '14 at 16:45
  • Because they differ by 33% and in general should not be compared equal. Andreas H.'s post follows the same argument. – Kit Fisto Jan 31 '14 at 08:17
0

There is no 'correct' way to compare floating point values (Even a f == 0.0 might be correct). Different comparison may be suitable. Have a look at http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

0

Similar to other posts, but introducing scale-invariance: If you are doing something like adding two sets of numbers together and then you want to know if the two set sums are equal, you can take the absolute value of the log-ratio (difference of logarithms) and test to see if this is less than your prescribed tolerance. That way, e.g. if you multiply all your numbers by 10 or 100 in summation calculations, it won't affect the result about whether the answers are equal or not. You should have a separate test to determine if two numbers are equal because they are close enough to 0.

user2566092
  • 4,631
  • 15
  • 20