Comparing the values of two generic Numbers

51,947

Solution 1

A working (but brittle) solution is something like this:

class NumberComparator implements Comparator<Number> {

    public int compare(Number a, Number b){
        return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
    }

}

It's still not great, though, since it counts on toString returning a value parsable by BigDecimal (which the standard Java Number classes do, but which the Number contract doesn't demand).

Edit, seven years later: As pointed out in the comments, there are (at least?) three special cases toString can produce that you need to take into regard:

Solution 2

This should work for all classes that extend Number, and are Comparable to themselves. By adding the & Comparable you allow to remove all the type checks and provides runtime type checks and error throwing for free when compared to Sarmun answer.

class NumberComparator<T extends Number & Comparable> implements Comparator<T> {

    public int compare( T a, T b ) throws ClassCastException {
        return a.compareTo( b );
    }
}

Solution 3

After having asked a similar question and studying the answers here, I came up with the following. I think it is more efficient and more robust than the solution given by gustafc:

public int compare(Number x, Number y) {
    if(isSpecial(x) || isSpecial(y))
        return Double.compare(x.doubleValue(), y.doubleValue());
    else
        return toBigDecimal(x).compareTo(toBigDecimal(y));
}

private static boolean isSpecial(Number x) {
    boolean specialDouble = x instanceof Double
            && (Double.isNaN((Double) x) || Double.isInfinite((Double) x));
    boolean specialFloat = x instanceof Float
            && (Float.isNaN((Float) x) || Float.isInfinite((Float) x));
    return specialDouble || specialFloat;
}

private static BigDecimal toBigDecimal(Number number) {
    if(number instanceof BigDecimal)
        return (BigDecimal) number;
    if(number instanceof BigInteger)
        return new BigDecimal((BigInteger) number);
    if(number instanceof Byte || number instanceof Short
            || number instanceof Integer || number instanceof Long)
        return new BigDecimal(number.longValue());
    if(number instanceof Float || number instanceof Double)
        return new BigDecimal(number.doubleValue());

    try {
        return new BigDecimal(number.toString());
    } catch(final NumberFormatException e) {
        throw new RuntimeException("The given number (\"" + number + "\" of class " + number.getClass().getName() + ") does not have a parsable string representation", e);
    }
}

Solution 4

One solution that might work for you is to work not with T extends Number but with T extends Number & Comparable. This type means: "T can only be set to types that implements both the interfaces."

That allows you to write code that works with all comparable numbers. Statically typed and elegant.

This is the same solution that BennyBoy proposes, but it works with all kinds of methods, not only with comparator classes.

public static <T extends Number & Comparable<T>> void compfunc(T n1, T n2) {
    if (n1.compareTo(n2) > 0) System.out.println("n1 is bigger");
}

public void test() {
    compfunc(2, 1); // Works with Integer.
    compfunc(2.0, 1.0); // And all other types that are subtypes of both Number and Comparable.
    compfunc(2, 1.0); // Compilation error! Different types.
    compfunc(new AtomicInteger(1), new AtomicInteger(2)); // Compilation error! Not subtype of Comparable
}

Solution 5

The most "generic" Java primitive number is double, so using simply

a.doubleValue() > b.doubleValue()

should be enough in most cases, but... there are subtle issues here when converting numbers to double. For example the following is possible with BigInteger:

    BigInteger a = new BigInteger("9999999999999992");
    BigInteger b = new BigInteger("9999999999999991");
    System.out.println(a.doubleValue() > b.doubleValue());
    System.out.println(a.doubleValue() == b.doubleValue());

results in:

false
true

Although I expect this to be very extreme case this is possible. And no - there is no generic 100% accurate way. Number interface have no method like exactValue() converting to some type able to represent number in perfect way without loosing any information.

Actually having such perfect numbers is impossible in general - for example representing number Pi is impossible using any arithmetic using finite space.

Share:
51,947
b_erb
Author by

b_erb

UUlm

Updated on July 05, 2022

