Math.abs zwraca nieprawidłową wartość Integer.Min_VALUE

90

Ten kod:

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

Zwroty -2147483648

Czy nie powinien zwracać wartości bezwzględnej jako 2147483648?

user665319
źródło

Odpowiedzi:

102

Integer.MIN_VALUEjest -2147483648, ale najwyższa wartość, jaką może zawierać 32-bitowa liczba całkowita, to +2147483647. Próba przedstawienia +2147483648w 32-bitowej liczbie int spowoduje efektywne przeniesienie na -2147483648. Dzieje się tak, ponieważ w przypadku używania liczb całkowitych ze znakiem, binarne reprezentacje dopełnienia do dwóch +2147483648i -2147483648są identyczne. Nie stanowi to jednak problemu, ponieważ +2147483648jest uważane za poza zasięgiem.

Aby uzyskać więcej informacji na ten temat, możesz zapoznać się z artykułem w Wikipedii o uzupełnieniu do dwóch .

jonmorgan
źródło
6
Cóż, nie jest problemem niedocenianie wpływu, może to oznaczać problemy. Osobiście wolałbym mieć wyjątek lub system liczbowy, który rozwija się dynamicznie w języku wyższego poziomu.
Maarten Bodewes
40

Zachowanie, które wskazałeś, jest rzeczywiście sprzeczne z intuicją. Jednak to zachowanie jest określone przez javadoc dlaMath.abs(int) :

Jeśli argument nie jest ujemny, zwracany jest argument. Jeśli argument jest ujemny, zwracana jest negacja argumentu.

Oznacza to, że Math.abs(int)powinien zachowywać się jak następujący kod Java:

public static int abs(int x){
    if (x >= 0) {
        return x;
    }
    return -x;
}

Oznacza to, że w negatywnym przypadku -x.

Zgodnie z sekcją JLS 15.15.4 , -xjest równe (~x)+1, gdzie ~jest operatorem dopełniacza bitowego.

Aby sprawdzić, czy to brzmi dobrze, weźmy -1 jako przykład.

Wartość całkowitą -1można zapisać 0xFFFFFFFFw postaci szesnastkowej w Javie (sprawdź to za pomocą printlnlub innej metody). Biorąc w -(-1)ten sposób daje:

-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1

Więc to działa.

Spróbujmy teraz z Integer.MIN_VALUE. Wiedząc, że najniższą liczbę całkowitą można przedstawić za pomocą 0x80000000, czyli pierwszego bitu ustawionego na 1, a pozostałych 31 bitów ustawionych na 0, otrzymujemy:

-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1 
                     = 0x80000000 = Integer.MIN_VALUE

I dlatego Math.abs(Integer.MIN_VALUE)wraca Integer.MIN_VALUE. Zauważ też, że 0x7FFFFFFFjest Integer.MAX_VALUE.

To powiedziawszy, w jaki sposób możemy uniknąć problemów z powodu tej sprzecznej z intuicją wartości zwracanej w przyszłości?

  • Moglibyśmy, jak podkreślił @Bombe , oddanych nasze ints do longwcześniej. My jednak musimy

    • wrzuć je z powrotem do ints, co nie działa, ponieważ Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Lub kontynuuj longs z nadzieją, że nigdy nie zadzwonimy Math.abs(long)z wartością równą Long.MIN_VALUE, ponieważ też mamy Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Możemy używać BigIntegers wszędzie, ponieważ BigInteger.abs()rzeczywiście zawsze zwraca wartość dodatnią. To dobra alternatywa, choć trochę wolniejsza niż manipulowanie surowymi typami liczb całkowitych.

  • Możemy napisać własne opakowanie Math.abs(int), na przykład:

/**
 * 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);
}
  • Użyj liczby całkowitej bitowej AND, aby wyczyścić wysoki bit, upewniając się, że wynik jest nieujemny: int positive = value & Integer.MAX_VALUE(zasadniczo przepełnia od Integer.MAX_VALUEdo 0zamiast Integer.MIN_VALUE)

Na koniec wydaje się, że problem ten jest znany od jakiegoś czasu. Zobacz na przykład ten wpis o odpowiedniej regule findbugs .

Bernard Paulus
źródło
12

Oto co mówi dokument Java dla Math.abs () w javadoc :

Zwróć uwagę, że jeśli argument jest równy wartości Integer.MIN_VALUE, czyli najbardziej ujemnej wartości typu int, wynikiem jest ta sama wartość, która jest ujemna.

moe
źródło
4

Aby zobaczyć oczekiwany wynik, rzuć Integer.MIN_VALUEna long:

System.out.println(Math.abs((long) Integer.MIN_VALUE));
Bombe
źródło
1
Rzeczywiście możliwa poprawka! Nie rozwiązuje to jednak faktu, że Math.absjest to sprzeczne z intuicją, zwracając liczbę ujemną:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard paulus
1
@bernardpaulus, cóż, co ma robić oprócz rzucania ArithmeticException? Ponadto zachowanie jest jasno udokumentowane w dokumentacji API.
Bombe
nie ma dobrej odpowiedzi na twoje pytanie ... Chciałem tylko zwrócić uwagę, że to zachowanie, które jest źródłem błędów, nie jest naprawiane przez użycie Math.abs(long). Przepraszam za mój błąd tutaj: myślałem, że zaproponowałeś użycie programu Math.abs(long)jako poprawki, kiedy pokazałeś go jako prosty sposób na „zobaczenie wyniku, którego oczekuje pytający”. Przepraszam.
bernard paulus
W Javie 15 z nowymi metodami w rzeczywistości generowany jest wyjątek.
chiperortiz
1

2147483648 nie może być przechowywany jako liczba całkowita w Javie, jego reprezentacja binarna jest taka sama jak -2147483648.

ymajoros
źródło
0

Ale (int) 2147483648L == -2147483648 jest jedna liczba ujemna, która nie ma dodatniego odpowiednika, więc nie ma dla niej wartości dodatniej. Zobaczysz to samo zachowanie w przypadku Long.MAX_VALUE.

Peter Lawrey
źródło
0

Jest to poprawka w Javie 15 będzie metodą int i long. Będą obecni na zajęciach

java.lang.Math and java.lang.StrictMath

Metody.

public static int absExact(int a)
public static long absExact(long a)

Jeśli zdasz

Integer.MIN_VALUE

LUB

Long.MIN_VALUE

Zostaje zgłoszony wyjątek.

https://bugs.openjdk.java.net/browse/JDK-8241805

Chciałbym sprawdzić, czy zostanie przekazana wartość Long.MIN_VALUE lub Integer.MIN_VALUE, a zwrócona zostanie wartość dodatnia, a nie wyjątek, ale.

chiperortiz
źródło
-1

Math.abs nie działa cały czas z dużymi liczbami. Używam tej małej logiki kodu, której nauczyłem się, gdy miałem 7 lat!

if(Num < 0){
  Num = -(Num);
} 
Dave
źródło
Co stu jest ?
aioobe
Przepraszam, że zapomniałem zaktualizować to z mojego oryginalnego kodu
Dave
Do czego to prowadzi, jeśli Numjest równe Integer.MIN_VALUEprzed fragmentem kodu?
aioobe