Jak Java radzi sobie z niedopełnieniami i przepełnieniami liczb całkowitych?
W następnej kolejności, jak byś sprawdził / przetestował, że to się dzieje?
java
integer
integer-overflow
KushalP
źródło
źródło
checked
ile wiem. Nie widzę, aby był używany zbyt często, a pisaniechecked { code; }
to tyle samo pracy, co wywoływanie metody.csc /checked ...
lub ustaw właściwość w panelu właściwości projektu w Visual Studio.Odpowiedzi:
Jeśli się przepełni, wraca do wartości minimalnej i kontynuuje od tego momentu . Jeśli się przepełni, wraca do wartości maksymalnej i kontynuuje od tego momentu .
Możesz to wcześniej sprawdzić w następujący sposób:
(możesz zastąpić
int
przez,long
aby wykonać te same kontrolelong
)Jeśli uważasz, że może się to zdarzyć częściej niż często, rozważ użycie typu danych lub obiektu, który może przechowywać większe wartości, np . A
long
możejava.math.BigInteger
. Ostatnia nie przepełnia się, praktycznie dostępna pamięć JVM jest limitem.Jeśli zdarzyło ci się już korzystać z Java8, możesz skorzystać z nowych
Math#addExact()
iMath#subtractExact()
metod, które spowodująArithmeticException
przepełnienie.Kod źródłowy można znaleźć odpowiednio tutaj i tutaj .
Oczywiście możesz też od razu ich użyć zamiast ukrywać je w
boolean
metodzie użytecznej.źródło
+100, -100
odpowiednio. Jeśli dodajesz jedną do liczby całkowitej Java, proces wyglądałby tak, jak się przepełnił.98, 99, 100, -100, -99, -98, ...
. Czy to ma większy sens?Math#addExact
to składnia normalnie używana podczas pisania javadoców - podczas gdy normalnie zostanie to przekonwertowaneMath.addExact
, czasem inna forma po prostu zostajeIf it underflows, it goes back to the maximum value and continues from there.
- wydaje się, że pomyliłeś niedopełnienie z przepełnieniem ujemnym. niedomiar liczb całkowitych zdarza się cały czas (gdy wynikiem jest ułamek).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:
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:
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):
(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:
(~ 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.
źródło
Domyślnie int i długa matematyka Javy po cichu owija się przy przepełnieniu i niedopełnieniu. (Operacje na liczbach całkowitych na innych typach liczb całkowitych są wykonywane przez promowanie operandów jako int lub long, zgodnie z JLS 4.2.2 .)
Jak Java 8,
java.lang.Math
zapewniaaddExact
,subtractExact
,multiplyExact
,incrementExact
,decrementExact
inegateExact
statyczne metody zarówno dla int i długich argumentów, które wykonują pracę w nazwie, rzucając ArithmeticException na przepełnienie. (Nie ma metody divideExact - sam musisz sprawdzić jeden specjalny przypadek (MIN_VALUE / -1
).)Począwszy od Java 8, java.lang.Math zapewnia także
toIntExact
rzutowanie wartości int na wartość int, rzucając ArithmeticException, jeśli wartość parametru long nie mieści się w wartości int. Może to być przydatne np. Do obliczenia sumy liczb całkowitych przy użyciu niezaznaczonej długiej matematyki, a następnie użyciatoIntExact
rzutowania na liczbę całkowitą na końcu (ale uważaj, aby nie przelać sumy).Jeśli nadal używasz starszej wersji Java, Google Guava zapewnia statyczne metody IntMath i LongMath do sprawdzania dodawania, odejmowania, mnożenia i potęgowania (wyrzucanie przy przepełnieniu). Klasy te zapewniają również metody obliczania silni i współczynników dwumianowych, które zwracają się
MAX_VALUE
po przepełnieniu (co jest mniej wygodne do sprawdzenia). Pierwotne użytecznych klas guawy, wSignedBytes
,UnsignedBytes
,Shorts
iInts
dostarczającheckedCast
metody zwężenie większych typów (rzucania IllegalArgumentException na mocy / przelewem, nie ArithmeticException), jak równieżsaturatingCast
metody powrotneMIN_VALUE
lubMAX_VALUE
na przepełnienie.źródło
Java nie robi nic z przepełnieniem liczb całkowitych dla typów pierwotnych int lub długich i ignoruje przepełnienie dodatnimi i ujemnymi liczbami całkowitymi.
Ta odpowiedź najpierw opisuje przepełnienie liczb całkowitych, podaje przykład, jak to może się zdarzyć, nawet przy wartościach pośrednich w ocenie wyrażenia, a następnie podaje linki do zasobów, które zapewniają szczegółowe techniki zapobiegania przepełnieniu liczb całkowitych i wykrywania ich.
Arytmetyka liczb całkowitych i wyrażenia będące wynikiem nieoczekiwanego lub niewykrytego przepełnienia są częstym błędem programowania. Nieoczekiwane lub niewykryte przepełnienie liczb całkowitych jest również dobrze znanym problemem bezpieczeństwa, który można wykorzystać, zwłaszcza, że wpływa na obiekty tablic, stosów i list.
Przepełnienie może wystąpić w kierunku dodatnim lub ujemnym, gdzie wartość dodatnia lub ujemna byłaby wyższa niż wartości maksymalne lub minimalne dla danego typu pierwotnego. Przepełnienie może wystąpić w wartości pośredniej podczas oceny wyrażenia lub operacji i wpłynąć na wynik wyrażenia lub operacji, w przypadku gdy oczekiwana wartość końcowa będzie w zakresie.
Czasami przepełnienie ujemne jest błędnie nazywane niedopełnieniem. Niedomiar występuje wtedy, gdy wartość byłaby bliższa zeru, niż pozwala na to reprezentacja. Niedomiar występuje w arytmetyce liczb całkowitych i jest oczekiwany. Niedopełnienie liczb całkowitych występuje, gdy ocena liczb całkowitych będzie wynosić od -1 do 0 lub 0 i 1. To, co będzie ułamkowym wynikiem, zostanie obcięte do 0. Jest to normalne i oczekiwane z arytmetyką liczb całkowitych i nie jest uważane za błąd. Może to jednak prowadzić do zgłoszenia wyjątku przez kod. Jednym z przykładów jest wyjątek „ArithmeticException: / by zero”, jeśli wynik niedopełnienia liczby całkowitej jest używany jako dzielnik w wyrażeniu.
Rozważ następujący kod:
co powoduje, że x ma przypisane 0, a następnie ocena bigValue / x zgłasza wyjątek, „ArithmeticException: / by zero” (tzn. dzielenie przez zero), zamiast y przypisano wartość 2.
Oczekiwany wynik dla x wyniósłby 858 993 458, czyli mniej niż maksymalna wartość int wynosząca 2 147 483 647. Jednak wynik pośredni z oceny liczby całkowitej.MAX_Value * 2 wyniósłby 4 294 967 294, co przekracza maksymalną wartość całkowitą i wynosi -2 zgodnie z reprezentacjami liczb całkowitych dopełniających 2s. Kolejna ocena -2 / 5 daje wynik 0, który zostaje przypisany do x.
Zmiana kolejności wyrażenia do obliczenia x na wyrażenie, które po obliczeniu dzieli się przed pomnożeniem, następujący kod:
skutkuje przypisaniem x 858,993,458 iy przypisaniem 2, co jest oczekiwane.
Wynik pośredni z bigValue / 5 wynosi 429,496,729, co nie przekracza maksymalnej wartości int. Późniejsza ocena 429,496,729 * 2 nie przekracza maksymalnej wartości int, a oczekiwany wynik zostaje przypisany do x. Ocena dla y nie dzieli się przez zero. Oceny dla xiy działają zgodnie z oczekiwaniami.
Wartości całkowite Java są przechowywane jako i zachowują się zgodnie z uzupełnieniami 2-częściowymi podpisanymi reprezentacjami liczb całkowitych. Gdy wynikowa wartość byłaby większa lub mniejsza od maksymalnej lub minimalnej wartości całkowitej, zamiast tego otrzymywana jest wartość całkowita 2 z uzupełnieniem. W sytuacjach, które nie zostały wyraźnie zaprojektowane do użycia zachowania uzupełnienia 2s, co jest najczęstszą sytuacją arytmetyczną na liczbach całkowitych, wynikowa wartość uzupełnienia 2s spowoduje błąd logiki programowania lub błąd obliczeniowy, jak pokazano w powyższym przykładzie. Doskonały artykuł w Wikipedii opisuje komplementarne liczby całkowite 2s tutaj: Uzupełnienie dwóch - Wikipedia
Istnieją techniki pozwalające uniknąć niezamierzonego przepełnienia liczb całkowitych. Techniki można sklasyfikować jako wykorzystujące testy warunków wstępnych, upcasting i BigInteger.
Testowanie warunków wstępnych polega na sprawdzeniu wartości przechodzących w operację lub wyrażenie arytmetyczne, aby upewnić się, że nie nastąpi przepełnienie tymi wartościami. Programowanie i projektowanie będą musiały utworzyć testy, które zapewnią, że wartości wejściowe nie spowodują przepełnienia, a następnie określą, co zrobić, jeśli wystąpią wartości wejściowe, które spowodują przepełnienie.
Upcasting obejmuje użycie większego typu pierwotnego do wykonania operacji arytmetycznej lub wyrażenia, a następnie ustalenie, czy wynikowa wartość przekracza wartości maksymalne lub minimalne dla liczby całkowitej. Nawet w przypadku upcastingu nadal możliwe jest, że wartość lub jakaś wartość pośrednia w operacji lub wyrażeniu będzie przekraczać maksymalne lub minimalne wartości dla typu upcast i spowoduje przepełnienie, które również nie zostanie wykryte i spowoduje nieoczekiwane i niepożądane wyniki. Dzięki analizie lub warunkom wstępnym może być możliwe zapobieganie przepełnieniu dzięki upcastingowi, gdy zapobieganie bez upcastingu nie jest możliwe lub praktyczne. Jeśli liczby całkowite są już długimi typami pierwotnymi, upcasting nie jest możliwy w przypadku typów pierwotnych w Javie.
Technika BigInteger polega na użyciu BigInteger do operacji arytmetycznej lub wyrażenia przy użyciu metod bibliotecznych wykorzystujących BigInteger. BigInteger nie przepełnia się. W razie potrzeby wykorzysta całą dostępną pamięć. Jego metody arytmetyczne są zwykle tylko nieco mniej wydajne niż operacje na liczbach całkowitych. Nadal możliwe jest, że wynik wykorzystujący BigInteger może przekraczać wartości maksymalne lub minimalne dla liczby całkowitej, jednak przepełnienie nie wystąpi w arytmetyce prowadzącej do wyniku. Programowanie i projektowanie będą nadal musiały określić, co zrobić, jeśli wynik BigInteger przekroczy maksymalne lub minimalne wartości dla pożądanego pierwotnego typu wyniku, np. Int lub long.
Program CERT Carnegie Mellon Software Engineering Institute i Oracle stworzyły zestaw standardów bezpiecznego programowania Java. Standardy obejmują techniki zapobiegania i wykrywania przepełnienia liczb całkowitych. Standard został opublikowany tutaj jako ogólnodostępny zasób online: CERT Oracle Secure Coding Standard for Java
Sekcja standardu, która opisuje i zawiera praktyczne przykłady technik kodowania w celu zapobiegania lub wykrywania przepełnienia liczb całkowitych, jest tutaj: NUM00-J. Wykryj lub zapobiegaj przepełnieniu liczb całkowitych
Dostępne są również formularze książkowe i PDF w CERT Oracle Secure Coding Standard for Java.
źródło
Po tym, jak sam wpadłem na ten problem, oto moje rozwiązanie (zarówno dla mnożenia, jak i dodawania):
nie krępuj się poprawić, jeśli jest źle lub jeśli można to uprościć. Przeprowadziłem pewne testy z wykorzystaniem metody mnożenia, głównie przypadków na krawędziach, ale nadal może być źle.
źródło
int*int
, to myślę, że po prostu rzucając sięlong
i zobaczyć, czy pasuje wynik wint
byłby najszybszy podejście. Bolong*long
jeśli znormalizujemy argumenty jako dodatnie, możemy podzielić każdą na górną i dolną 32-bitową połówkę, wypromować każdą połowę do długiej (uważaj na przedłużenia znaku!), A następnie obliczyć dwa częściowe produkty [jedna z górnych połówek powinna być zero].Istnieją biblioteki, które zapewniają bezpieczne operacje arytmetyczne, które sprawdzają przepełnienie / niedopełnienie liczb całkowitych. Na przykład GuMa IntMath.checkedAdd (int a, int b) zwraca sumę
a
ib
, pod warunkiem, że się nie przepełnia, i wyrzuca,ArithmeticException
jeślia + b
przepełnia sięint
arytmetyką ze znakiem.źródło
Math
klasa zawiera podobny kod.Owija się wokół.
na przykład:
odciski
źródło
Myślę, że powinieneś użyć czegoś takiego i nazywa się to Upcasting:
Możesz przeczytać więcej tutaj: Wykryj lub zapobiegaj przepełnieniu liczb całkowitych
Jest to dość wiarygodne źródło.
źródło
Nic nie robi - po prostu zdarza się niedopełnienie / przepełnienie.
„-1”, które jest wynikiem przepełnionego obliczenia, nie różni się od „-1” wynikającego z innych informacji. Nie możesz więc stwierdzić za pomocą jakiegoś statusu lub sprawdzając tylko wartość, czy jest przepełniona.
Ale możesz być mądry w swoich obliczeniach, aby uniknąć przepełnienia, jeśli ma to znaczenie, lub przynajmniej wiedzieć, kiedy to nastąpi. Jaka jest twoja sytuacja
źródło
źródło
Myślę, że powinno być dobrze.
źródło
Jest jeden przypadek, o którym nie wspomniano powyżej:
będzie produkować:
Przypadek ten omówiono tutaj: Przepełnienie liczb całkowitych powoduje wyzerowanie.
źródło