Math.abs restituisce un valore errato per Integer.Min_VALUE


92

Questo codice:

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

ritorna -2147483648

Non dovrebbe restituire il valore assoluto come 2147483648?

Risposte:


103

Integer.MIN_VALUEè -2147483648, ma il valore più alto che un intero a 32 bit può contenere è +2147483647. Il tentativo di rappresentare +2147483648in 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 +2147483648e -2147483648sono 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 .


6
Ebbene, non è un problema sottovalutare l'impatto, potrebbe benissimo significare problemi. Personalmente preferirei avere un'eccezione o un sistema numerico che cresce dinamicamente in un linguaggio di livello superiore.
Maarten Bodewes

41

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 -1può essere annotato come 0xFFFFFFFFin esadecimale in Java (verificalo con printlnao 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 inta longprima. Tuttavia, anche noi dobbiamo

    • rigettali in ints, che non funziona perché Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • O continuare con longs 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 BigIntegers 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);
}
  • Usa un intero bit per bit AND per cancellare il bit alto, assicurandoti che il risultato non sia negativo: int positive = value & Integer.MAX_VALUE(essenzialmente traboccante da Integer.MAX_VALUEa 0invece di Integer.MIN_VALUE)

Come nota finale, questo problema sembra essere noto da tempo. Vedi ad esempio questa voce sulla corrispondente regola findbugs .


12

Ecco cosa dice il documento Java per Math.abs () in javadoc :

Notare che se l'argomento è uguale al valore di Integer.MIN_VALUE, il valore int rappresentabile più negativo, il risultato è lo stesso valore, che è negativo.


4

Per vedere il risultato che ti aspetti, trasmetti Integer.MIN_VALUEa long:

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

1
Una possibile soluzione, anzi! Tuttavia, questo non risolve il fatto che Math.abssia controintuitivo restituendo un numero negativo:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard paulus

1
@bernardpaulus, beh, cosa dovrebbe fare, oltre a lanciare un ArithmeticException? Inoltre, il comportamento è chiaramente documentato nella documentazione API.
Bombe

non c'è una buona risposta alla tua domanda ... Volevo solo far notare che questo comportamento, che è fonte di bug, non viene risolto dall'uso di 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.
bernard paulus

In Java 15 con i nuovi metodi infatti viene lanciata un'eccezione.
chiperortiz

1

2147483648 non può essere memorizzato in un numero intero in java, la sua rappresentazione binaria è la stessa di -2147483648.


0

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.


0

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.


-1

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);
} 

Cosa c'è squi?
aioobe

Scusa se ho dimenticato di aggiornarlo dal mio codice originale
Dave

Quindi in cosa si traduce se è NumugualeInteger.MIN_VALUE prima dello snippet?
aioobe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.