The problem: bits have no minus sign
In the last guide you met the unsigned integer: with n bits you can name every whole number from 0 up to 2^n - 1, and that is all. There is no leftover symbol for a minus sign — a byte is just eight tiny on/off switches. So the question is genuinely awkward: if every bit pattern is already spoken for by a non-negative number, where do we put -5? Whatever scheme we invent must somehow carve the same 2^n patterns into negatives and non-negatives, and ideally play nicely with the hardware we already have.
The obvious first idea is sign-magnitude: steal the top bit to mean the sign (0 for plus, 1 for minus) and let the remaining bits hold the magnitude, exactly like writing a number on paper. It is easy for a human to read, but it has two ugly flaws. First, it gives you two zeros — a positive 0000 and a negative 1000 — which wastes a pattern and forces hardware to treat them as equal. Second, the rule for adding is full of special cases: the CPU must inspect the signs and decide whether to add or subtract the magnitudes. Sign-magnitude is honest but expensive.
A second attempt, ones-complement, flips every bit to negate a number. It is closer to what we want, but it still suffers two zeros (all-zeros and all-ones both mean zero) and it needs an awkward 'end-around carry' when you add. Both schemes were really used in old machines, but the world converged on something better. The winner solves the double-zero problem and, almost magically, makes subtraction free.
The trick: weight the top bit negative
Two's complement is the scheme every modern CPU uses, and the whole idea fits in one sentence: keep all the usual place values, but make the top bit's place value negative. In an unsigned 4-bit number the bits are worth 8, 4, 2, 1. In two's complement they are worth -8, 4, 2, 1. So the very same pattern can mean a positive or a negative number depending only on that one rule — nothing else about the bits changes.
4-bit two's complement (top bit worth -8) place values: -8 4 2 1 ----------------------------------------- 0101 = 0 + 4 + 0 + 1 = +5 1011 = -8 + 0 + 2 + 1 = -5 1111 = -8 + 4 + 2 + 1 = -1 1000 = -8 + 0 + 0 + 0 = -8 (most negative) 0111 = 0 + 4 + 2 + 1 = +7 (most positive) 0000 = 0 + 0 + 0 + 0 = 0 (the ONLY zero) range of n bits: -2^(n-1) .. +2^(n-1) - 1
Read off the consequences. The top bit alone tells you the sign: 0 means non-negative, 1 means negative, just like a sign bit — but it is not a separate flag, it is a real place value pulling its weight. There is now a single zero (0000), so no wasted pattern and no awkward equality check. And the range is lopsided on purpose: for n bits you get -2^(n-1) up to 2^(n-1) - 1. For a 32-bit int that is -2,147,483,648 to 2,147,483,647 — one more negative than positive, because zero lives on the non-negative side.
How to negate: flip the bits, add one
There is a famous mechanical recipe for negating a two's-complement number: invert every bit, then add 1. To get -5 in 4 bits, start from +5 (0101), flip to 1010, add one to get 1011 — and check: -8 + 2 + 1 = -5. The recipe works in both directions, so applying it to 1011 gives back 0101. Why does 'flip and add one' work? Flipping all n bits turns x into (2^n - 1) - x. Add 1 and you have 2^n - x, which is exactly the bit pattern that behaves like -x once you let the top bit carry its negative weight.
- Write the magnitude in plain binary, e.g. +5 is 0101 in 4 bits.
- Invert every bit (the ones-complement step): 0101 becomes 1010.
- Add 1 to the result: 1010 + 1 = 1011, which is -5.
- Sanity-check with the place values: 1011 = -8 + 2 + 1 = -5. Done.
The payoff: one adder does add and subtract
Here is the reason two's complement won. Because a negative value is stored as 2^n - x, ordinary binary addition — the same ripple-carry adder of full adders you will build in the digital-logic rung — just works, with no special handling of signs. Add 0101 (+5) and 1011 (-5) and you get 10000; the carry out of the top bit is simply thrown away in n-bit arithmetic, leaving 0000 = 0. The same wires that compute 5 + (-5) compute 200 + 37. The CPU does not even need to know whether you meant the bits as signed or unsigned: the bit pattern of the sum is identical either way.
Subtraction comes for free from the same circuit. To compute a - b, the hardware negates b (flip its bits, add one) and adds. In practice the ALU feeds b through inverters and forces the carry-in to 1 — that single carry-in supplies the '+1' of the negation. So subtraction reuses the adder with two cheap extras and no second machine. This is why two's complement is described as making the hardware simpler: one datapath, one set of gates, for both operations.
Trace 7 - 5 to feel it. Take +5 = 0101, invert to 1010, and add it to 0111 (+7) with a forced carry-in of 1: 0111 + 1010 + 1 = 10010. Drop the carry-out and you are left with 0010 = +2, exactly 7 - 5. The adder never knew it was 'subtracting' — it only ever adds; the inverters and the carry-in did the negation. That is the whole magic in one line: a - b = a + (~b) + 1.
Two honest pitfalls: sign extension and overflow
When you copy a narrow signed value into a wider register — say a 8-bit char into a 32-bit int — you cannot just pad with zeros, or every negative number would suddenly turn positive. You must copy the sign bit into all the new high bits. This is sign extension: -5 as 1011 in 4 bits becomes 1111_1011 in 8 bits, still -5. (An unsigned value is widened by padding with zeros instead — the CPU has separate signed and unsigned load instructions for exactly this reason.) Sign extension is why the same constant can be used as a small immediate and still mean a negative number once stretched to register width.
The second pitfall is integer overflow. With a fixed n bits there are only 2^n patterns, so any result that lands outside the range -2^(n-1)..2^(n-1)-1 wraps around silently. In 4 bits, 7 + 1 = 1000 = -8: add one to the largest positive and you fall off the cliff into the most negative. The hardware does not stop you; it just sets an overflow flag in the condition flags and carries on. Signed overflow is detected by a tell-tale sign: it happens exactly when the two inputs share a sign but the result has the opposite sign (two positives giving a negative, or two negatives giving a positive).