How it works
The explanation, with examples you can run.
Binary and two's complement
Each bit is worth twice the bit to its right: 0000 0101 is 4 + 1 = 5. Eight bits give 2⁸ = 256 patterns, so an unsigned byte holds 0 to 255.
For negative numbers, almost every CPU uses two's complement: the top bit counts as −2⁷ (−128) instead of +128. So 1000 0000 is −128, 1111 1111 is −1, and the signed range is −128 to 127. To negate a number, flip every bit and add one. The big advantage is that the same adder circuit works for signed and unsigned values; only the interpretation differs.
Typed arrays let you see this directly from JavaScript:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
This prints 0, −128 and −2147483648 for the three overflows, then shows −1 as thirty-two 1 bits, and −5 as …11111011. The last line prints 4,3,2,1 on x86 and Arm machines: they are little-endian, storing the least significant byte first. Network protocols traditionally use big-endian ("network byte order"), which is why binary parsers must say which one they expect.
What languages do on overflow
- Wrap silently: Java, Go, C# (unless you use
checked), and unsigned integers in C and C++. - Trap: Swift always; Rust in debug builds (release builds wrap unless you enable checks, and
checked_addand friends are always available). - Undefined behaviour: signed overflow in C and C++. The compiler may assume it never happens and optimise accordingly, so the program can do anything.
- Grow: Python's
intand JavaScript'sBigInthave no fixed width, so they never overflow, at the cost of speed.
JavaScript's special case
A JavaScript number is a 64-bit floating-point value (next topic). It represents every integer exactly only up to Number.MAX_SAFE_INTEGER, 2⁵³ − 1 = 9,007,199,254,740,991. Beyond that, 2 ** 53 + 1 evaluates to 9007199254740992. That bites when a 64-bit database id or a Twitter-style snowflake id is parsed from JSON as a number, so such ids are usually sent as strings. Bitwise operators convert to 32-bit integers first, which is why (2 ** 31) | 0 is −2147483648.
Real bugs
The Ariane 5 rocket's first flight in 1996 failed when a 64-bit floating-point value was converted to a 16-bit signed integer that couldn't hold it. Many binary searches, including the one in Java's standard library until 2006, computed (low + high) / 2, which overflows for arrays of over a billion elements. Unix time stored in a signed 32-bit integer runs out on 19 January 2038, a topic in the time chapter.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.