Cóż, jeśli chodzi o prymitywne typy liczb całkowitych, Java wcale nie obsługuje Over / Underflow (dla float i double zachowanie jest inne, będzie spłukiwać do +/- nieskończoności, tak jak nakazuje IEEE-754).
Dodanie dwóch liczb całkowitych nie spowoduje, że nastąpi przepełnienie. Prostą metodą sprawdzenia przepełnienia jest użycie następnego większego typu do faktycznego wykonania operacji i sprawdzenie, czy wynik jest nadal w zakresie dla typu źródłowego:
public int addWithOverflowCheck(int a, int b) {
// the cast of a is required, to make the + work with long precision,
// if we just added (a + b) the addition would use int precision and
// the result would be cast to long afterwards!
long result = ((long) a) + b;
if (result > Integer.MAX_VALUE) {
throw new RuntimeException("Overflow occured");
} else if (result < Integer.MIN_VALUE) {
throw new RuntimeException("Underflow occured");
}
// at this point we can safely cast back to int, we checked before
// that the value will be withing int's limits
return (int) result;
}
To, co byś zrobił zamiast klauzul dotyczących rzucania, zależy od wymagań aplikacji (rzucaj, spłucz do min / maks lub po prostu rejestruj cokolwiek). Jeśli chcesz wykryć przepełnienie przy długich operacjach, nie masz szczęścia z prymitywami, zamiast tego użyj BigInteger.
Edycja (2014-05-21): Ponieważ wydaje się, że do tego pytania często się powołuje i sam musiałem rozwiązać ten sam problem, dość łatwo jest ocenić stan przepełnienia tą samą metodą, którą CPU obliczy swoją flagę V.
Jest to w zasadzie wyrażenie boolowskie, które obejmuje znak zarówno operandów, jak i wynik:
/**
* Add two int's with overflow detection (r = s + d)
*/
public static int add(final int s, final int d) throws ArithmeticException {
int r = s + d;
if (((s & d & ~r) | (~s & ~d & r)) < 0)
throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");
return r;
}
W Javie łatwiej jest zastosować wyrażenie (w if) do całych 32 bitów i sprawdzić wynik, używając <0 (to skutecznie przetestuje bit znaku). Zasada działa dokładnie tak samo dla wszystkich typów pierwotnych liczb całkowitych , zmiana wszystkich deklaracji w powyższej metodzie na długą powoduje, że działa ona długo.
W przypadku mniejszych typów, ze względu na niejawną konwersję na int (szczegóły w JLS dla operacji bitowych), zamiast sprawdzania <0, czek musi jawnie maskować bit znaku (0x8000 dla krótkich argumentów, 0x80 dla bajtów, dostosuj rzutowania i deklaracja parametru odpowiednio):
/**
* Subtract two short's with overflow detection (r = d - s)
*/
public static short sub(final short d, final short s) throws ArithmeticException {
int r = d - s;
if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
return (short) r;
}
(Należy zauważyć, że powyższy przykład wykorzystuje potrzebę ekspresji dla odjąć wykrywania przelewowego)
Jak więc / dlaczego działają te wyrażenia logiczne? Po pierwsze, niektóre logiczne myślenie ujawnia, że przepełnienie może wystąpić tylko wtedy, gdy znaki obu argumentów są takie same. Ponieważ, jeśli jeden argument jest ujemny, a jeden dodatni, wynik (dodawania) musi być bliższy zeru, lub w skrajnym przypadku jeden argument jest zerowy, taki sam jak drugi argument. Ponieważ same argumenty nie mogą stworzyć warunku przepełnienia, ich suma nie może również spowodować przepełnienia.
Co się stanie, jeśli oba argumenty będą miały ten sam znak? Przyjrzyjmy się, że oba są dodatnie: dodanie dwóch argumentów, które tworzą sumę większą niż typy MAX_VALUE, zawsze da wartość ujemną, więc przepełnienie występuje, jeśli arg1 + arg2> MAX_VALUE. Teraz maksymalna wartość, która mogłaby wynikać, to MAX_VALUE + MAX_VALUE (skrajny przypadek obu argumentów to MAX_VALUE). W przypadku bajtu (przykład) oznaczałoby to 127 + 127 = 254. Patrząc na reprezentacje bitowe wszystkich wartości, które mogą wynikać z dodania dwóch wartości dodatnich, można stwierdzić, że wszystkie te, które przepełniają (128 do 254), mają ustawiony bit 7, podczas gdy wszystkie nie przepełnione (od 0 do 127) mają wyczyszczony bit 7 (najwyższy, znak). Dokładnie to sprawdza pierwsza (prawa) część wyrażenia:
if (((s & d & ~r) | (~s & ~d & r)) < 0)
(~ s & ~ d & r) staje się prawdą, tylko jeśli oba operandy (s, d) są dodatnie, a wynik (r) jest ujemny (wyrażenie działa na wszystkich 32 bitach, ale jedynym bitem, który nas interesuje to najwyższy bit (znak), który jest sprawdzany przez <0).
Teraz, jeśli oba argumenty są ujemne, ich suma nigdy nie może być bliższa zeru niż którykolwiek z argumentów, suma musi być bliższa minus nieskończoności. Najbardziej ekstremalną wartością, jaką możemy wytworzyć, jest MIN_VALUE + MIN_VALUE, co (ponownie dla przykładu bajtu) pokazuje, że dla dowolnej wartości z zakresu (-1 do -128) bit znaku jest ustawiony, a każda możliwa przepełniona wartość (-129 do -256 ) ma wyczyszczony bit znaku. Zatem znak wyniku ponownie pokazuje stan przepełnienia. To właśnie sprawdza lewa połowa (s & d & ~ r) w przypadku, gdy oba argumenty (s, d) są ujemne, a wynik jest dodatni. Logika jest w dużej mierze równoważna z przypadkiem pozytywnym; wszystkie wzorce bitowe, które mogą wynikać z dodania dwóch wartości ujemnych, będą wyczyszczone bity znaku, jeśli i tylko w przypadku wystąpienia niedopełnienia.