Two's complement, explained

Binary has no minus sign. Two's complement is the convention nearly all hardware uses to represent negative integers — and once you see why it was chosen, several confusing things (like −1 being 0xFF) become obvious.

The problem: where does the sign go?

With 8 bits you can write 256 patterns. If you want negative numbers, you must spend some patterns on them. The naive idea — reserve the top bit as a sign flag and keep the rest as magnitude — creates two zeros (+0 and −0) and breaks ordinary addition. Two's complement fixes both.

The rule

To negate a number: invert every bit, then add one. For −42 in 8 bits:

  1. 42 = 0010 1010
  2. invert → 1101 0101
  3. +1 → 1101 0110 = -42

The top bit still tells you the sign — patterns starting with 1 are negative — but it isn't just a flag: it carries the value −2⁷. Formally, in 8 bits the leading bit is worth −128 and the rest are positive powers of two: 1101 0110 = −128 + 64 + 16 + 4 + 2 = −42.

Why −1 is all ones

  1. 1 = 0000 0001
  2. invert → 1111 1110
  3. +1 → 1111 1111 = -1

That's why −1 prints as 0xFF in a byte, 0xFFFFFFFF in 32 bits, and 0xFFFFFFFFFFFFFFFF in 64. Same idea, wider register.

Why hardware loves it

Addition just works: -42 + 50 is plain binary addition of 1101 0110 + 0011 0010, carry falls off the end, result 0000 1000 = 8. No special circuitry for signs, no minus-zero. Subtraction becomes "negate and add".

Overflow: the range is asymmetric

8 bits hold −128…127. Note the asymmetry: −128 exists but +128 doesn't. If you add 127 + 1 in signed 8-bit arithmetic you wrap to −128 — that's signed overflow, and it's the kind of bug that hides until the worst moment. The HexCalculator app checks 64-bit overflow on every operation and shows an error instead of a silently wrapped result.

One pattern, two meanings

1101 0110 is −42 signed and 214 unsigned. Bits don't know which one you mean — the interpretation is yours. Try it in the two's complement calculator: it shows both readings side by side for any width from 8 to 64 bits.

Sign extension

Widening a signed value copies the sign bit into the new bits: −42 goes from 1101 0110 (8-bit) to 1111 1111 1101 0110 (16-bit). Widening an unsigned value pads with zeros. Mixing those two up is a classic source of "why is my number suddenly huge" bugs when casting in C.

Keep exploring