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?

user665319
la source

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 .

Jonmorgan
la source
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 .

bernard paulus
la source
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.

moe
la source
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));
Bombe
la source
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.

ymajoros
la source
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.

Peter Lawrey
la source
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

chiperortiz
la source
-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);
} 
Dave
la source
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