4

At first I thought I could rely on the maximum relative difference only, but I was wrong. For example, if a = 0.0, and b = 0.5, their relative difference is 1.0. In this case approxEquals(lhs, rhs, maxRelDiff, maxAbsDiff) relies on the maximum absolute difference to determine if two floating point numbers are equal.

The two question are:

  1. how do I come up with a new maximum relative and absolute difference pair if the default (1e-2, 1e-5) isn't precise enough? How were 1e-2 and 1e-5 chosen as the default values? For example, if I choose 1e-4 as my maximum relative difference, what is the maximum absolute difference?

  2. How do I adjust the maximum relative and absolute difference values to work properly with floats and doubles?

Arlen
  • 6,641
  • 4
  • 29
  • 61

2 Answers2

1

checking the source code gives me this (I cut out the implementations for the ranges)

bool approxEqual(T, U, V)(T lhs, U rhs, V maxRelDiff, V maxAbsDiff = 1e-5)
{   

    if (rhs == 0)
    {
        return fabs(lhs) <= maxAbsDiff;
    }
    static if (is(typeof(lhs.infinity)) && is(typeof(rhs.infinity)))
    {
        if (lhs == lhs.infinity && rhs == rhs.infinity ||
            lhs == -lhs.infinity && rhs == -rhs.infinity) return true;
    }
    return fabs((lhs - rhs) / rhs) <= maxRelDiff
        || maxAbsDiff != 0 && fabs(lhs - rhs) <= maxAbsDiff;
}

this last line is what we'll need to study:

return fabs((lhs - rhs) / rhs) <= maxRelDiff
        || maxAbsDiff != 0 && fabs(lhs - rhs) <= maxAbsDiff;

in other words the function returns true if the numbers are either relatively different by no more than a factor of maxRelDiff OR absolutely different by no more than maxAbsDiff

so using a maxRelDiff of 0.01 (or 1E-2) compares with an accuracy of 2 (decimal) digits

and using maxAbsDiff different from 0 allows numbers close to 0 to be considered equal even though there relative difference is greater than maxRelDiff

edit: basically first decide how accurate the comparison needs to be and choose your maxRelDiff based on that, then decide at what point should a number be equal to 0

with the examples in the comments:

approxEqual(1+1e-10, 1.0, 1e-10, 1e-30) 
approxEqual(1+1e-10, 1.0, 1e-9, 1e-30)

this compares values close to 1 so maxRelDiff trumps here and choosing any maxAbsDiff (lower than maxRelDiff) wont change anything

approxEqual(0, 1e-10, 1e-10, 1e-30) 
approxEqual(0, 1e-9, 1e-9, 1e-30)

this compares values close to 0 to 0 so the RelDiff (fabs((lhs - rhs) / rhs)) will be 1 and maxAbsDiff trumps

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
  • Thanks, but I'm very well aware of the implementation in Phobos. Yes, the function uses either `maxRelDiff` or `maxAbsDiff`, but you're missing the point. The questions aren't about how `approxEqual()` works. Instead, I want to know how to choose a different `maxRelDiff` and `maxAbsDiff` pair than the default one used in Phobos. I would also like to extend the pair to work properly with `floats` and `doubles`. – Arlen Jan 02 '12 at 04:33
  • And just to show why the pair need to be carefully chosen: `approxEqual(1+1e-10, 1.0, 1e-10, 1e-30)` and `approxEqual(1+1e-10, 1.0, 1e-9, 1e-30)` are not affect by a poor choice of values for `maxRelDiff` and `maxAbsDiff`, but `approxEqual(0, 1e-10, 1e-10, 1e-30)` and `approxEqual(0, 1e-9, 1e-9, 1e-30)` are. – Arlen Jan 02 '12 at 04:38
  • dude last paragraphs read them carefully, and I added a bit of explenation – ratchet freak Jan 02 '12 at 13:32
0

Although I can't answer your original question, I personally just use fabs for floating-point comparisons:

return fabs(f1 - f2) < 0.10;
Andrej Mitrović
  • 3,322
  • 3
  • 20
  • 33
  • 1
    You're calculating the `absolute difference` and comparing it against `0.10`. You can't just rely on absolute difference, and your `0.10` isn't small enough to be practical in most situations. – Arlen Jan 02 '12 at 02:36
  • If f1 = 0.01 and f2 = 0.03, you have a a fairly large relative difference, but your expression will say "they're the same". – Jonathan Leffler Jan 02 '12 at 04:02
  • For the purposes of some code this is the behavior I've wanted. – Andrej Mitrović Jan 02 '12 at 16:21