web-dev-qa-db-fra.com

Comparaison des valeurs de deux nombres génériques

Je veux comparer avec des variables, toutes deux de type T extends Number. Maintenant, je veux savoir laquelle des deux variables est supérieure à l'autre ou égale. Malheureusement, je ne connais pas encore le type exact, je sais seulement que ce sera un sous-type de Java.lang.Number. Comment puis je faire ça?

EDIT : J'ai essayé une autre solution de contournement en utilisant TreeSets, qui fonctionnait en fait avec un ordre naturel (bien sûr, cela fonctionne, toutes les sous-classes de Number implémente Comparable sauf pour AtomicInteger et AtomicLong). Ainsi, je perdrai des valeurs en double. Lorsque vous utilisez Lists, Collection.sort() n'acceptera pas ma liste en raison de disparités liées. Très insatisfaisant.

54
b_erb

Une solution fonctionnelle (mais fragile) ressemble à ceci:

class NumberComparator implements Comparator<Number> {

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

}

Ce n'est toujours pas génial, car il compte sur toString renvoyant une valeur analysable par BigDecimal (ce que font les classes standard Java Number) , mais que le contrat Number n'exige pas).

Edit, sept ans plus tard: Comme indiqué dans les commentaires, il y a (au moins?) Trois cas particuliers toString peuvent produire cela vous devez prendre en compte:

30
gustafc

Cela devrait fonctionner pour toutes les classes qui étendent Number et sont comparables à elles-mêmes. En ajoutant le & Comparable, vous autorisez à supprimer toutes les vérifications de type et fournissez gratuitement des vérifications de type à l'exécution et le lancement d'erreurs par rapport à la réponse Sarmun.

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

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

Une solution qui pourrait fonctionner pour vous est de ne pas travailler avec T extends Number mais avec T extends Number & Comparable. Ce type signifie: "T ne peut être défini que sur des types qui implémentent both les interfaces."

Cela vous permet d'écrire du code qui fonctionne avec tous les nombres comparables. Statique et élégant.

C'est la même solution que BennyBoy propose, mais elle fonctionne avec toutes sortes de méthodes, pas seulement avec les classes de comparaison.

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
}
13
Lii

Après avoir posé une question similaire et étudié les réponses ici, j'ai trouvé ce qui suit. Je pense qu'elle est plus efficace et plus robuste que la solution proposée par 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);
    }
}
12
rolve

Le plus "générique" Java nombre primitif est double, donc en utilisant simplement

a.doubleValue() > b.doubleValue()

devrait être suffisant dans la plupart des cas, mais ... il y a des problèmes subtils ici lors de la conversion des nombres en double. Par exemple, ce qui suit est possible avec 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());

résulte en:

false
true

Bien que je m'attende à ce que ce soit un cas très extrême, cela est possible. Et non - il n'y a aucun moyen générique précis à 100%. L'interface numérique n'a pas de méthode comme exactValue () pour convertir en un type capable de représenter le nombre de manière parfaite sans perdre aucune information.

En fait, avoir de tels nombres parfaits est impossible en général - par exemple, représenter le nombre Pi est impossible en utilisant n'importe quelle arithmétique utilisant un espace fini.

5
kopper

Cela devrait fonctionner pour toutes les classes qui étendent Number et sont comparables à elles-mêmes.

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();
    }
}
2
Sarmun
if(yourNumber instanceof Double) {
    boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue();
    // [...]
}

Remarque: La vérification instanceof n'est pas nécessairement nécessaire - dépend de la façon exacte dont vous souhaitez les comparer. Vous pouvez bien sûr toujours simplement utiliser .doubleValue(), car chaque Number doit fournir les méthodes listées ici .

Edit : Comme indiqué dans les commentaires, vous devrez (toujours) vérifier BigDecimal et ses amis. Mais ils fournissent une méthode .compareTo():

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

Vous pouvez simplement utiliser la méthode Number's doubleValue() pour les comparer; cependant, vous pouvez trouver que les résultats ne sont pas assez précis pour vos besoins.

1
Steven Mackenzie

Et celui-ci? Certainement pas Nice, mais il traite de tous les cas nécessaires mentionnés.

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!");
            }

        }

    }
1
b_erb
System.out.println(new BigDecimal(0.1d).toPlainString());
System.out.println(BigDecimal.valueOf(0.1d).toPlainString());
System.out.println(BigDecimal.valueOf(0.1f).toPlainString());
System.out.println(Float.valueOf(0.1f).toString());
System.out.println(Float.valueOf(0.1f).doubleValue());
0
t _ liang

Si vos instances de Number sont ( jamais Atomic (ie AtomicInteger), vous pouvez faire quelque chose comme:

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;
}

En effet, tous les Number non atomiques implémentent Comparable

MODIFIER :

Cela coûte cher à cause de la réflexion: je sais

EDIT 2:

Bien sûr, cela ne prend pas un cas dans lequel vous souhaitez comparer les décimales aux pouces ou à certains autres ...

MODIFIER:

Cela suppose qu'aucun descendant de Number personnalisé ne met en œuvre Comparable (merci @DJClayworth)

0
Yaneeve

Supposons que vous ayez une méthode comme:

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

Si vous savez qu'il n'y a que des entiers, des longs et des doubles peuvent être passés en tant que paramètres, vous pouvez changer la signature de la méthode en:

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

Cela fonctionnera pour octet, court, entier, long et double.

Si vous supposez que BigInteger ou BigDecimal ou un mélange de flottants et de doubles peuvent être passés, vous ne pouvez pas créer une méthode commune pour comparer tous ces types de paramètres.

0
Roman