🧮 Foundations — Before C
Negative numbers: two’s complement
▶ Open the interactive lesson — free, no signupIn 2014, Gangnam Style racked up so many views it maxed out YouTube's counter at exactly 2,147,483,647 — and in old arcade games, a score that grew too big could suddenly flip to a huge negative number. This lesson shows where that oddly specific limit comes from, how a machine with no minus switch stores numbers below zero, and why values that get too big wrap around instead of just stopping.
Bits can only be 0 or 1 — so how do we store -5? There's no minus switch. The answer, used by essentially every CPU on Earth, is a clever trick called two's complement — the scheme behind every signed type (programmer-speak for "allowed to be negative").
The big idea: make the top bit negative
In an 8-bit signed number, the leftmost bit doesn't mean +128 — it means −128. Every other bit stays positive. So the value is: −128·b₇ + 64·b₆ + 32·b₅ + … + 1·b₀.
▶ This spot has an interactive bits widget — open the interactive lesson to play with it.
Why this design is genius: addition just works. The CPU uses the same circuit for signed and unsigned math — the bits don't care. (-1) + 1 = 11111111 + 00000001 = 1_00000000 → the ninth bit falls off the edge → 00000000 = 0. ✓
Quick negation recipe: to compute −x, flip every bit and add 1. So 5 = 00000101 → flip → 11111010 → +1 → 11111011 = −5. In C: -x == ~x + 1.
🧠 Checkpoint: In 8-bit two’s complement, what value is 11111111?
- 255
- −1
- −127
- −255
Show answer
−1 — −128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = −1. All-ones is always −1 in two’s complement, at any width. (As unsigned it would be 255.)
Ranges are lopsided
8 bits give 256 values. Two's complement splits them as −128 … +127. Notice: one more negative than positive, because zero eats one of the positive slots.
| type (typical) | bits | min | max |
|---|---|---|---|
signed char | 8 | −128 | 127 |
short | 16 | −32,768 | 32,767 |
int | 32 | −2,147,483,648 | 2,147,483,647 |
unsigned int | 32 | 0 | 4,294,967,295 |
Overflow: driving off the cliff
#include <stdio.h>
#include <limits.h>
int main(void) {
int big = INT_MAX; /* 2147483647 */
printf("big = %d\n", big);
printf("big + 1 = %d\n", big + 1); /* undefined behavior! */
unsigned int u = 0;
printf("0u - 1 = %u\n", u - 1); /* well-defined wrap */
return 0;
}$ gcc overflow.c -o overflow && ./overflow big = 2147483647 big + 1 = -2147483648 0u - 1 = 4294967295
What happened? INT_MAX + 1 wrapped around to INT_MIN — the odometer rolled over. For unsigned types, wraparound is well-defined (modulo 2ⁿ). For signed types it is undefined behavior — the compiler is allowed to assume it never happens, and weird things follow. We have a whole lesson on UB later.
🧠 Checkpoint: Why do CPUs love two’s complement?
- It stores bigger numbers
- The same adder circuit works for signed and unsigned
- It never overflows
- It uses fewer bits
Show answer
The same adder circuit works for signed and unsigned — One adder to rule them all. Sign-magnitude or offset encodings would need special-case hardware; two’s complement makes +, −, × identical at the bit level.
🧠 Checkpoint: Signed integer overflow in C is…
- wraparound, like unsigned
- a compile error
- undefined behavior
- always a crash
Show answer
undefined behavior — It usually looks like wraparound, but the standard says undefined behavior — the optimizer may assume it can’t happen and transform your code in surprising ways.