The C Path — learn C, visually

🧮 Foundations — Before C

Negative numbers: two’s complement

⏱ 12 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

In 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)bitsminmax
signed char8−128127
short16−32,76832,767
int32−2,147,483,6482,147,483,647
unsigned int3204,294,967,295

Overflow: driving off the cliff

overflow.c
#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;
}
terminal
$ 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.

▶ Practice this lesson interactively (with live gcc)