Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does using epsilon in comparison of floating-point break strict-weak-ordering?

Does following class breaks strict-weak-ordering (in comparison to regular std::less (So ignoring edge case values such as Nan))

struct LessWithEpsilon
{
    static constexpr double epsilon = some_value;
    bool operator() (double lhs, double rhs) const
    {
        return lhs + epsilon < rhs;
    }
};

LessWithEpsilon lessEps{};
like image 528
Jarod42 Avatar asked Jun 24 '21 10:06

Jarod42


People also ask

Why fixed Epsilons instead of floats?

The exponential nature of floats means that many more values are gathered there than anywhere else on the number line. Despite being a fairly small value in the context of many calculations, 0.1 is over one billion ULPs away from zero! Consequently, fixed epsilons are probably the best choice when you expect the results to be small. What particular

How do you find the Epsilon of a float?

float a = 0.15 + 0.15 float b = 0.1 + 0.2 if (a == b) // can be false! if (a >= b) // can also be false! The solution is to check not whether the numbers are exactly the same, but whether their difference is very small. The error margin that the difference is compared to is often called epsilon. The most simple form:

How to compare non-zero values between two Epsilons?

When comparing non-zero values, some ULPs-based comparison is probably the best choice. When values could be anywhere on the number line, some hybrid of the two is needed. Choose epsilons carefully based on expected outputs.

Is the relative error smaller than the Epsilon?

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”. Therefore, it is necessary to see whether the relative error is smaller than epsilon:


2 Answers

From https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings

  1. Transitivity of incomparability: For all x,y,z in S, if x is incomparable with y (meaning that neither x < y nor y < x is true) and if y is incomparable with z, then x is incomparable with z.

Similarly, from https://en.cppreference.com/w/cpp/named_req/Compare

If equiv(a, b) == true and equiv(b, c) == true, then equiv(a, c) == true

With {x, y, z} = {0, epsilon, 2 * epsilon}, that rule is broken:

  • !lessEps(x, y) && !lessEps(y, x) && !lessEps(y, z) && !lessEps(z, y) but lessEps(x, z).
  • equiv(x, y) == true and equiv(y, z) == true but equiv(x, z) == false (as x + epsilon < z)

So, that class breaks strict-weak-ordering.

like image 54
Jarod42 Avatar answered Oct 16 '22 13:10

Jarod42


It is true that LessWithEpsilon does not impose a strict weak order for the domain of all doubles, as explained in Jarod42's answer.

However, there can be cases where the input has a limited domain of values for which LessWithEpsilon can impose a strict weak order. In particular, if the input consists of set of disjoint ranges where values of each range are equal to each other (within epsilon) and unequal to all other ranges (distance between ranges greater than epsilon).

In case you're wondering whether it is reasonable to consider limited input domains, consider that std::less also doesn't impose a strict weak order for domain of all doubles - NaN must be excluded.


As for what may have been the intention when writing the comparison function, I suggest a possible alternative: Transform the inputs such that each value is rounded to nearest multiple of epslon. This would technically make the input valid for the suggested comparison function, but it also makes it unnecessary because we would get same result using std::less.

like image 4
eerorika Avatar answered Oct 16 '22 12:10

eerorika