Ce code:
System.out.println(Math.abs(Integer.MIN_VALUE));
Retour -2147483648
S'il ne renvoie pas la valeur absolue comme 2147483648
?
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 .
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 int
s en long
auparavant. Nous devons cependant
int
s, ce qui ne fonctionne pas car Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)
.long
s 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 BigInteger
s 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);
}
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 .
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.
Pour voir le résultat attendu, exécutez Integer.MIN_VALUE
à long
:
System.out.println(Math.abs((long) Integer.MIN_VALUE));
2147483648 ne peut pas être stocké dans un entier en Java, sa représentation binaire est la même que -2147483648.
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.