Comparing double values for equality in Java.
Asked Answered
A

3

13

I would like some advice from people who have more experience working with primitive double equality in Java. Using d1 == d2 for two doubles d1 and d2 is not sufficient due to possible rounding errors.

My questions are:

  1. Is Java's Double.compare(d1,d2) == 0 handling rounding errors to some degree? As explained in the 1.7 documentation it returns value 0 if d1 is numerically equal to d2. Is anyone certain what exactly they mean by numerically equal?

  2. Using relative error calculation against some delta value, is there a generic (not application specific) value of delta you would recommend? Please see example below.

Below is a generic function for checking equality considering relative error. What value of delta would you recommend to capture the majority of rounding errors from simple operations +,-,/,* operations?

public static boolean isEqual(double d1, double d2) {
    return d1 == d2 || isRelativelyEqual(d1,d2);
}

private static boolean isRelativelyEqual(double d1, double d2) {
    return delta > Math.abs(d1- d2) / Math.max(Math.abs(d1), Math.abs(d2));
}
Amesace answered 6/8, 2014 at 12:26 Comment(4)
possible duplicate of How to resolve a Java Rounding Double issueNoenoel
possible duplicate of Why is Java's Double.compare(double, double) implemented the way it is?Parasympathetic
Your isRelativelyEqual is the right approach (aside from NaN issues) for an all-out attack on the problem. You'd pick the delta based on the number of bits of precision in your float format, effectively 53 bits or 16 decimal digits for double-precision, and how "close" you wanted to be. Alternatively, you could use doubleToLongBits on the numbers, "round up" the fraction to eliminate 1-3 fraction bits, then compare for equal.Vitrics
possible duplicate of What should be the epsilon value when performing double value equal comparisonChicky
G
7

You could experiment with delta values in the order of 10-15 but you will notice that some calculations give a larger rounding error. Furthermore, the more operations you make the larger will be the accumulated rounding error.

One particularly bad case is if you subtract two almost equal numbers, for example 1.0000000001 - 1.0 and compare the result to 0.0000000001

So there is little hope to find a generic method that would be applicable in all situations. You always have to calculate the accuracy you can expect in a certain application and then consider results equal if they are closer than this accuracy.

For example the output of

public class Main {

    public static double delta(double d1, double d2) {
        return Math.abs(d1- d2) / Math.max(Math.abs(d1), Math.abs(d2));
    }

    public static void main(String[] args) {
        System.out.println(delta(0.1*0.1, 0.01));
        System.out.println(delta(1.0000000001 - 1.0, 0.0000000001));
    }

}

is

1.7347234759768068E-16
8.274036411668976E-8

Interval arithmetic can be used to keep track of the accumulated rounding errors. However in practise the error intervals come out too pessimistic, because sometimes rounding errors also cancel each other.

Gnotobiotics answered 6/8, 2014 at 12:31 Comment(2)
What if the expected value is 0.0?Unspotted
@Unspotted Nothing special, as I said above: "there is little hope to find a generic method that would be applicable in all situations".Gnotobiotics
L
2

From the javadoc for compareTo

  • Double.NaN is considered by this method to be equal to itself and greater than all other double values (including Double.POSITIVE_INFINITY).
  • 0.0d is considered by this method to be greater than -0.0d.

You may find this article very helpful

If you want you can check like

double epsilon = 0.0000001;
if      ( d <= ( 0 - epsilon ) ) { .. }
else if ( d >= ( 0 + epsilon ) ) { .. }
else { /* d "equals" zero */ }
Libidinous answered 6/8, 2014 at 12:31 Comment(0)
V
2

You could try something like this (not tested):

public static int sortaClose(double d1, double d2, int bits) {
    long bitMask = 0xFFFFFFFFFFFFFFFFL << bits;
    long thisBits = Double.doubleToLongBits(d1) & bitMask;
    long anotherBits = Double.doubleToLongBits(d2) & bitMask;

    if (thisBits < anotherBits) return -1;
    if (thisBits > anotherBits) return 1;
    return 0;                        
}

"bits" would typically be from 1 to 4 or so, depending on how precise you wanted the cutoff.

A refinement would be to add 1 to the position of the first bit to be zeroed before masking (for "rounding"), but then you have to worry about ripple all the way up past the most significant bit.

Vitrics answered 6/8, 2014 at 15:46 Comment(1)
Thanks, this is the approach I was looking for. However, this doesn't quite work "across 0" -- positive +ε and negative -ε aren't seen as being anywhere close. Here's an alternate version that passes the tests I've made (but I wouldn't be shocked if it has some glitch somewhere, too): return d1==d2 /* hotpath; also handles infinities and NaNs. */ || (Math.abs(d1-d2) < Math.max( Math.ulp(d1), Math.ulp(d2) ) * (0b1L << bits));Underlayer

© 2022 - 2024 — McMap. All rights reserved.