How Two's Complement Works
Two's complement is how virtually every CPU stores negative integers. It makes subtraction reuse the addition circuitry, gives zero a single representation, and explains why an 8-bit value runs from -128 to 127. Here is the intuition, the recipe, and the language gotchas.
The recipe
To negate a number in two's complement:
- Write the positive value in binary at your chosen width (e.g. 8 bits): 5 → 00000101.
- Invert every bit (one's complement): 11111010.
- Add 1: 11111011. That is -5.
Why it works
In 8 bits, arithmetic wraps modulo 256. Two's complement stores -n as 256 - n, so adding n and -n naturally lands on 256, which wraps to 0. The hardware never needs to know a number is negative - the same adder circuit works for signed and unsigned values.
The top bit doubles as the sign: if it is 1, the value is negative. That is why the 8-bit range is -128 to 127 - half the 256 patterns are negative, half non-negative, and zero takes only one pattern (unlike sign-magnitude, which wastes a pattern on negative zero).
Ranges and overflow
An N-bit signed integer holds -2^(N-1) through 2^(N-1)-1: 8-bit is -128..127, 16-bit is -32768..32767, 32-bit is about ±2.1 billion. Overflow wraps silently in most hardware: 127 + 1 in 8 bits becomes -128. Classic bugs - including the original Gangnam Style YouTube counter overflow - come from crossing these limits.
Language gotchas
JavaScript bitwise operators coerce numbers to signed 32-bit values, so ~0 is -1 and (0xFFFFFFFF | 0) is -1; use >>> 0 to view a result as unsigned. Python integers are arbitrary precision, so negative numbers keep an infinite conceptual sign extension - mask with & 0xFF to see the two's complement byte. In C, signed overflow is undefined behavior, not wraparound, and the compiler may optimize on that assumption.
Doing it in code
// JavaScript numbers are 64-bit, but bitwise ops use 32-bit
const num = -5;
const twosComp = num >>> 0; // Convert to unsigned 32-bit
twosComp.toString(2); // "11111111111111111111111111111011"Frequently Asked Questions
Why is it called two's complement?
Because negating n takes its complement with respect to a power of two: in 8 bits, -n is stored as 2⁸ - n = 256 - n. The name distinguishes it from one's complement, which uses 2⁸ - 1 - n.
How do I read a two's complement number?
If the top bit is 0, read it as normal binary. If the top bit is 1, the value is negative: invert all bits, add 1, and negate. 11111011 → invert → 00000100 → +1 → 00000101 = 5, so the value is -5.
What is -1 in two's complement?
All ones, at any width: 11111111 in 8 bits, 0xFFFFFFFF in 32 bits. That is why setting every bit of a signed integer produces -1, and why memset(&x, 0xFF, ...) fills integers with -1.