qFuzzyCompare fails with small (near zero) values

Qt’s qFuzzyCompare function is useful for approximate comparison of two float or double values. As we know that arithmetic operations with double values often result with numerical errors, comparing two values directly with operator == would most likely make your algorithm fail. So qFuzzyCompare or similar function is a must-have for most algorithms that need to compare doubles.

So for example, if we write:

bool equal = qFuzzyCompare(1, 1.00000000000000000001);

the function would indeed return true. We would expect the same for this example:

bool equal = qFuzzyCompare(0, 0.00000000000000000001);

But wait, this results in equal = false. How can that be possible? Well, it is caused by implementation of qFuzzyCompare like this:

return (qAbs(p1 - p2) * 1000000000000. <= qMin(qAbs(p1), qAbs(p2)))

Now if any of p1 or p2 is zero, obviously the right side of the expression will be zero too. In this case, only if p1 and p2 are exactly equal the function will detect that and return true, but otherwise, if values are only approximately equal, it will return false. The Qt docs (https://doc.qt.io/qt-5/qtglobal.html#qFuzzyCompare) indeed confirms that and advises: “If one of the values is likely to be 0.0, one solution is to add 1.0 to both values.” Really? But what if we do that and one of the values is -1.0? Again we fail.

To fix that, we could naively implement our own version of fuzzyCompare like this:

return std::abs(p1 - p2) < 1e-12;

But we could be wrong with this implementation. To show this, let’s construct this example:

double p1 = 1e5 * (1 + 1e-13), p2 = 1e5;
bool equal1 = qFuzzyCompare(p1, p2);
bool equal2 = std::abs(p1 - p2) < 1e-12;

and equal1 = true (correct), while equal2 = false (not correct). So obviously a more sophisticated check is needed. But there is a solution, see e.g. this: http://realtimecollisiondetection.net/blog/?p=89

If we decide to use simpler solution from this blog so that absolute tolerance is equal to relative tolerance, and we choose 1e-12 (the same as in Qt), we can implement like this:

return std::abs(p1 - p2) <= 1e-12 * std::max({ 1.0, std::abs(p1), std::abs(p2) });

This would finally succeed for all the examples on this page 🙂