Questo codice:
System.out.println(Math.abs(Integer.MIN_VALUE));
ritorna -2147483648
Non dovrebbe restituire il valore assoluto come 2147483648
?
Questo codice:
System.out.println(Math.abs(Integer.MIN_VALUE));
ritorna -2147483648
Non dovrebbe restituire il valore assoluto come 2147483648
?
Risposte:
Integer.MIN_VALUE
è -2147483648
, ma il valore più alto che un intero a 32 bit può contenere è +2147483647
. Il tentativo di rappresentare +2147483648
in un int a 32 bit verrà effettivamente "rollover" a -2147483648
. Questo perché, quando si usano interi con segno, le rappresentazioni binarie del complemento a due di +2147483648
e -2147483648
sono identiche. Questo non è un problema, tuttavia, poiché +2147483648
è considerato fuori portata.
Per ulteriori informazioni su questo argomento, potresti dare un'occhiata all'articolo di Wikipedia sul complemento di due .
Il comportamento che fai notare è davvero controintuitivo. Tuttavia, questo comportamento è quello specificato da javadoc perMath.abs(int)
:
Se l'argomento non è negativo, viene restituito l'argomento. Se l'argomento è negativo, viene restituita la negazione dell'argomento.
Cioè, Math.abs(int)
dovrebbe comportarsi come il seguente codice Java:
public static int abs(int x){
if (x >= 0) {
return x;
}
return -x;
}
Cioè, in caso negativo, -x
.
Secondo la sezione JLS 15.15.4 , -x
è uguale a (~x)+1
, dove ~
è l'operatore di complemento bit per bit.
Per verificare se suona bene, prendiamo -1 come esempio.
Il valore intero -1
può essere annotato come 0xFFFFFFFF
in esadecimale in Java (verificalo con println
ao qualsiasi altro metodo). Prendere -(-1)
così dà:
-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1
Quindi funziona.
Proviamo ora con Integer.MIN_VALUE
. Sapendo che il numero intero più basso può essere rappresentato 0x80000000
, cioè, il primo bit impostato a 1 e i 31 bit rimanenti impostati a 0, abbiamo:
-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1
= 0x80000000 = Integer.MIN_VALUE
Ed è per questo che Math.abs(Integer.MIN_VALUE)
ritorna Integer.MIN_VALUE
. Nota anche che lo 0x7FFFFFFF
è Integer.MAX_VALUE
.
Detto questo, come possiamo evitare i problemi dovuti a questo valore di ritorno controintuitivo in futuro?
Potremmo, come sottolineato da @Bombe , trasmettere i nostri messaggi int
a long
prima. Tuttavia, anche noi dobbiamo
int
s, che non funziona perché
Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)
.long
s in qualche modo sperando che non chiameremo mai Math.abs(long)
con un valore uguale a Long.MIN_VALUE
, dato che anche noi abbiamo Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
.Possiamo usare BigInteger
s ovunque, perché in BigInteger.abs()
effetti restituisce sempre un valore positivo. Questa è una buona alternativa, anche se un po 'più lenta rispetto alla manipolazione dei tipi interi grezzi.
Possiamo scrivere il nostro wrapper per Math.abs(int)
, in questo modo:
/**
* 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
(essenzialmente traboccante da Integer.MAX_VALUE
a 0
invece di Integer.MIN_VALUE
)Come nota finale, questo problema sembra essere noto da tempo. Vedi ad esempio questa voce sulla corrispondente regola findbugs .
Per vedere il risultato che ti aspetti, trasmetti Integer.MIN_VALUE
a long
:
System.out.println(Math.abs((long) Integer.MIN_VALUE));
Math.abs
sia controintuitivo restituendo un numero negativo:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
ArithmeticException
? Inoltre, il comportamento è chiaramente documentato nella documentazione API.
Math.abs(long)
. Mi scuso per il mio errore qui: pensavo che avessi proposto l'uso di Math.abs(long)
come soluzione, quando lo hai mostrato come un modo semplice per "vedere il risultato che il richiedente si aspetta". Scusate.
Ma (int) 2147483648L == -2147483648
esiste un numero negativo che non ha un equivalente positivo, quindi non ha un valore positivo. Vedrai lo stesso comportamento con Long.MAX_VALUE.
C'è una soluzione a questo in Java 15 sarà un metodo per int e long. Saranno presenti sulle classi
java.lang.Math and java.lang.StrictMath
I metodi.
public static int absExact(int a)
public static long absExact(long a)
Se passi
Integer.MIN_VALUE
O
Long.MIN_VALUE
Viene generata un'eccezione.
https://bugs.openjdk.java.net/browse/JDK-8241805
Vorrei vedere se Long.MIN_VALUE o Integer.MIN_VALUE viene passato un valore positivo sarebbe restituito e non un'eccezione ma.
Math.abs non funziona sempre con numeri grandi. Uso questa piccola logica del codice che ho imparato quando avevo 7 anni!
if(Num < 0){
Num = -(Num);
}
s
qui?
Num
ugualeInteger.MIN_VALUE
prima dello snippet?