How to correctly and standardly compare floats?
Asked Answered
R

8

42

Every time I start a new project and when I need to compare some float or double variables I write the code like this one:

if (fabs(prev.min[i] - cur->min[i]) < 0.000001 &&
    fabs(prev.max[i] - cur->max[i]) < 0.000001) {
        continue;
}

Then I want to get rid of these magic variables 0.000001(and 0.00000000001 for double) and fabs, so I write an inline function and some defines:

#define FLOAT_TOL 0.000001

So I wonder if there is any standard way of doing this? May be some standard header file? It would be also nice to have float and double limits(min and max values)

Romeliaromelle answered 28/12, 2010 at 17:38 Comment(4)
Might want to look at this #17833Preoccupied
Depends on the use case, but what about very small numbers? Your code would compare 1e-10 and 1e-15 and -1e-10 as all equal. There is no single "correct" way to compare floating point numbers for "closeness".Gardening
Why #define? You can just use a static const float for this purpose.Mikkimiko
I bet you forgot to compare with 0? :)Afroamerican
R
9

Thanks for your answers, they helped me a lot. I've read these materials:first and second

The answer is to use my own function for relative comparison:

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

This is the most suitable solution for my needs. However I've wrote some tests and other comparison methods. I hope this will be useful for somebody. areEqualRel passes these tests, others don't.

#include <iostream>
#include <limits>
#include <algorithm>

using std::cout;
using std::max;

bool areEqualAbs(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon);
}

bool areEqual(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b)));
}

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

int main(int argc, char *argv[])
{
    cout << "minimum: " << FLT_MIN      << "\n";
    cout << "maximum: " << FLT_MAX      << "\n";
    cout << "epsilon: " << FLT_EPSILON  << "\n";

    float a = 0.0000001f;
    float b = 0.0000002f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
    a = 1000001.f;
    b = 1000002.f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
}
Romeliaromelle answered 4/1, 2011 at 21:56 Comment(3)
Surely you mean std::max(fabs(a), fabs(b)), unless all your floats are positiveJebel
Thanks TonyK, you're correct, I haven't time to write a complete unit test to see it. I've fixed areEqualRel in my postRomeliaromelle
nice, but probably doesn't work for special floats like subnormal numbers.Mauk
P
24

From The Floating-Point Guide:

This is a bad way to do it because a fixed epsilon chosen because it “looks small” could actually be way too large when the numbers being compared are very small as well. The comparison would return “true” for numbers that are quite different. And when the numbers are very large, the epsilon could end up being smaller than the smallest rounding error, so that the comparison always returns “false”.

The problem with the "magic number" here is not that it's hardcoded but that it's "magic": you didn't really have a reason for choosing 0.000001 over 0.000005 or 0.0000000000001, did you? Note that float can approximately represent the latter and still smaller values - it's just about 7 decimals of precision after the first nonzero digit!

If you're going to use a fixed epsilon, you should really choose it according to the requirements of the particular piece of code where you use it. The alternative is to use a relative error margin (see link at the top for details) or, even better, or compare the floats as integers.

Panocha answered 28/12, 2010 at 19:50 Comment(2)
For what it's worth, Bruce Dawson has mentioned that his article on comparing floating-point numbers is now obsolete, and that readers should refer to the 2012 edition instead.Peti
@Chris Frederick: thanks, I'll add a link to that edition to the websitePanocha
M
17

The Standard provides an epsilon value. It's in <limits> and you can access the value by std::numeric_limits<float>::epsilon and std::numeric_limits<double>::epsilon. There are other values in there, but I didn't check what exactly is.

Mikkimiko answered 28/12, 2010 at 18:16 Comment(1)
Although beware that epsilon is not a straight replacement for the constant tolerance as used by the questioner. It represents out-by-1 in the least significant bit of the value 1.0, so if your values are approximately 2, then it's too small to provide any tolerance. It's quite difficult to use effectively.Conjurer
G
17

You can use std::nextafter for testing two double with the smallest epsilon on a value (or a factor of the smallest epsilon).

bool nearly_equal(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */)
{
  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}
Gentry answered 7/2, 2016 at 11:34 Comment(0)
R
9

Thanks for your answers, they helped me a lot. I've read these materials:first and second

The answer is to use my own function for relative comparison:

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

This is the most suitable solution for my needs. However I've wrote some tests and other comparison methods. I hope this will be useful for somebody. areEqualRel passes these tests, others don't.