Comments

  • b_erb
    b_erb almost 2 years

    I want to compare to variables, both of type T extends Number. Now I want to know which of the two variables is greater than the other or equal. Unfortunately I don't know the exact type yet, I only know that it will be a subtype of java.lang.Number. How can I do that?

    EDIT: I tried another workaround using TreeSets, which actually worked with natural ordering (of course it works, all subclasses of Number implement Comparable except for AtomicInteger and AtomicLong). Thus I'll lose duplicate values. When using Lists, Collection.sort() will not accept my list due to bound mismatchs. Very unsatisfactory.

  • b_erb
    b_erb about 14 years
    What about BigDecimal or other types out of range for doubles?
  • Tedil
    Tedil about 14 years
    simply add a if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal){ boolean greaterThanOtherNumber = yourNumber.compareTo(otherNumber) > 0; }
  • Yaneeve
    Yaneeve about 14 years
    I think that this is too risky
  • Roman
    Roman about 14 years
    BigInteger and BigDecimal implements Number as well. This wouldn't work.
  • Roman
    Roman about 14 years
    it's not likely but it can cause NumberFormatException if I create a subclass of Number and don't override toString in a proper way.
  • Steven Mackenzie
    Steven Mackenzie about 14 years
    True, it could be risky and it could fail. There are more caveats than the precision/accuracy issue I noted. But it may suit the OP's needs if he doesn't need to handle large BigDecimals.
  • DJClayworth
    DJClayworth about 14 years
    Hopefully the programmer will know if any descendants of Number other than the standard ones might be encountered.
  • DJClayworth
    DJClayworth about 14 years
    This assumes that there are no custom-defined descendants of Number.
  • Yaneeve
    Yaneeve about 14 years
    @DJClayworth: true enough. And those are not the only constraints as I had mentioned above. But why would there be 'custom-defined descendants of Number'?
  • Saurabh
    Saurabh over 13 years
    An example of a custom-defined descendant of Number is org.apache.commons.math.fraction.BigFraction, which I use in many of my programs.
  • Eric
    Eric over 11 years
    Note that BigDecimal.valueOf should be used instead of the constructor ESPECIALLY for the Float/Double creation or you'll get base2 floating point errors in the conversion.
  • rolve
    rolve over 11 years
    Thanks for your comment! I'm not sure your point is valid though. The Javadoc of the double constructor says: "[...], note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor." It is true that the exact conversion often yields unexpected results (e.g. for 0.1 literals). But since we don't know anything about the numbers' origins, I think the only sensible way to handle them is to assume nothing about it.
  • user949300
    user949300 over 10 years
    Zeeks! This is a really bad solution. Though I can't off hand think of a better one, so I won't downvote yet. :-(
  • gustafc
    gustafc over 10 years
    @user949300 As I said, it's a working (but brittle) solution which is not great, though - so, I agree it's a bad solution! But the whole premise is flawed, generally one shouldn't work with arbitrary Numbers. Best thing here probably would've been constraining the type parameter further, to T extends Number & Comparable<T>.
  • Christopher Barber
    Christopher Barber about 8 years
    Note that this also does not work for Long either since they can also not be fully represented using Double.
  • Campa
    Campa over 7 years
    Is there any drawback with this answer? Otherwise it should probably be chosen as best answer.
  • Admin
    Admin over 7 years
    It requires the source collection to be a specific type, so it won't work for abstract Number class.
  • Andreas
    Andreas about 7 years
    "toString returning a value parsable by BigDecimal (which the standard Java Number classes do" That is not entirely true, given that Double and Float can return 3 special values (NaN, Infinity, and -Infinity) that are not supported by BigDecimal.
  • gustafc
    gustafc about 7 years
    Good catch @Andreas. You're the first one in 7 years to find it :)
  • Abdalrazag Al-Shrofat
    Abdalrazag Al-Shrofat over 5 years
    Like you said, not nice, shouldn't be here :)
  • Pr0methean
    Pr0methean almost 5 years
    This only works when the compareTo method of one actually accepts the other. It'll throw a ClassCastException if called with a Long and a Double.
  • sorbet
    sorbet over 3 years
    @keni But do you have a better solution?
  • Abdalrazag Al-Shrofat
    Abdalrazag Al-Shrofat over 3 years
    It doesn't deal with all the necessary cases actually, not a lot more than @BennyBoy's nor Lii's certainly and suffers the same probs as pointed out by Pr0methean. Yea, and a lot more verbose and not close to being closed to modification. No offence meant.