100 votes

Math.abs renvoie une valeur erronée pour Integer.Min_VALUE

Ce code :

System.out.println(Math.abs(Integer.MIN_VALUE));

Renvoie à -2147483648

Ne devrait-il pas retourner la valeur absolue comme 2147483648 ?

113voto

jonmorgan Points 1384

Integer.MIN_VALUE est -2147483648 mais la plus haute valeur qu'un entier de 32 bits peut contenir est +2147483647 . Tenter de représenter +2147483648 dans un int de 32 bits sera effectivement "reporté" sur l'int de 32 bits. -2147483648 . En effet, lors de l'utilisation d'entiers signés, les représentations binaires en complément à deux de +2147483648 et -2147483648 sont identiques. Ce n'est toutefois pas un problème, car +2147483648 est considéré comme hors de portée.

Pour en savoir un peu plus sur le sujet, vous pouvez consulter l'article suivant Article de Wikipédia sur le complément à deux .

44voto

bernard paulus Points 690

Le comportement que vous indiquez est en effet contre-intuitif. Cependant, ce comportement est celui spécifié par la directive javadoc pour Math.abs(int) :

Si l'argument n'est pas négatif, l'argument est retourné. Si l'argument est négatif, la négation de l'argument est retournée.

C'est-à-dire, Math.abs(int) devrait se comporter comme le code Java suivant :

public static int abs(int x){
    if (x >= 0) {
        return x;
    }
    return -x;
}

C'est-à-dire, dans le cas négatif, -x .

Selon le JLS section 15.15.4 le -x est égal à (~x)+1~ est l'opérateur de complémentation binaire.

Pour vérifier si cela semble correct, prenons l'exemple de -1.

La valeur entière -1 peut être noté comme 0xFFFFFFFF en hexadécimal en Java (vérifiez cela avec une println ou toute autre méthode). En prenant -(-1) donne donc :

-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1

Donc, ça marche.

Essayons maintenant avec Integer.MIN_VALUE . Sachant que le plus petit entier peut être représenté par 0x80000000 c'est-à-dire que le premier bit est mis à 1 et les 31 bits restants sont mis à 0, nous avons :

-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1 
                     = 0x80000000 = Integer.MIN_VALUE

Et c'est pourquoi Math.abs(Integer.MIN_VALUE) renvoie à Integer.MIN_VALUE . Notez également que 0x7FFFFFFF est Integer.MAX_VALUE .

Cela dit, comment éviter les problèmes dus à cette valeur de retour contre-intuitive à l'avenir ?

  • On pourrait, comme l'a souligné @Bombe , lancez notre int s à long avant. Cependant, nous devons soit

    • les remettre dans int ce qui ne fonctionne pas car Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE) .
    • Ou continuer avec long espérant en quelque sorte que nous n'appellerons jamais Math.abs(long) avec une valeur égale à Long.MIN_VALUE puisque nous avons aussi Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE .
  • Nous pouvons utiliser BigInteger partout, parce que BigInteger.abs() renvoie en effet toujours une valeur positive. C'est une bonne alternative, un peu plus lente que la manipulation de types entiers bruts.

  • Nous pouvons écrire notre propre wrapper pour Math.abs(int) comme ceci :

    /**

    • Fail-fast wrapper for {@link Math#abs(int)}
    • @param x
    • @return the absolute value of x
    • @throws ArithmeticException when a negative value would have been returned by {@link Math#abs(int)} */ public static int abs(int x) throws ArithmeticException { if (x == Integer.MIN_VALUE) { // fail instead of returning Integer.MAX_VALUE // to prevent the occurrence of incorrect results in later computations throw new ArithmeticException("Math.abs(Integer.MIN_VALUE)"); } return Math.abs(x); }

En guise de conclusion, ce problème semble être connu depuis un certain temps. Voir par exemple cette entrée sur la règle correspondante de findbugs .

13voto

moe Points 2718

Voici ce que dit la doc Java pour Math.abs() dans javadoc :

Notez que si l'argument est égal à la valeur de Integer.MIN_VALUE, la valeur de valeur int représentable la plus négative, le résultat est cette même valeur, qui est négative.

6voto

Bombe Points 34185

Pour voir le résultat que vous attendez, lancez Integer.MIN_VALUE à long :

System.out.println(Math.abs((long) Integer.MIN_VALUE));

1voto

ymajoros Points 1024

2147483648 ne peut pas être stocké dans un entier en java, sa représentation binaire est la même que -2147483648.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X