Performant way to check java.lang.Double for equal

2019-01-29 08:28发布

问题:

What is the most performant way to check double values for equality.

I understand that

double a = 0.00023d;
double b = 0.00029d;

boolean eq = (a == b);

is slow.

So I'm using

double epsilon = 0.00000001d;
eq = Math.abs(a - b) < epsilon;

The problem is that Infinitest is complaning about tests taking too much time. It's not a big deal (1 sec top), but it made me curious.

Additional info

a is hard coded since it's the expected value, b is computed by

  // fyi: current = int, max = int
  public double getStatus()
  {
    double value = 0.0;
    if (current != 0 && max != 0)
      value = ((double) current) / max;
    return value;
  }

Update

java.lang.Double does it that way

public boolean equals(Object obj) {
return (obj instanceof Double)
       && (doubleToLongBits(((Double)obj).value) ==
          doubleToLongBits(value));
}

so one could assume that is the best practice.

回答1:

JUnit has a method of checking a Double for 'equality' with a given delta:

Assert.assertEquals(0.00023d, 0.00029d, 0.0001d);

See this API documentation.

As noted in the comments, JUnit actually most likely is slower than comparing manually with a given delta. JUnit first does a Double.compare(expected, actual) followed (if not equal) by a Math.abs(expected - actual) <= delta.

Hopefully this answer still is useful for people not aware that JUnit actually provides a way for inexact Double equality testing.



回答2:

Actually, comparing two float/double values for equality is a bad practice in itself, because floating point numbers suffer from rounding errors. Two numbers which would be equal in symbolic maths may be different to the computer, depending on how they are computed.

The best practice is the second option you used: Math.abs(a - b) < epsilon.
I understand it may be slower than you'd like, but it's the right way to do it. Bitwise comparison may result in two numbers being considered as different even though they're the same, from the point of view of the application (they would be equal if you had computed them by hand, but they are bitwise different due to rounding errors).



回答3:

java.lang.Double

As shown in the question java.lang.Double.equals() calls public static long doubleToLongBits(double value), which

/**
 * Returns a representation of the specified floating-point value
 * according to the IEEE 754 floating-point "double
 * format" bit layout.

and then checks for equality with ==. (doubleToLongBits internally calls public static native long doubleToRawLongBits(double value), so it is platform dependent).

Here the way the primitive type works.

primitive type double

The floating-point types are float and double, which are conceptually associated with the single-precision 32-bit and double-precision 64-bit format IEEE 754 values and operations as specified in IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985 (IEEE, New York).JLS-4.2.3


Operators on floating-point numbers behave as specified by IEEE 754 (with the exception of the remainder operator (§15.17.3)). JLS-4.2.4


So the fastest way would be using primitive types and possibly performing a 'delta check' depending on the needed accuracy. If that's not possible using the methods provided by Double.

One should not use the JUnit assert method, since it performs more checks, one would be better off doing sth like:

boolean eq = Double.valueOf(5.0d).equals(Double.valueOf(2.0d));
Assert.assertTrue(eq);