3

I'm trying to find some Java code to determine if two doubles are nearly equal. I did a lot of Googling and found bits and pieces that I've put together here. Where it starts to escape me is the use of a "relative epsilon". This approach seems like what I'm looking for. I don't want to have to specify the epsilon directly but want to use an epsilon based on the magnitudes of the two arguments. Here is the code I put together, I need a sanity check on it. (P.S. I know just enough math to be dangerous.)

public class MathUtils
{
    // http://stackoverflow.com/questions/3728246/what-should-be-the-
    // epsilon-value-when-performing-double-value-equal-comparison
    // ULP = Unit in Last Place
    public static double relativeEpsilon( double a, double b )
    {
        return Math.max( Math.ulp( a ), Math.ulp( b ) );
    }

    public static boolean nearlyEqual( double a, double b )
    {
        return nearlyEqual( a, b, relativeEpsilon( a, b ) );
    }

    // http://floating-point-gui.de/errors/comparison/
    public static boolean nearlyEqual( double a, double b, double epsilon )
    {
        final double absA = Math.abs( a );
        final double absB = Math.abs( b );
        final double diff = Math.abs( a - b );

        if( a == b )
        {
            // shortcut, handles infinities
            return true;
        }
        else if( a == 0 || b == 0 || absA + absB < Double.MIN_NORMAL )
        {
            // a or b is zero or both are extremely close to it
            // relative error is less meaningful here
            // NOT SURE HOW RELATIVE EPSILON WORKS IN THIS CASE
            return diff < ( epsilon * Double.MIN_NORMAL );
        }
        else
        {
            // use relative error
            return diff / Math.min( ( absA + absB ), Double.MAX_VALUE ) < epsilon;
        }
    }
}
Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
careysb
  • 49
  • 1
  • 2
  • 7
  • 1
    One ulp is probably a bit too small of an epsilon. – Louis Wasserman Apr 07 '17 at 22:53
  • Any recommendations on how I should bump it up? – careysb Apr 07 '17 at 23:07
  • Ah, @LouisWasserman, looks like your stuff that Klaus mentioned. – careysb Apr 07 '17 at 23:18
  • Mine among many others. But I'd use at least a significant constant multiple of an ulp? Though frankly, there's _not_ a universal rule or everyone would already be using it, and most people actively prefer explicitly specifying an epsilon. But generally: errors are going to be significantly larger than one ulp, and one ulp is going to be significantly bigger than `Double.MIN_NORMAL`. – Louis Wasserman Apr 07 '17 at 23:19
  • Thanks to all. I didn't realize what a can of worms I was getting into. Looks like I'll have to tailor the epsilon to each specific use. – careysb Apr 09 '17 at 01:20

3 Answers3

10

I would use a library for this, the one I normally use is DoubleMath fro Googles Guava library. https://google.github.io/guava/releases/19.0/api/docs/com/google/common/math/DoubleMath.html

if (DoubleMath.fuzzyEquals(a, b, epsilon)) { // a and b are equal within the tolerance given } there is also a fuzzyCompare.

Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30
  • The library's fuzzyEqals() requires a tolerance parameter which is what I'm trying to avoid by using ulp (which apparently I haven't done correctly). – careysb Apr 07 '17 at 23:05
  • 3
    @careysb The reason libraries ask for a tolerance is because the correct tolerance does not just depend on the ulp. It depends on the expected range of rounding error taking into account the calculation so far. – Patricia Shanahan Apr 07 '17 at 23:34
  • Exactly, and if any of the floats/doubles were created using a calculation you will need a tolerance since precision could be lost. For instance `(2f/11 * 9) * 11;` which is 18.000002, compared to `3f*6` which is 18.0, you will need a tolerance when comparing. – Klaus Groenbaek Apr 08 '17 at 10:04
0

The usual way to compare 2 floating values a,b is:

if ( Math.abs(a-b) <= epsilon ) do_stuff_if_equal;
 else                       do_stuff_if_different;

where Math.abs() is absolute value. As I do not code in JAVA you need to use double variant if that is not the case. The epsilon is your difference. As mentioned ulp is too small for this. You need to use value that makes sense for the values you are comparing. So how to compute epsilon?

That is a bit tricky and yes it is possible to use magnitude of a,b but that is not robust way because if exponents of a,b are too different you can obtain false positives easily. Instead you should use a meaning-full value. For example if you are comparing position coordinates then the epsilon should be fraction of min detail or minimal distance you consider as the same point. For angles some minimal angle that is small enough like 1e-6 deg but the value depends on ranges and accuracy you work with. For normalized <-1,1> ranges I usually use 1e-10 or 1e-30.

As you can see the epsilon depends mostly on target accuracy and magnitude and change very from case to case so creating some uniform way (to get rid of epsilon like you want) is not safe and only would lead to head aches later on.

To ease up this I usually define a _zero constant or variable (in case of computational classes) that can be changed. Set it on default to value that is good enough for most cases and if cause problems at some point I know I can easily change it ...

If you want to do it your way anyway (ignoring above text) then you can do this:

if (Math.abs(a)>=Math.abs(b)) epsilon=1e-30*Math.abs(b);
 else                         epsilon=1e-30*Math.abs(a);

but as I said this may lead to wrong results. If you persist on using ulp then I would use Min instead of Max.

Spektre
  • 49,595
  • 11
  • 110
  • 380
0

You can use the class org.apache.commons.math3.util.Precision from the Apache Commons Math. Example:

if (Precision.equals(sum, price, 0.009)) {
    // arguments are equal or within the range of allowed error (inclusive)
}
Paul Vargas
  • 41,222
  • 15
  • 102
  • 148