Comparing the values of two generic Numbers

JavaGenericsNumbersComparable

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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 2 - Java

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 3 - Java

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 - Java

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 - Java

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.

Solution 6 - Java

if(yourNumber instanceof Double) {
    boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue();
    // [...]
}

Note: The instanceof check isn't necessarily needed - depends on how exactly you want to compare them. You could of course simply always use .doubleValue(), as every Number should provide the methods listed here.

Edit: As stated in the comments, you will (always) have to check for BigDecimal and friends. But they provide a .compareTo() method:

if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal) { 
    boolean greaterThanOtherNumber = ((BigDecimal)yourNumber).compareTo((BigDecimal)otherNumber) > 0;
} 

Solution 7 - Java

What about this one? Definitely not nice, but it deals with all necessary cases mentioned.

public class SimpleNumberComparator implements Comparator<Number>
	{
		@Override
		public int compare(Number o1, Number o2)
		{
			if(o1 instanceof Short && o2 instanceof Short)
			{
				return ((Short) o1).compareTo((Short) o2);
			}
			else if(o1 instanceof Long && o2 instanceof Long)
			{
				return ((Long) o1).compareTo((Long) o2);
			}
			else if(o1 instanceof Integer && o2 instanceof Integer)
			{
				return ((Integer) o1).compareTo((Integer) o2);
			}
			else if(o1 instanceof Float && o2 instanceof Float)
			{
				return ((Float) o1).compareTo((Float) o2);
			}
			else if(o1 instanceof Double && o2 instanceof Double)
			{
				return ((Double) o1).compareTo((Double) o2);
			}
			else if(o1 instanceof Byte && o2 instanceof Byte)
			{
				return ((Byte) o1).compareTo((Byte) o2);
			}
			else if(o1 instanceof BigInteger && o2 instanceof BigInteger)
			{
				return ((BigInteger) o1).compareTo((BigInteger) o2);
			}
			else if(o1 instanceof BigDecimal && o2 instanceof BigDecimal)
			{
				return ((BigDecimal) o1).compareTo((BigDecimal) o2);
			}
			else
			{
				throw new RuntimeException("Ooopps!");
			}

		}

	}

Solution 8 - Java

This should work for all classes that extend Number, and are Comparable to themselves.

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

    public int compare(T a, T b){
        if (a instanceof Comparable) 
            if (a.getClass().equals(b.getClass()))
                return ((Comparable<T>)a).compareTo(b);        
        throw new UnsupportedOperationException();
    }
}

Solution 9 - Java

You can simply use Number's doubleValue() method to compare them; however you may find the results are not accurate enough for your needs.

Solution 10 - Java

Let's assume that you have some method like:

public <T extends Number> T max (T a, T b) {
   ...
   //return maximum of a and b
}

If you know that there are only integers, longs and doubles can be passed as parameters then you can change method signature to:

public <T extends Number> T max(double a, double b) {
   return (T)Math.max (a, b);
}

This will work for byte, short, integer, long and double.

If you presume that BigInteger's or BigDecimal's or mix of floats and doubles can be passed then you cannot create one common method to compare all these types of parameters.

Solution 11 - Java

If your Number instances are never Atomic (ie AtomicInteger) then you can do something like:

private Integer compare(Number n1, Number n2) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {

 Class<? extends Number> n1Class = n1.getClass();
 if (n1Class.isInstance(n2)) {
  Method compareTo = n1Class.getMethod("compareTo", n1Class);
  return (Integer) compareTo.invoke(n1, n2);
 }

 return -23;
}

This is since all non-Atomic Numbers implement Comparable

EDIT:

This is costly due to reflection: I know

EDIT 2:

This of course does not take of a case in which you want to compare decimals to ints or some such...

EDIT 3:

This assumes that there are no custom-defined descendants of Number that do not implement Comparable (thanks @DJClayworth)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionb_erbView Question on Stackoverflow
Solution 1 - JavaBennyBoyView Answer on Stackoverflow
Solution 2 - JavagustafcView Answer on Stackoverflow
Solution 3 - JavarolveView Answer on Stackoverflow
Solution 4 - JavaLiiView Answer on Stackoverflow
Solution 5 - JavakopperView Answer on Stackoverflow
Solution 6 - JavaTedilView Answer on Stackoverflow
Solution 7 - Javab_erbView Answer on Stackoverflow
Solution 8 - JavaSarmunView Answer on Stackoverflow
Solution 9 - JavaSteven MackenzieView Answer on Stackoverflow
Solution 10 - JavaRomanView Answer on Stackoverflow
Solution 11 - JavaYaneeveView Answer on Stackoverflow