Math.abs renvoie une valeur incorrecte pour Integer.Min_VALUE


90

Ce code:

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

Retour -2147483648

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

Réponses:


102

Integer.MIN_VALUEest -2147483648, mais la valeur la plus élevée qu'un entier 32 bits peut contenir est +2147483647. Tenter de représenter +2147483648dans un int 32 bits "retournera" effectivement à -2147483648. En effet, lorsque vous utilisez des entiers signés, les représentations binaires complémentaires à deux de +2147483648et -2147483648sont identiques. Ce n'est cependant pas un problème, car il +2147483648est considéré comme hors de portée.

Pour un peu plus de lecture à ce sujet, vous voudrez peut-être consulter l'article de Wikipedia sur le complément de Two .


6
Eh bien, pas un problème est de sous-estimer l'impact, cela pourrait très bien signifier des problèmes. Personnellement, je préfère avoir une exception ou un système de numérotation qui se développe dynamiquement dans un langage de niveau supérieur.
— Maarten Bodewes

40

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

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

Autrement dit, Math.abs(int)devrait se comporter comme le code Java suivant:

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

Autrement dit, dans le cas négatif, -x.

Selon la section JLS 15.15.4 , le -xest égal à (~x)+1, où ~est l'opérateur de complément au niveau du bit.

Pour vérifier si cela vous convient, prenons -1 comme exemple.

La valeur entière -1est peut être notée comme 0xFFFFFFFFen hexadécimal en Java (vérifiez cela avec une printlnou toute autre méthode). Prendre -(-1)donne ainsi:

-(-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 le premier bit mis à 1 et les 31 bits restants mis à 0, nous avons:

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

Et c'est pourquoi Math.abs(Integer.MIN_VALUE)revient Integer.MIN_VALUE. Notez également que 0x7FFFFFFFc'est Integer.MAX_VALUE.

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

  • Nous pourrions, comme l'a souligné @Bombe , lancer nos ints longavant. Cependant, nous devons soit

    • les replacer dans ints, ce qui ne fonctionne pas car Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Ou continuez en longespérant d'une manière ou d'une autre que nous n'appellerons jamais Math.abs(long)avec une valeur égale à Long.MIN_VALUE, puisque nous l'avons également fait Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Nous pouvons utiliser BigIntegers partout, car BigInteger.abs()il renvoie en effet toujours une valeur positive. C'est une bonne alternative, bien qu'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);
}
  • Utilisez un entier binaire AND pour effacer le bit haut, en vous assurant que le résultat n'est pas négatif: int positive = value & Integer.MAX_VALUE(débordant essentiellement de Integer.MAX_VALUEà 0au lieu de Integer.MIN_VALUE)

Pour finir, ce problème semble connu depuis un certain temps. Voir par exemple cette entrée sur la règle findbugs correspondante .


12

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

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


4

Pour voir le résultat que vous attendez, effectuez un cast Integer.MIN_VALUEen long:

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

1
Une solution possible, en effet! Cependant, cela ne résout pas le fait qui Math.absest contre-intuitif en renvoyant un nombre négatif:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
— bernard paulus

1
@bernardpaulus, eh bien, qu'est-ce qu'il est censé faire, à part lancer un ArithmeticException? En outre, le comportement est clairement documenté dans la documentation de l'API.
— Bombe

il n'y a pas de bonne réponse à votre question ... Je voulais juste souligner que ce comportement, source de bugs, n'est pas corrigé par l'utilisation de Math.abs(long). Je m'excuse de mon erreur ici: j'ai pensé que vous aviez proposé l'utilisation de Math.abs(long)comme solution, lorsque vous l'avez montré comme un moyen simple de "voir le résultat attendu par le demandeur". Pardon.
— bernard paulus

Dans Java 15 avec les nouvelles méthodes en fait, une exception est lancée.
— chiperortiz

1

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


0

Mais (int) 2147483648L == -2147483648 il y a un nombre négatif qui n'a pas d'équivalent positif donc il n'y a pas de valeur positive pour lui. Vous verrez le même comportement avec Long.MAX_VALUE.


0

Il y a un correctif à cela dans Java 15 sera une méthode à int et long. Ils seront présents sur les classes

java.lang.Math and java.lang.StrictMath

Les méthodes.

public static int absExact(int a)
public static long absExact(long a)

Si vous réussissez

Integer.MIN_VALUE

OU

Long.MIN_VALUE

Une exception est lancée.

https://bugs.openjdk.java.net/browse/JDK-8241805

Je voudrais voir si Long.MIN_VALUE ou Integer.MIN_VALUE est passé une valeur positive serait return et non une exception mais


-1

Math.abs ne fonctionne pas tout le temps avec de grands nombres J'utilise cette petite logique de code que j'ai apprise quand j'avais 7 ans!

if(Num < 0){
  Num = -(Num);
} 

Qu'y a-t-il sici?
— aioobe

Désolé, j'ai oublié de mettre à jour cela à partir de mon code d'origine
— Dave

Alors, qu'est-ce que cela entraîne si Numégal Integer.MIN_VALUEavant l'extrait de code?
— aioobe
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.