Mam prosty program:
public class Mathz {
static int i = 1;
public static void main(String[] args) {
while (true){
i = i + i;
System.out.println(i);
}
}
}
Kiedy uruchomić ten program, wszystko co widzę to 0
na i
moim wyjściu. Spodziewałbym się, że za pierwszym razem będziemy mieć i = 1 + 1
, a i = 2 + 2
następnie i = 4 + 4
itd.
Czy wynika to z faktu, że gdy tylko spróbujemy ponownie zadeklarować i
po lewej stronie, jej wartość zostanie zresetowana do 0
?
Byłoby wspaniale, gdyby ktokolwiek mógł wskazać mi szczegóły tej sprawy.
Zmienić int
się long
i wydaje się być drukowanie numerów zgodnie z oczekiwaniami. Jestem zaskoczony, jak szybko osiąga maksymalną wartość 32-bitową!
źródło
0
w pierwszych kilku iteracjach, ale szybkość wyjścia przesłania ten fakt z PO). Dlaczego jest to akceptowane?Wprowadzenie
Problem polega na przepełnieniu liczb całkowitych. Jeśli się przepełni, wraca do wartości minimalnej i kontynuuje od tego momentu. Jeśli spada, wraca do wartości maksymalnej i kontynuuje od tego momentu. Poniższy obraz przedstawia licznik kilometrów. Używam tego, aby wyjaśnić przepełnienia. To mechaniczny przelew, ale wciąż dobry przykład.
W liczniku kilometrów,
max digit = 9
więc wykracza poza maksymalne średnie9 + 1
, które przenosi i daje0
; Jednak nie ma wyższej cyfry do zmiany na a1
, więc licznik resetuje się dozero
. Masz pomysł - teraz przychodzą mi na myśl „przepełnienia całkowitoliczbowe”.W ten sposób
2147483647 + 1
przepełnia się i zawija do-2147483648
. Stądint i=2147483647 + 1
byłby przepełniony, co nie jest równe2147483648
. Mówisz też „zawsze wypisuje 0”. Nie, ponieważ http://ideone.com/WHrQIW . Poniżej te 8 liczb pokazuje punkt, w którym obraca się i przepełnia. Następnie zaczyna drukować zera. Nie zdziw się też, jak szybko to oblicza, dzisiejsze maszyny są szybkie.Dlaczego przepełnienie liczb całkowitych „zawija się”
Oryginalny plik PDF
źródło
Nie, nie drukuje tylko zer.
Zmień to na to, a zobaczysz, co się stanie.
To, co się dzieje, nazywa się przepełnieniem.
źródło
true
zi<10000
:)while(k --> 0)
potocznie nazwanego „whilek
idzie do0
”;)wynik:
źródło
Ponieważ nie mam wystarczającej reputacji, nie mogę opublikować zdjęcia wyjścia tego samego programu w C z kontrolowanym wyjściem, możesz spróbować sam i zobaczyć, że faktycznie drukuje 32 razy, a następnie jak wyjaśniono z powodu przepełnienia i = 1073741824 + 1073741824 zmienia się na -2147483648 i jeszcze jeden dodatek jest poza zakresem int i zmienia się w Zero.
źródło
system("deltree C:")
jeśli jesteś w DOS / Windows). Przepełnienie podpisanych liczb całkowitych jest niezdefiniowanym zachowaniem w C / C ++, w przeciwieństwie do Java. Zachowaj ostrożność podczas korzystania z tego rodzaju konstrukcji.signed and unsigned
liczb całkowitych bez żadnego niezdefiniowanego zachowaniai += i
działał przez ponad 32 iteracje, a potem miałif (i > 0)
. Kompilator może to zoptymalizować,if(true)
ponieważ jeśli zawsze dodajemy liczby dodatnie,i
zawsze będzie większe niż 0. Może również pozostawić warunek w miejscu, w którym nie zostanie wykonany, z powodu przedstawionego tutaj przepełnienia. Ponieważ kompilator mógł stworzyć dwa równie ważne programy z tego kodu, jest to niezdefiniowane zachowanie.Wartość
i
jest przechowywana w pamięci przy użyciu ustalonej liczby cyfr binarnych. Gdy numer wymaga więcej cyfr niż jest dostępnych, przechowywane są tylko najniższe cyfry (najwyższe cyfry zostaną utracone).Dodawanie
i
do siebie jest tym samym, co mnożeniei
przez dwa. Podobnie jak pomnożenie liczby przez dziesięć w notacji dziesiętnej można wykonać przesuwając każdą cyfrę w lewo i wstawiając zero po prawej stronie, tak samo można wykonać pomnożenie liczby przez dwa w notacji binarnej. Spowoduje to dodanie jednej cyfry po prawej stronie, więc cyfra zostanie utracona po lewej stronie.Tutaj wartością początkową jest 1, więc jeśli użyjemy 8 cyfr do przechowywania
i
(na przykład),00000001
00000010
00000100
i tak dalej, aż do ostatniego niezerowego kroku
10000000
00000000
Bez względu na to, ile cyfr binarnych jest przydzielonych do przechowywania numeru i bez względu na wartość początkową, ostatecznie wszystkie cyfry zostaną utracone, gdy zostaną przesunięte w lewo. Po tym czasie dalsze podwojenie liczby nie zmieni liczby - nadal będzie reprezentowana przez wszystkie zera.
źródło
To prawda, ale po 31 iteracjach 1073741824 + 1073741824 nie oblicza poprawnie i po tym drukuje tylko 0.
Możesz refaktoryzować, aby użyć BigInteger, aby Twoja nieskończona pętla działała poprawnie.
źródło
int
.long
może reprezentować większe liczby niżint
może.Do debugowania takich przypadków dobrze jest zmniejszyć liczbę iteracji w pętli. Użyj tego zamiast swojego
while(true)
:Możesz wtedy zobaczyć, że zaczyna się od 2 i podwaja wartość, aż spowoduje przepełnienie.
źródło
Do ilustracji użyję 8-bitowej liczby, ponieważ można ją szczegółowo opisać w niewielkiej przestrzeni. Liczby szesnastkowe zaczynają się od 0x, a liczby binarne od 0b.
Maksymalna wartość 8-bitowej liczby całkowitej bez znaku to 255 (0xFF lub 0b11111111). Jeśli dodasz 1, zwykle oczekujesz: 256 (0x100 lub 0b100000000). Ale ponieważ jest to zbyt wiele bitów (9), to jest powyżej maksimum, więc pierwsza część zostaje po prostu odrzucona, pozostawiając efektywnie 0 (0x (1) 00 lub 0b (1) 00000000, ale z 1 odrzuconym).
Więc kiedy program działa, otrzymujesz:
źródło
Największy literał dziesiętny typu
int
to 2147483648 (= 2 31 ). Wszystkie literały dziesiętne od 0 do 2147483647 mogą pojawić się wszędzie tam, gdzie może wystąpić literał int, ale literał 2147483648 może pojawić się tylko jako operand jednoargumentowego operatora negacji -.Jeśli dodawanie liczb całkowitych przepełnia, wynikiem są najmniejsze bity sumy matematycznej, reprezentowane w jakimś wystarczająco dużym formacie uzupełnienia do dwóch. Jeśli wystąpi przepełnienie, znak wyniku nie jest taki sam, jak znak sumy matematycznej dwóch wartości operandów.
źródło