web-dev-qa-db-fra.com

Math.abs renvoie une valeur incorrecte pour Integer.Min_VALUE

Ce code:

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

Retour -2147483648

S'il ne renvoie pas la valeur absolue comme 2147483648?

76
user665319

Integer.MIN_VALUE est -2147483648, mais la valeur la plus élevée qu'un entier 32 bits puisse contenir est +2147483647. Tentative de représenter +2147483648 dans un int 32 bits "basculera" effectivement vers -2147483648. En effet, lors de l'utilisation d'entiers signés, les deux représentations binaires complémentaires de +2147483648 et -2147483648 sont identiques. Ce n'est cependant pas un problème, car +2147483648 est considéré comme hors limites.

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

83
jonmorgan

Le comportement que vous signalez est en effet contre-intuitif. Cependant, ce comportement est celui spécifié par le javadoc pour Math.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 suit Java code:

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

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

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

Pour vérifier si cela sonne bien, prenons -1 comme exemple.

La valeur entière -1 Est notée comme 0xFFFFFFFF En hexadécimal dans Java (vérifiez cela avec un println ou toute autre méthode) La prise de -(-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 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) 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?

  • Nous pourrions, comme indiqué par @Bombe , convertir nos ints en long auparavant. Nous devons cependant

    • les reconstituer dans ints, ce qui ne fonctionne pas car Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Ou continuez avec longs en espérant que nous n'appellerons jamais Math.abs(long) avec une valeur égale à Long.MIN_VALUE, Car nous avons également Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Nous pouvons utiliser BigIntegers partout, car BigInteger.abs() renvoie en effet toujours une valeur positive. C'est une bonne alternative, difficile 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 bit à bit ET 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 Vers 0 Au lieu de Integer.MIN_VALUE)

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

32
bernard paulus

Voici ce que Java doc dit 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 cette même valeur, qui est négative.

11
moe

Pour voir le résultat attendu, exécutez Integer.MIN_VALUE à long:

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

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

0
ymajoros

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

0
Peter Lawrey