#include <iostream>
#include <limits>
#include <algorithm>

using std::cout;
using std::max;

bool areEqualAbs(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon);
}

bool areEqual(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b)));
}

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

int main(int argc, char *argv[])
{
    cout << "minimum: " << FLT_MIN      << "\n";
    cout << "maximum: " << FLT_MAX      << "\n";
    cout << "epsilon: " << FLT_EPSILON  << "\n";

    float a = 0.0000001f;
    float b = 0.0000002f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
    a = 1000001.f;
    b = 1000002.f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
}
Romeliaromelle answered 4/1, 2011 at 21:56 Comment(3)
Surely you mean std::max(fabs(a), fabs(b)), unless all your floats are positiveJebel
Thanks TonyK, you're correct, I haven't time to write a complete unit test to see it. I've fixed areEqualRel in my postRomeliaromelle
nice, but probably doesn't work for special floats like subnormal numbers.Mauk
A
8

Here is a c++11 implementation of @geotavros 's solution. It makes use of the new std::numeric_limits<T>::epsilon() function and the fact that std::fabs() and std::fmax() now have overloads for float, double and long float.

template<typename T>
static bool AreEqual(T f1, T f2) { 
  return (std::fabs(f1 - f2) <= std::numeric_limits<T>::epsilon() * std::fmax(std::fabs(f1), std::fabs(f2)));
}
Auston answered 14/10, 2016 at 9:11 Comment(0)
R
4

You should use the standard define in float.h:

#define DBL_EPSILON     2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */

or the numeric_limits class:

// excerpt
template<>
class numeric_limits<float> : public _Num_float_base
{
public:
    typedef float T;

    // return minimum value
    static T (min)() throw();

    // return smallest effective increment from 1.0
    static T epsilon() throw();

    // return largest rounding error
    static T round_error() throw();

    // return minimum denormalized value
     static T denorm_min() throw();
};

[EDIT: Made it just a little bit more readable.]

But in addition, it depends on what you're after.

Renshaw answered 28/12, 2010 at 18:30 Comment(4)
+1: nice, but a header copy-paste isn't the most helpful IMO.Folks
I just want to show that there are more interesting values in the numeric_limits<float> implementation.Renshaw
Where did you get the comment “smallest float value such that 1.0+DBL_EPSILON != 1.0” from? This is the wrong phrase to define DBL_EPSILON. blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILONButt
I've got the comment from the implementation in Visual Studio 2012. Didn't think about until your post.Renshaw
C
2

You should be aware that if you are comparing two floats for equality, you are intrinsically doing the wrong thing. Adding a slop factor to the comparison is not good enough.

Courteous answered 28/12, 2010 at 17:44 Comment(5)
Agree with @ddyer: OP needs to go and do a course on numerical analysis.Rockoon
How about unit tests? If I'm testing an algorithm and I want to check that the result, with given input values, is close to the expected (float) value?Lishalishe
Good question, with no simple answer. If you're just checking for gross errors in the algorithm, then I suppose a slop factor is a good place to start. Other tests would involve feeding data designed to trigger problems, such as using 2^32-1 as an integer input. More generally, you'd probably plot the differences between your implementation and a reference standard, looking for evidence of divergence.Courteous
This is true only for a subset of use cases. In my current case, float comparisons are being made to debounce values coming from an A2D with an epsilon chosen to reflect that A2D's properties.Concatenation
Furthermore there are plenty of use cases where you aren't even considered with the accuracy of the represented number but instead that it is represented. I'm writing unit tests for a JSON parser.Kennith
D
1

This post has a comprehensive explanation of how to compare floating point numbers: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

Excerpt:

  • If you are comparing against zero, then relative epsilons and ULPs based comparisons are usually meaningless. You’ll need to use an absolute epsilon, whose value might be some small multiple of FLT_EPSILON and the inputs to your calculation. Maybe.
  • If you are comparing against a non-zero number then relative epsilons or ULPs based comparisons are probably what you want. You’ll probably want some small multiple of FLT_EPSILON for your relative epsilon, or some small number of ULPs. An absolute epsilon could be used if you knew exactly what number you were comparing against.
Dinorahdinosaur answered 19/7, 2013 at 5:0 Comment(1)
Your link has gone stale.Football

© 2022 - 2024 — McMap. All rights reserved.