The only return value the java.lang.Comparable<T>
interface explicitly requires is 0 for when T a
and T b
are equal. If a
is less than b
, then compare(a, b)
must be negative, not necessarily −1, and compare(b, a)
must be positive, not necessarily 1.
And yet some comparable classes in the JDK limit the output of the comparison function precisely in that manner, eg,
scala> (65 to 90).map(n => java.lang.Integer.compare(77, n))
res19: IndexedSeq[Int] = Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)
and some don't, eg,
scala> ('A' to 'Z').map(ch => java.lang.Character.compare('M', ch))
res10: IndexedSeq[Int] = Vector(12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13)
I was well aware of the somewhat obscure case of RDNs (from javax.naming.ldap
), but I wasn't expecting to come across this with anything from java.lang
. I first noticed unrestricted output in Character.compare()
in the context of a Caesar cypher program in Java, but I find it easier to run "experiments" like these in the local Scala REPL.
When I wrote my implementation of Fraction
, I followed the example of Integer
rather than Character
.
scala> val fractA = new fractions.Fraction(65, 128)
fractA: fractions.Fraction = 65/128
scala> val fractB = new fractions.Fraction(90, 128)
fractB: fractions.Fraction = 45/64
scala> fractA to fractB
res20: fractions.FractionRange = 65/128 to 45/64
scala> val fractC = new fractions.Fraction(77, 128)
fractC: fractions.Fraction = 77/128
scala> res20.map(_.compare(fractC))
res21: IndexedSeq[Int] = Vector(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
scala> res20.map(fractC.compare)
res22: IndexedSeq[Int] = Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)
scala> res19 == res22
res23: Boolean = true
In the case of Fraction
, it's no problem to implement it like this. Actually, it would be more trouble to not do it like this. The numerator and denominator are of type long
, so there could be problems if the numerator of the difference is just a little outside the range of int
. By using Long.signum()
, I reduce the possibility of wrong results to a small set of edge cases.
Since char
maps to half the range of int
, I suppose it's easier for String
to not restrict the result of compare()
to {−1, 0, 1}.
scala> "Hello, World!" compare "Hello, world?"
res30: Int = -32
Here I'm guessing that if no surrogates are involved, or maybe even if surrogates are involved, it's easier to just run Character.compare()
on each character of the String
until either the first nonzero result or reaching the end.
Is this the explanation, that one should do whatever is easiest and gives the correct results? Or is there a deeper reason to restrict to signum of the difference in some comparable classes and not others?
Because implementations are free to choose whatever positive or negative value suits them best. The spec says about the return value:
a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
One might argue whether this spec was a wise decision or not, but that's the way it has been defined. So, never rely on the results of a comparison being only -1, 0, or 1, even if experimenting with one specific Java version shows that behaviour - it might change with the next release.
This answer is already found in your question, mainly.
There are two typical ways of implementing comparison:
if ... else if ... else
construct where the return values for the three cases are explicitly given. Then it's a natural choice to use -1, 0, and 1, and I don't know of an implementation using other fixed values.So, subtraction is a valid solution for byte and char, where an int-based subtraction has enough reserve bits that overflow can't happen. So, it's more likely for those datataypes and their derivatives to show values outside of -1, 0, and 1.
You're wrting about a Fraction
class you're implementing. If two Fraction instances can be created, not giving an exception, I'd require the compareTo()
method to give correct results, always. As comparison of fractions is a tricky thing, overflows of intermediate results can be expected. So, I'd recommend to create some test cases with numerators and/or denominators close to the validity limits (whatever you define them to be).
Another approach to avoid overflow would be switching to the unlimited-range BigInteger
type, but that might have a performance impact.
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.