简体   繁体   中英

signed zero double equals (as in ==) but Double.compare(double,double) != 0

I've encountered something strange regarding comparing double zeros. Depending on how the double zero primitives are initiated the Double.compare(double,double) method may or may not think that they are "equal" (may or may not return 0).

Comparing the various zero double:s using == always reports them as equal. If they are equal in terms of ==, they must (should) be equal in terms of the the compare method. They are not!

Check out this sample program:

public class CompareZeros {

public static void main(final String[] args) {

    final double negDbl = -0.0;
    final double posInt = 0;
    final double posDbl = 0.0;
    final double negInt = -0;

    CompareZeros.compare("negDbl <-> posInt", negDbl, posInt);
    CompareZeros.compare("negDbl <-> posDbl", negDbl, posDbl);
    CompareZeros.compare("negDbl <-> negInt", negDbl, negInt);

    CompareZeros.compare("posInt <-> negDbl", posInt, negDbl);
    CompareZeros.compare("posInt <-> posDbl", posInt, posDbl);
    CompareZeros.compare("posInt <-> negInt", posInt, negInt);

    CompareZeros.compare("posDbl <-> negDbl", posDbl, negDbl);
    CompareZeros.compare("posDbl <-> posInt", posDbl, posInt);
    CompareZeros.compare("posDbl <-> negInt", posDbl, negInt);

    CompareZeros.compare("negInt <-> negDbl", negInt, negDbl);
    CompareZeros.compare("negInt <-> posInt", negInt, posInt);
    CompareZeros.compare("negInt <-> posDbl", negInt, posDbl);

}

static void compare(final String id, final double arg0, final double arg1) {

    System.out.print(id + ": ");

    if (arg0 == arg1) {
        if (Double.compare(arg0, arg1) == 0) {
            System.out.println("OK");
        } else {
            System.out.println("Strange, and must be wrong!");
        }
    } else {
        if (Double.compare(arg0, arg1) == 0) {
            System.out.println("Strange, but perhaps logically ok");
        } else {
            System.out.println("Consistent...");
        }
    }

}
}

It outputs this:

negDbl <-> posInt: Strange, and must be wrong!
negDbl <-> posDbl: Strange, and must be wrong!
negDbl <-> negInt: Strange, and must be wrong!
posInt <-> negDbl: Strange, and must be wrong!
posInt <-> posDbl: OK
posInt <-> negInt: OK
posDbl <-> negDbl: Strange, and must be wrong!
posDbl <-> posInt: OK
posDbl <-> negInt: OK
negInt <-> negDbl: Strange, and must be wrong!
negInt <-> posInt: OK
negInt <-> posDbl: OK

I checked the source of Double.compare in the JDK that I have. It looks like this:

public static int compare(double d1, double d2) {
    if (d1 < d2)
        return -1;           // Neither val is NaN, thisVal is smaller
    if (d1 > d2)
        return 1;            // Neither val is NaN, thisVal is larger

    // Cannot use doubleToRawLongBits because of possibility of NaNs.
    long thisBits    = Double.doubleToLongBits(d1);
    long anotherBits = Double.doubleToLongBits(d2);

    return (thisBits == anotherBits ?  0 : // Values are equal
            (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
             1));                          // (0.0, -0.0) or (NaN, !NaN)
}

The last few lines explain why this happens. The two parameters are converted into long bits. These bits are then compared. -0.0 and 0.0 are represented differently in bits. The long value for 0.0 is larger than -0.0 . The former is 0 while the latter is -9223372036854775808.

The docs also says so:

0.0d is considered by this method to be greater than -0.0d.

Why is -0 different then?

This is because -0 is the negation operator applied to an integer literal 0 , which evaluates to 0 . The 0 is then converted implicitly into a double .

As to why == thinks negative zero and positive zero are the same, it is specified very clearly in the JLS - section 15.21.1 :

15.21.1. Numerical Equality Operators == and !=

...

Floating-point equality testing is performed in accordance with the rules of the IEEE 754 standard:

...

  • Positive zero and negative zero are considered equal.

Double.compare is documented to be equivalent to

public static int compare(double d1, double d2)

Compares the two specified double values. The sign of the integer value returned is the same as that of the integer that would be returned by the call:

  new Double(d1).compareTo(new Double(d2)) 

And the contract of compareTo , as specified by the interface Comparable requires this method to impose a total order:

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method.

Therefore it cannot implement the comparison relations defined by IEEE 754 and implemented by the numeric comparison operators :

The result of a floating-point comparison, as determined by the specification of the IEEE 754 standard, is:

  • If either operand is NaN, then the result is false.

  • All values other than NaN are ordered, with negative infinity less than all finite values, and positive infinity greater than all finite values.

  • Positive zero and negative zero are considered equal.

...which is obviously not a total order.


A total order is one of the preconditions for stable sorting, and certainly a desirable trait in a comparable type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM