Ce code:
System.out.println(Math.abs(Integer.MIN_VALUE));
Retour -2147483648
Ne devrait-il pas renvoyer la valeur absolue comme 2147483648
?
Ce code:
System.out.println(Math.abs(Integer.MIN_VALUE));
Retour -2147483648
Ne devrait-il pas renvoyer la valeur absolue comme 2147483648
?
Réponses:
Integer.MIN_VALUE
est -2147483648
, mais la valeur la plus élevée qu'un entier 32 bits peut contenir est +2147483647
. Tenter de représenter +2147483648
dans un int 32 bits "retournera" effectivement à -2147483648
. En effet, lorsque vous utilisez des entiers signés, les représentations binaires complémentaires à deux de +2147483648
et -2147483648
sont identiques. Ce n'est cependant pas un problème, car il +2147483648
est 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 .
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 -x
est é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 -1
est peut être notée comme 0xFFFFFFFF
en hexadécimal en Java (vérifiez cela avec une println
ou 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 0x7FFFFFFF
c'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 int
s long
avant. Cependant, nous devons soit
int
s, ce qui ne fonctionne pas car
Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)
.long
espé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 BigInteger
s 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);
}
int positive = value & Integer.MAX_VALUE
(débordant essentiellement de Integer.MAX_VALUE
à 0
au 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 .
Pour voir le résultat que vous attendez, effectuez un cast Integer.MIN_VALUE
en long
:
System.out.println(Math.abs((long) Integer.MIN_VALUE));
Math.abs
est contre-intuitif en renvoyant un nombre négatif:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
ArithmeticException
? En outre, le comportement est clairement documenté dans la documentation de l'API.
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.
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.
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
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);
}
s
ici?
Num
égal Integer.MIN_VALUE
avant l'extrait de code?