Zašto celi brojevi iznenađuju ljude
Mašinski ceo broj ima fiksan broj bitova, pa svaka operacija može da proizvede vrednost koja ne staje. Šta se tada dešava ne odlučuje hardver (koji jednostavno prelama vrednost, wraparound), već jezik:
| Jezik | Prekoračenje sa znakom | Prekoračenje bez znaka | Pomeranje za ≥ širinu | Deljenje nulom |
|---|---|---|---|---|
| C / C++ | nedefinisano ponašanje: optimizator pretpostavlja da se nikad ne dešava | prelama se modulo 2ⁿ (definisano) | nedefinisano | nedefinisano (SIGFPE na x86) |
| Rust | panic u debug režimu, prelamanje u release; checked_/wrapping_/saturating_/overflowing_ su eksplicitni | isto kao sa znakom | panic u debug režimu | panic |
| Go | prelama se (definisano) | prelama se | 0 / −1, bez panica | panic |
| Java | prelama se (definisano); Math.addExact baca izuzetak | nema neoznačenih tipova (char je 16-bitni neoznačen) | broj pomeranja maskiran na 5/6 bitova | ArithmeticException |
| JavaScript | brojevi su double: tačni do 2⁵³, zatim zaokruživanje; bitovski operatori rade nad int32 | broj pomeranja maskiran na 5 bitova | Infinity / NaN | |
Pravila C-a koja ujedaju
- Promocija celih brojeva. Sve manje od
intpostajeintpre aritmetike, pa jeuint8_t a = 200, b = 100; a + bjednako 300, a tek dodela nazad uuint8_todseca na 44.~xnaduint8_tdaje negativan int. - Uobičajene aritmetičke konverzije. Kada se označeni sretne sa neoznačenim iste širine, označena strana postaje neoznačena:
-1 > 0uje tačno,if (len - 1 < 0)nikad nije tačno kada jelentipasize_t. - Prekoračenje sa znakom je UB.
if (x + 1 < x)kao proveru prekoračenja GCC i Clang brišu sa uključenom optimizacijom, jer smeju da pretpostave dax + 1nikad ne prekoračuje. Proveravajte pre operacije (x > INT_MAX - y) ili koristite__builtin_add_overflow. - Pomeranja. Pomeranje za širinu ili više, ili pomeranje negativne vrednosti ulevo, je nedefinisano; x86 maskira broj pomeranja, ARM daje nulu, kompajler radi šta god njegov model kaže.
- Deljenje. Odseca ka nuli, pa je
-7 / 2 == -3i-7 % 2 == -1;INT_MIN / -1prekoračuje i zaista izaziva trap na x86.
Čitanje izlaza
Rezultat se prikazuje u tipu u kome završava, sa svojim obrascem bitova i istim bitovima pročitanim sa suprotnom označenošću, što je ono što vidite kada vrednost pređe granicu (int) / (unsigned) ili se sačuva i pročita kroz drugi tip. Lista koraka je redosled izračunavanja sa tipom u svakom koraku; napomene su mesta gde je jezik uradio nešto drugo osim obične aritmetike.
Pitanja
Zašto se rezultat za C razlikuje od onoga što je moj program ispisao?
Za nedefinisano ponašanje ne postoji jedan tačan odgovor: ova stranica prikazuje wraparound vrednost koju proizvodi neoptimizovan kod i označava korak kao UB. Sa -O2 kompajler je možda pretpostavio da prekoračenje ne može da se desi i proizveo drugačiji kod. Kompajlirajte sa -fsanitize=undefined da vidite tačno mesto.
Šta radi izbor tipa?
To je tip literala koji nemaju sufiks ni kastovanje, što je u C-u podrazumevano int. Izaberite u8 i ukucajte 200 + 100 da vidite kako Rust panikuje, a C promoviše u int i uspeva.
Kako da napišem kastovanje?
C stil: (u8) expr, (uint32_t) expr, (int) expr. Rust stil: expr as u8. Oba rade u svakom jezičkom režimu. INT_MAX, INT_MIN, UINT_MAX, LONG_MAX, SIZE_MAX su dostupni kao imena.
Da li radi sa 128 bita?
Da: i128 i u128 se izračunavaju tačno (sva aritmetika se radi sa proizvoljnom preciznošću, pa se zatim uklapa u tip).
Šta je sa brojevima u pokretnom zarezu?
Ne ovde; to pokriva IEEE 754 vizuelizator float brojeva na ovom sajtu. Ovaj alat se bavi samo onim što se dešava celim brojevima u tipovima fiksne širine.