18

So far I've seen many posts dealing with equality of floating point numbers. The standard answer to a question like "how should we decide if x and y are equal?" is

abs(x - y) < epsilon

where epsilon is a fixed, small constant. This is because the "operands" x and y are often the results of some computation where a rounding error is involved, hence the standard equality operator == is not what we mean, and what we should really ask is whether x and y are close, not equal.

Now, I feel that if x is "almost equal" to y, then also x*10^20 should be "almost equal" to y*10^20, in the sense that the relative error should be the same (but "relative" to what?). But with these big numbers, the above test would fail, i.e. that solution does not "scale".

How would you deal with this issue? Should we rescale the numbers or rescale epsilon? How? (Or is my intuition wrong?)

Here is a related question, but I don't like its accepted answer, for the reinterpret_cast thing seems a bit tricky to me, I don't understand what's going on. Please try to provide a simple test.

Community
  • 1
  • 1
Federico A. Ramponi
  • 46,145
  • 29
  • 109
  • 133
  • See additional answer at https://stackoverflow.com/questions/4915462/how-should-i-do-floating-point-comparison/66750064#66750064 . – NewSites Mar 25 '21 at 12:04

5 Answers5

20

It all depends on the specific problem domain. Yes, using relative error will be more correct in the general case, but it can be significantly less efficient since it involves an extra floating-point division. If you know the approximate scale of the numbers in your problem, using an absolute error is acceptable.

This page outlines a number of techniques for comparing floats. It also goes over a number of important issues, such as those with subnormals, infinities, and NaNs. It's a great read, I highly recommend reading it all the way through.

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • Thank you. The paper also explains the motivations behind the rude cast to int (although in ordinary code I would opt for understandability and use one of the all-float solutions :) – Federico A. Ramponi Nov 30 '08 at 05:40
  • 2
    Updated version of the link above is [Comparing floating point numbers, 2012 edition](http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/) – sfstewman Mar 27 '13 at 08:04
5

As an alternative solution, why not just round or truncate the numbers and then make a straight comparison? By setting the number of significant digits in advance, you can be certain of the accuracy within that bound.

J.T. Hurley
  • 521
  • 9
  • 12
  • Rounding and truncation work poorly. If we round (to nearest) to three digits then 1.499999999999 and 1.5000000000001 will compare ad different, despite being ridiculously close. If we truncate then 1.999999999999 and 2.00000000000001 will compare as different despite being extremely close. Any rounding or truncation scheme will have cusps like this. Any solution has to start by subtracting the numbers and then deciding whether the different is large enough to be significant. – Bruce Dawson May 27 '14 at 22:27
  • 1
    @BruceDawson 1.4999999999999 and 1.500000000000001 rounded to 3 digits will compare as equal... (both 1.5) – komodosp Feb 24 '15 at 15:55
  • 1
    Huh. I'm not sure why I gave those numbers as examples because you're right, they don't work as I claimed. The actual problem is that you can have two arbitrarily close numbers that round away from each other and therefore compare as different when they should compare as equal. For round-to-nearest: 1.50500000000000001 1.50499999999999999 When rounded to three digits these give us 1.51 and 1.50, despite being separate by less than one part in a billion. Any rounding scheme will hit this problem with numbers near the cusp. That's why round-and-compare is broken. – Bruce Dawson Feb 24 '15 at 16:59
  • 1
    @BruceDawson: Round-and-compare may be useful with some kinds of equivalence classes (e.g. hashset/map/dictionary) if items which are near a threshold are added to the collection more than once. – supercat Feb 27 '15 at 23:03
1

The problem is that with very big numbers, comparing to epsilon will fail.

Perhaps a better (but slower) solution would be to use division, example:

div(max(a, b), min(a, b)) < eps + 1

Now the 'error' will be relative.

leppie
  • 115,091
  • 17
  • 196
  • 297
0

Using relative error is at least not as bad as using absolute errors, but it has subtle problems for values near zero due to rounding issues. A far from perfect, but somewhat robust algorithm combines absolute and relative error approaches:

boolean approxEqual(float a, float b, float absEps, float relEps) {
    // Absolute error check needed when comparing numbers near zero.
    float diff = abs(a - b);
    if (diff <= absEps) {
        return true;
    }

    // Symmetric relative error check without division.
    return (diff <= relEps * max(abs(a), abs(b)));
}

I adapted this code from Bruce Dawson's excellent article Comparing Floating Point Numbers, 2012 Edition, a required read for anyone doing floating-point comparisons -- an amazingly complex topic with many pitfalls.

Franz D.
  • 1,061
  • 10
  • 23
0

Most of the time when code compares values, it is doing so to answer some sort of question. For example:

  1. If I know what a function returned when given a value of X, can I assume it will return the same thing if given Y?

  2. If I have a method of computing a function which is slow but accurate, I am willing to accept some inaccuracy in exchange for speed, and I want to test a candidate function which seems to fit the bill, are the outputs from that function close enough to the known-accurate one to be considered "correct".

To answer the first question, code should ideally do a bit-wise comparison on the value, though unless a language supports the new operators added to IEEE-754 in 2009 that may be less efficient than ideal. To answer the second question, one should define what degree of accuracy is required and test against that.

I don't think there's much merit in a general-purpose method which regards as equal things which are close, since different applications will have differing requirements for both absolute and relative tolerance, based upon what exact questions the tests are supposed to answer.

supercat
  • 77,689
  • 9
  • 166
  • 211