Proposal: Sameness operators

Reinier Zwitserloot reinier at zwitserloot.com
Thu Apr 2 09:15:22 PDT 2009


The argument that .compareTo should not be used because it isn't  
entirely congruent with either the meaning of .equals() or the  
meanings of all the comparison operators (from == to <) on the  
primitives, just doesn't hold water, for this simple reason:

They make absolutely no sense now, either, and nobody cares about that.


Here's some fun facts (all asserts are true):

int x = 0/0; //ArithmeticException
double x = 0/0.0; //okay, x = NaN
double y = 0/0.0;

assert x != y;
assert ! (x == y);
assert Double.valueOf(x).equals(Double.valueOf(y)); //WTF!
assert Double.valueOf(x).equals(y); //WTF!

Clearly, equals is fundamentally broken. We need a new equals! Oh no!

assert ! (x < y);
assert ! (x <= y);
assert Double.valueOf(x).compareTo(y) == 0; //WTF!

So, compareTo is broken as well. Just like equals is.

assert x < "foo"; //Compiler Error: Cant compare those two.
assert Double.valueOf(x).compareTo("foo"); //With java 1.4: Runtime  
error.
assert Double.valueOf(x).compareTo("foo"); //With java 1.5+: Compile  
time error.

A compiler error is better, but these two results certainly are  
consistent. In fact, in java 1.5+, you DO get a compiler error.

So, if x < "foo" would be rewritten to the version below that after  
comparison operator support is added that uses compareTo, you get  
roughly the same situation: A compiler error that tells you that the  
compiler didn't expect a String there. The exact text of the error is  
slightly different, but other than that, it's the same. Why is this a  
problem?



The fix to me seems relatively simple:

1. Anything that is legal now needs to stay legal. I would write down  
the rules for this, except they are totally nuts, as the above samples  
show. So, just do whatever java 6 does for everything that would have  
been legal in 6. In particular, this involves unboxing both sides if  
both sides can be unboxed. And unbox one side if the other side is a  
numeric primitive. Yes, this leads to crazy puzzlers, such as:

Double x = Double.valueOf(Double.NaN);
double y = 0;
if ( x != y && !(x < y) && !(x > y) ) System.out.println("This line  
prints? Whoa!");

But that's what we're stuck with. Unless Lovatt's and my crazy idea of  
breaking backwards compatibility with an explicit source keyword is  
implemented, this craziness can't go away, we're stuck with it.

2. First unbox whatever can be unboxed, then continue to:

3. If either LHS or RHS implements Comparable, we'll use that. Note  
that primitives do not implement Comparable. Comparison operations  
between primitives are hardcoded into the JLS spec. If both sides do  
implement Comparable, LHS wins. If only the RHS does, the order is  
mathematically reversed (a > b becomes b < a). If the winning HS is  
willing to take Number according to the generics bound of Comparable  
(which means RAW types *DO*, as they take all objects), then numeric  
primitives are boxed. If neither side implements Comparable, compiler  
error. If a side does, but the type of the other side isn't compatible  
with Comparable's bound, or is a non-numeric primitive (boolean - char  
is actually numeric, because this didn't look quite enough like a  
Salvador Dali painting yet, go figure) you get a compile-time error,  
even if the other way around would have worked.

4. BigDecimal and BigInteger are changed to implement  
Comparable<Number> instead. The fact that they currently don't is  
something I don't really understand would consider filing as a bug if  
I worked more with mixed BI/BD and primitives math. By doing this,  
something like:

5 < new BigInteger("10") would work out the way you expect it to (that  
would return 'true').


BI/BD's comparison methods can't just convert the incoming primitive  
to a BI/BD, because NaN and the infinities don't convert, yet the  
answer to this expression:

Double.NEGATIVE_INFINITY < BigInteger.ZERO

should clearly be true. The above plan can make that happen by adding  
more logic to BI/BD's compareTo methods.



The reason Double and company need to unbox is because their  
implementations of compareTo can then stay blissfully unaware of BI  
and BD's existence. There's a problem if you attempt to compare 2  
numeric entities that aren't hardcoded into the JVM, but I don't think  
a perfect solution is possible here.

This proposal is not entirely consistent with the primitive's compare  
operators, but attempting to be consistent with the current mess that  
is comparison operators between primitives is not a goal that we  
should aspire to, IMO. The concept of 'I'll give you an answer if  
there is a mathematical answer, and if there isn't, I will give you a  
compile-time error if I can, and a runtime error otherwise' seems  
perfect to me. That's compareTo's current semantics, so, lucky us!


NB: Attempting to integrate equality into compareTo is probably a bad  
idea, eventhough we are stuck with some funny puzzlers where equality  
and comparison clearly aren't consistent. The real problem is that  
numeric equality is just different from object equality (numerically,  
NaN is not equal to NaN, but as objects, they are, and asking if apple  
== pear makes no sense numerically, but as objects, there's a logical  
answer: No, they aren't). I don't see us getting out of that one.  
Possibly use <> as an operator that specifically means: Not  
Numerically equal, and !<> as numerically equal. Yeah, that doesn't  
really look too readable to me either.


  --Reinier Zwitserloot



On Apr 2, 2009, at 14:48, Mark Thornton wrote:

> Tom Hawtin wrote:
>> Reinier Zwitserloot wrote:
>>
>>> Why can't we use compareTo and equals?
>>>
>>
>>  * compareTo may well be unreasonably inefficient.
>>  * equals handles nulls (asymmetrically), compareTo does not
>>  * equals does test-and-cast (event for generic types - urgh),  
>> compareTo uses generics
>>  * equals does not throw ClassCastException, compareTo does (but  
>> probably shouldn't with generics)
>>
>> Therefore, new methods please. I'm sure you are aware the whole  
>> subject of operator overloading in general is a minefield.
>>
> a.compareTo(b) == 0 and a.equals(b) are not always the same,  
> sometimes for very good reasons. I seem to remember that there is at  
> least one such class in the Java platform.
>
> Mark Thornton
>




More information about the coin-dev mailing list