I was looking at the implementation of compare(double, double) in the Java standard library (6). It reads:
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
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)
}
What are the merits of this implementation?
edit: "Merits" was a (very) bad choice of words. I wanted to know how this works.
The explanation is in the comments in the code. Java has double values for both 0.0
and -0.0
, as well as "not a number" (NaN
). You can't use simple ==
operator for these values. Take a peek into the doubleToLongBits()
source and at the Javadoc for the Double.equals()
method:
Note that in most cases, for two instances of class
Double
,d1
andd2
, the value ofd1.equals(d2)
istrue
if and only ifd1.doubleValue() == d2.doubleValue()
also has the value
true
. However, there are two exceptions:
- If
d1
andd2
both representDouble.NaN
, then the equals method returnstrue
, even thoughDouble.NaN == Double.NaN
has the valuefalse
.- If
d1
represents+0.0
whiled2
represents-0.0
, or vice versa, the equal test has the valuefalse
, even though+0.0 == -0.0
has the valuetrue
.This definition allows hash tables to operate properly.
@Shoover's answer is correct (read it!), but there is a bit more to it than this.
As the javadoc for Double::equals
states:
"This definition allows hash tables to operate properly."
Suppose that the Java designers had decided to implement equals(...)
and compare(...)
with the same semantics as ==
on the wrapped double
instances. This would mean that equals()
would always return false
for a wrapped NaN. Now consider what would happen if you tried to use a wrapped NaN in a Map or Collection.
List<Double> l = new ArrayList<Double>();
l.add(Double.NaN);
if (l.contains(Double.NaN)) {
// this wont be executed.
}
Map<Object,String> m = new HashMap<Object,String>();
m.put(Double.NaN, "Hi mum");
if (m.get(Double.NaN) != null) {
// this wont be executed.
}
Doesn't make a lot of sense does it!
Other anomalies would exist because -0.0
and +0.0
have different bit patterns but are equal according to ==
.
So the Java designers decided (rightly IMO) on the more complicated (but more intuitive) definition for these Double methods that we have today.
The merit is that it's the simplest code that fulfills the specification.
One common characteristic of rookie programmers is to overvalue reading source code and undervalue reading specifications. In this case, the spec:
http://java.sun.com/javase/6/docs/api/java/lang/Double.html#compareTo%28java.lang.Double%29
... makes the behavior and the reason for the behavior (consistency with equals()) perfectly clear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With