Arithmetic
Two’s complement: how computers store negative numbers
A byte has no minus sign. Two’s complement is the trick that lets eight plain bits stand for −128 to 127, and it works so well that the same adder circuit handles positive and negative numbers without knowing the difference.
The one-sentence version
In an 8-bit two’s complement number, the leftmost bit is worth −128 instead of +128. Every other bit keeps its usual positive weight (64, 32, 16, 8, 4, 2, 1). That is the whole definition. Everything else follows from it.
Reading a two’s complement number
Add up the weights of the bits that are 1, remembering that the top one is negative:
00000101= 4 + 1 = 511111111= −128 + 127 = −110000000= −128, the most negative byte01111111= 127, the most positive byte
If the top bit is 0 the number is zero or positive, and it reads exactly like an ordinary unsigned number. If the top bit is 1 the number is negative. For that reason the top bit is often called the sign bit, but unlike a real minus sign it carries a weight and takes part in the arithmetic.
The odometer picture
Another way to see it: an 8-bit register is a counter that wraps around after 255, like a car’s odometer rolling from 99999 back to 00000. Count down from 0 and you land on 255, then 254, then 253. Two’s complement simply agrees to call those values −1, −2 and −3. A negative number −x is stored as 256 − x.
That is why it works with ordinary addition. Adding 256 to a number in an 8-bit register changes nothing, because the extra 1 falls off the top. So adding 256 − x is the same as subtracting x. Try it: 7 + (−5) is 00000111 + 11111011 = 1 00000010. Drop the ninth bit and the answer is 2.
Range
| Bits | Unsigned range | Two’s complement range |
|---|---|---|
| 4 | 0 … 15 | −8 … 7 |
| 8 | 0 … 255 | −128 … 127 |
| 16 | 0 … 65,535 | −32,768 … 32,767 |
| 32 | 0 … 4,294,967,295 | −2,147,483,648 … 2,147,483,647 |
There is one more negative number than positive, because zero takes up one of the “non-negative” patterns. A curious consequence is that negating −128 in a byte gives −128 back, since +128 does not fit.
Why not just use a sign bit?
The obvious idea, sign-magnitude, uses one bit for the sign and the rest for the size, as we do on paper. Some early computers did that, and others used ones’ complement, where negating means flipping every bit. Both have two zeros (+0 and −0), and both need extra logic so that adding a negative number gives the right answer. Two’s complement has one zero, and the plain binary adder already does the right thing. That saving in hardware is why practically every processor made since the 1970s uses it. Floating-point numbers are the main exception: they keep a separate sign bit.
Converting by hand
To write a negative number −x in n bits, work out 2n − x and write that in binary. For −5 in 8 bits: 256 − 5 = 251 = 11111011. To go the other way, read the number with the top bit worth −2n−1, as above. Both methods follow straight from the definition and always work.
Spoiler for the game’s negation puzzle: the shortcut
There is a well-known shortcut for negating that needs no subtraction: invert every bit, then add 1. For 5: 00000101 → 11111010 → 11111011, which is −5. It works because a number plus its bit-flipped twin is always 11111111, which is −1. The game asks you to build a negation circuit, so skip this box if you want to find the idea yourself.
Overflow
Add 100 + 100 in a signed byte and you get 11001000, which reads as −56. The true answer, 200, is outside the range, so it has overflowed. The tell-tale sign is that two numbers with the same sign produced a result with the other sign. CPUs record this in an overflow flag, separate from the carry flag used for unsigned numbers. The adder produces the same bits either way, and it is the program’s choice whether to treat them as signed or unsigned. That is why many instruction sets have two sets of comparison and branch instructions.
Sign extension
To widen a signed number, say from 8 to 16 bits, copy the sign bit into all the new bits. −5 as a byte is 11111011, and as a 16-bit word it is 11111111 11111011. A positive number gets zeros, as you would expect. Instruction sets that load bytes into wider registers usually offer a “load signed” and a “load unsigned” variant for exactly this reason.