Re: Discussion of why java.lang.Number does not implement Comparable
On Jul 28, 11:54 am, Patricia Shanahan <p...@acm.org> wrote:
Even if a class represents values of some transcendental function there
can be some points at which the function has an algebraic solution. For
example, consider the sines of angles that can be expressed as rational
numbers of degrees. sin(30), sin(45), and sin(60) are exact solutions to
quadratic equations.
To clarify, when I said above that transcendental and algebraic were
unequal I meant individual numbers where one's known to be
transcendental and the other algebraic.
The trig values that are algebraic are well-characterized aren't they?
So knowing which are not transcendental is not impossible.
However, to make Number implement Comparable we would need a general
approach to comparing that does not depend on writing special code for
every pair of Number subclasses.
Here's a challenge for those who want to make Number implement
Comparable<Number>. Write the API documentation for the compareTo
method. Tell me what I have to do if I am writing a new Number subclass
in order to conform to the contract.
The simplest most general way is to convert to BigDecimal and punt to
BigDecimal.compareTo, with precision decided by something (perhaps
something new) in the way of a MathContext-like entity. It couldn't be
passed to the compareTo method obviously so it would have to be a
threadlocal or something (a singleton would cause problems if distinct
threads did simultaneous math and wanted distinct precisions).
Ones that don't compare equals() have to be treated as distinct, so
give a nonzero compareTo, and I think objects of different Number
direct-subclasses never compare equals(). If they all have sensible
implementations of hashCode() too, the thing to do would be:
In class Foo, see if the argument is also a Foo. If so, use a suitable
comparison within Foos. Otherwise, get BigDecimal versions at the
context-determined precision and compare those. If nonzero return the
result. If zero compare the hashCodes. If nonzero return the result.
If still zero ... should be damned rare, but I dunno what. :) Actually
forget hash codes; in the case the arguments are of different Number
direct-subclasses just use the lexical ordering of
a.getClass().toString() and b.getClass().toString(), which should be
different. Using a locale-independent collation!
But I think at this point the best thing is probably to leave things
as is, and users of mixed Number types can convert to a common type
manually to do comparisons. Implementing Number.compareTo(Number) in
general is only warranted if people are liable to be stuffing a
heterogeneous mix of Numbers into a TreeMap as keys or something. And
since they all implement hashCode sensibly, so far as I'm aware,
HashMap is probably the better choice for that anyway, leaving cases
where you want a collection (e.g. TreeSet) to iterate over a
heterogeneous mix in sorted order. For that a wrapper suffices:
public class NumberWrapper implements Comparable<NumberWrapper> {
private Number delegate;
private int bigDecimalDigitPrecision;
// obvious constructor and getNumber()
// obvious hashCode punts to delegate
// obvious equals returns false if arg not a NumberWrapper,
// then unwraps both and punts to delegates
public int compareTo (NumberWrapper nw) {
if (delegate.getClass().equals(nw.delegate.getClass())) {
// Insert suitably clever use of reflection to call
// delegate.compareTo(nw.delegate) with both cast to
// the appropriate subclass. Making the above if
// trigger if delegate classes differ but have
// common ancestor deeper than Number left as an
// exercise for the reader.
return ...
}
BigDecimal t1 = // May need reflection here too, or for
// Sun to put a bigDecimalValue(precision) in Number
BigDecimal t2 = // Ditto, with nw.delegate
int result = t1.compareTo(t2);
if (result != 0) return result;
return delegate.getClass().toString().compareTo(
nw.delegate.getClass().toString());
}
}
The major issues apparent are:
* The wrapper will heavily need reflection, somewhat less if Number
were changed non-drastically. Adding bigDecimalValue(precision)
to Number would go a long way. Adding a compare(Number) that may
throw if the class isn't the same would help enormously.
* The wrappers can hold the precision to use, but may act up if
ones with different precisions were mixed.
* OTOH not using the wrappers seems to require either hard-coding
the precision (evil!) or using a ThreadLocal (slow!)
Fortunately I think putting heterogeneous Numbers into a sorted
collection is really damn rare.