5

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
apete
  • 1,250
  • 1
  • 10
  • 16
  • 3
    For a quick comment, the [javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#compare-double-double-) should tell you why `0.0` is not equal to `-0.0` using `compare(Double,Double)`. I am sure there is a similar question, so I will not post an answer. Here is the interesting part "_0.0d is considered by this method to be greater than -0.0d._" EDIT : I don't have the source but I am pretty sure `-0` is loosing the sign before it is cast in `double`, giving `0.0` instead of `-0.0`, explaining the result. – AxelH Aug 07 '17 at 09:59
  • 1
    Regarding the int -0, there is no distinct negative-zero value, there is only one representation for zero being all bits zero. – Ralf Kleberhoff Aug 07 '17 at 10:09
  • 3
    I don't think you've asked a question. – Dawood ibn Kareem Aug 07 '17 at 10:39
  • There is nothing wrong with my intuition (in this case) and it's perfectly in line with the JLS and the IEEE. All I'm saying is that if positive and negative zeros are equal (according to JLS and IEEE 754) then why does the various compare methods not reflect this? (That's the question.) It would make perfectly sense to me if some numbers that do compareTo=0 would not be considered ==, but the opposite just seems utterly wrong. Is there some justification for this? – apete Aug 07 '17 at 12:13

2 Answers2

7

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.
Community
  • 1
  • 1
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I checked the source code as well (before posting). Whatever the docs say I think that if val1 == val2 then compare(val1,val2) absolutely should return 0. Anything else seems crazy. From now on I'll never use the compare methods again - they're wrong! – apete Aug 07 '17 at 10:37
  • @apete But `0.0` and `-0.0` are not the same values (not the same bit representation), so this is correct. It is just not what you was expecting but in every language, you have to lower your expectation to match the documentation ;) – AxelH Aug 07 '17 at 10:43
  • More correctly, `-0` is a unary operator followed by an integer literal. The literal part is zero, and applying the negation operator has no effect on its value. – Dawood ibn Kareem Aug 07 '17 at 10:43
  • @DawoodibnKareem Thanks for the clarification, edited. – Sweeper Aug 07 '17 at 10:45
  • Then 0.0 == -0.0 should not evaluate to true, but it does! – apete Aug 07 '17 at 10:46
  • Your answer is way more complet than mine now! I like it – AxelH Aug 07 '17 at 10:52
  • 2
    @apete The JLS says that `0.0 == -0.0` evaluates to true. Whatever the JLS says trumps your intuition about "should" and "should not". – Dawood ibn Kareem Aug 07 '17 at 10:53
  • 1
    Nevertheless according to the principle of least astonishment, it feels like there should be something in the contract of `equals()` that it must be consistent with `==`, that is if `x == y` and neither of them is `null`, then `x.equals(y)`. And implicitly there indeed is but only for non-primitive values. That's where the confusion is coming from, and it's a design error that they decided to implement `==` for floating point numbers the way they did. (On the other hand, if they did it the other way, they'd violate IEEE754, which is equally bad.) – biziclop Aug 07 '17 at 12:29
  • Of course the implementation of == should be in line with IEEE754. Isn't the design error in the implementations of the equals() and compare() methods? I still don't get why it's done this way. What was the tradeoff here? – apete Aug 07 '17 at 19:37
  • 1
    @apete the tradeoff is that due to this implementation (together with the special treatment of `NaN`), the `natural order` of Doubles is a total order. Without a total order, stable sorting of lists of Doubles would be a lot harder. – Hulk Aug 08 '17 at 08:05
1

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.

Hulk
  • 6,399
  • 1
  • 30
  • 52