Two's Complement
So far every binary number you have met has been positive — the columns are worth
1, 2, 4, 8, \dots and you add up the ones. But a real computer must also
store negative integers: temperatures below zero, an account overdrawn, a sprite
moving left. It has no minus-sign key and no extra symbols — only 0s and
1s, packed into a fixed width such as
8 bits. So how do you fit a minus sign into a byte?
The answer is a beautifully sneaky code called two's complement. It is the scheme
used by essentially every processor on Earth, and its killer feature is this: once negatives are
stored this way, ordinary binary addition — the exact same
binary addition
you already know, with the same adder circuit — gives the right answer for
subtraction too. No separate subtracting hardware required. That single trick is why
two's complement won.
The big idea: make the top column negative
In an ordinary 8-bit number the columns are
128, 64, 32, 16, 8, 4, 2, 1 — all positive. Two's complement changes
one thing: the leftmost column (the most significant bit,
or MSB) is given a negative place value, -128. Every other
column stays exactly as it was.
Read a signed byte the same way as always — add up the columns that hold a
1 — but remember the top one subtracts
128. Because that top bit is the only one that can make the total
negative, it doubles as a sign flag: a leading 0 means the
number is zero-or-positive, a leading 1 means it is negative.
- The most positive value is 01111111 = 64+32+16+8+4+2+1 = +127.
- The most negative value is 10000000 = -128 (only the
-128 column is on).
- So an 8-bit two's-complement byte spans
-128 to +127 — still
256 different values, just shifted to straddle zero.
- In general, n bits cover
-2^{\,n-1} to +2^{\,n-1}-1.
Reading a negative byte
Take 11110011. The top bit is 1, so this is
negative. Add up the on-columns, remembering the sign of the first:
-128 + 64 + 32 + 16 + 0 + 0 + 2 + 1 = -13.
And 00001101? The top bit is 0, so it is
positive: 8 + 4 + 1 = +13. Notice how those two bytes look — they are
mirror-ish images of one another. That is not a coincidence; it is exactly the negation rule we meet
next.
Negating a number: invert, then add one
To find the two's-complement pattern for -x when you already have
x, follow one short recipe:
- Invert every bit (this is the one's complement: swap every
0 \leftrightarrow 1).
- Add 1 with ordinary binary addition.
Let's turn +13 = 00001101 into -13. Watch the
two steps run:
Out comes 11110011 — precisely the byte we decoded as
-13 above. The lovely part is that the recipe is its own inverse: invert
and add one to 11110011 and you land back on 00001101.
The same button both negates and un-negates.
Inverting a byte replaces x with
11111111 - x, because flipping every bit is the same as subtracting from
the all-ones pattern. Now 11111111 is just
256 - 1 = 255 in 8 bits, so the inverse is
255 - x. Add the 1 and you have
256 - x. On an 8-bit wheel of
256 values, 256 - x and
-x sit on the very same spot (they differ by a full lap of
256). That is the whole magic — two's complement is arithmetic
modulo 2^n, and the negative numbers are simply the far side of
the wheel.
Why bother? Subtraction becomes free addition
Here is the pay-off. To compute a - b, a processor does not
subtract. It negates b (invert-and-add-one) and then adds,
throwing away any carry that runs off the top. Let's do 20 - 13 in a byte.
+20 = 00010100, and we found -13 = 11110011.
Add them:
00010100 + 11110011 = 1\,00000111.
The answer needs a ninth bit, but we only keep 8: drop the leading carry
and we are left with 00000111 = +7. And
20 - 13 = 7. It just works — no borrow, no special circuit, only the adder
you already have. Try it live below.
Convert and negate yourself — edit and Run
This program stores signed values in 8 bits. It prints the bit pattern for
a number, negates it by inverting and adding one, and decodes patterns back to signed
values. Change x and press Run.
const BITS = 8;
const MASK = (1 << BITS) - 1; // 11111111 = 255
// Encode a signed number as an 8-bit two's-complement string.
function toByte(n: number): string {
return ((n & MASK) >>> 0).toString(2).padStart(BITS, "0");
}
// Decode an 8-bit pattern back to its signed value (top bit worth -128).
function fromByte(bits: string): number {
const u = parseInt(bits, 2);
return u >= 128 ? u - 256 : u;
}
// Negate by inverting every bit and adding one.
function negate(bits: string): string {
const inverted = (~parseInt(bits, 2) & MASK) >>> 0;
return toByte(inverted + 1);
}
const x: number = 13;
const bx: string = toByte(x);
const bneg: string = negate(bx);
console.log(x + " = " + bx);
console.log("invert + 1 -> " + bneg + " = " + fromByte(bneg));
// Subtraction as addition: 20 - 13 = 20 + (-13)
const sum = (parseInt(toByte(20), 2) + parseInt(bneg, 2)) & MASK;
console.log("20 + (-13) = " + toByte(sum) + " = " + fromByte(toByte(sum)));
Watch the pitfalls
The single most common mistake is to read a signed byte like an unsigned one. In
unsigned binary, 11110011 is
128+64+32+16+2+1 = 243. In two's complement the very
same bits mean -13, because that leading 1
is worth -128, not +128. A leading
1 always signals a negative number. The bits alone never tell
you which — you must know whether the value is being treated as signed or unsigned.
A signed byte only reaches +127. Add
+100 + 50:
01100100 + 00110010 = 10010110. That top bit is now
1, so the stored result reads as -106 — the
answer 150 was too big to fit and has overflowed into
the sign, coming out negative. The tell-tale sign of overflow in two's complement is adding two
numbers of the same sign and getting an answer of the opposite sign. There is no
error message; the wrong number just sails on. Processors set an overflow flag for
exactly this, but if your program never checks it, the bug is silent — always ask whether the
largest possible result still fits the width.
An older scheme, sign-and-magnitude, used the top bit purely as a plus/minus flag and the
rest as size — which gives you both a +0 (00000000)
and a -0 (10000000), two zeros for one number,
and it breaks ordinary addition. Two's complement has a single zero
(00000000; invert-and-add-one on it wraps neatly back to itself) and one
spare negative pattern 10000000 = -128 with no positive twin. That single
zero and the free subtraction are why every modern CPU chose it.