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.