A memory cell only knows 0 and 1. There is no physical minus sign. So how does a computer store a number like ? The answer is two’s complement, the encoding that virtually every modern CPU (and every C compiler we care about) uses for signed integers. This note explains it from scratch. You only need Binary Number System as background.
The Problem
With bits we have patterns. If we only ever stored plain binary (called unsigned), every pattern would mean a value from to , and negative numbers would be impossible.
Idea: sacrifice the leftmost bit (the MSB) to act as a sign bit, and design a rule so that the remaining bits encode the magnitude of negative values in a clever way.
One’s Complement
The one’s complement of a binary number is obtained by flipping every bit: every 0 becomes 1 and every 1 becomes 0:
Legend: what each symbol means
| Symbol | Meaning |
|---|---|
| Some number, written as an -bit pattern | |
| The pattern with every bit flipped. This bar is the standard math symbol for “the flip of”. So is the one’s complement | |
| The all-ones pattern ( ones in a row) | |
| The value of all-ones, from the “all ones trick” in Binary Number System > Counting and the All Ones Trick | |
| ”which means” / “therefore”: the right part follows from the left part |
Why the equation is true, in words: a bit and its flip always add up to 1 (0+1 = 1, 1+0 = 1). So column by column, plus its flip gives 1 in every position, the all-ones pattern. Rearranging that fact (“take away from all-ones”) gives the right-hand side: the flip of equals .
Check it with numbers
, : flipping the bits should give the same answer as subtracting:
And indeed
0001 1001flipped bit-by-bit is1110 0110. ✓ Both roads lead to the same pattern.
One's complement of 25
+25: 0001 1001 flip: 1110 0110 ← one's complement
Historically, “negative ” was stored as the one’s complement of . But this has an ugly flaw: it produces two zeros.
+0: 0000 0000
-0: 1111 1111 ← same value, different pattern!
Two patterns for zero wastes one pattern, forces hardware to special-case zero, and makes arithmetic awkward. This is why nobody uses it in practice. Instead we take one more step.
Two’s Complement
The two’s complement of is its one’s complement plus one:
Legend: what each symbol means
| Symbol | Meaning |
|---|---|
| The -bit pattern we use to represent the negative of (not a new pattern, a reinterpretation) | |
| “is the same thing as” | |
| The subtraction: subtract from the total pattern count | |
| “modulo ”: if the result needs more than bits, throw the extra bits away. With only bits there are just patterns, so anything past wraps around, like a clock: 13 o’clock is 1 o’clock |
In words: to negate , compute . The wrap-around rule is what makes this legal. The pattern for and the pattern we call are the same -bit object.
Check it with numbers
, :
Same pattern as the flip-and-add-1 recipe below. Subtracting from and “flip then add 1” are two routes to the same destination.
Recipe to negate a number
- Write the number in binary (positive, plain binary).
- Flip every bit → one’s complement.
- Add
1→ two’s complement.
Worked example: store in one byte
Step 1: magnitude in binary
Step 2: one’s complement (flip all bits)
0001 1001 → 1110 0110Step 3: add 1
1110 0110 + 1 ----------- 1110 0111 ← two's complement of 25So is stored as
1110 0111, which in hex is0xE7.
The recipe also works in reverse. Flipping the bits of 1110 0111 and adding 1 gives back 0001 1001 (). Negation is its own inverse.
Reading a stored byte back
Given a pattern, how do we know which number it represents?
- MSB = 0: the number is non-negative. Just read it as plain binary.
- MSB = 1: the number is negative. Its value is .
In one formula, for an -bit pattern :
Legend: what each symbol means
| Symbol | Meaning |
|---|---|
| The signed value the pattern represents | |
| The MSB (leftmost bit), the sign bit | |
The MSB’s column, counted as negative. If the sign bit is 0 this term is ; if it’s 1 it contributes | |
| Every other column, the ordinary “bit times its weight” from Binary Number System > Key Formulas, all positive | |
| ”add up all those ordinary columns”, from position 0 to (everything except the sign bit) |
In words: read the pattern exactly like plain binary, except the leftmost column has a minus sign in front of it.
Reading
1110 0111back, column by columnSame answer as the shortcut “MSB is
1, so subtract ”: . Exactly the we stored.
Why Computers Use Two’s Complement
- Only one zero.
0000 0000is the only zero, so no pattern is wasted. - Subtraction becomes addition. can be computed as using the same adder circuit the CPU already has, so no separate subtractor is needed.
- No exceptions. Adding works identically whether the operands are positive or negative (any carry out of the MSB is simply discarded).
25: 0001 1001 (0x19)
+ -25: 1110 0111 (0xE7)
----------------- ┐
1 0000 0000 ┘ discard the 9th bit
= 0000 0000 = 0 ✓
Extremes Worth Memorizing
Working these out with the recipe makes the signed range intuitive:
| Value | Pattern (byte) | Hex | Why |
|---|---|---|---|
1111 1111 | 0xFF | All magnitude bits set, the “last” step before wrapping to 0 | |
1000 0000 | 0x80 | Sign bit set, all magnitude bits zero, the most negative byte | |
0111 1111 | 0x7F | Sign bit clear, all magnitude bits set, the largest positive byte |
Legend: the pattern 1000 0000 read as plain binary is . Since its MSB is 1 we subtract (the “reading it back” rule), and . This is why all-zeros-magnitude lands at the most negative value, not at zero.
Key Formulas
| Operation | Formula (for bits) | In plain words |
|---|---|---|
| Negate | Subtract from the total pattern count. Any overflow past bits wraps away | |
| One’s complement | Subtract from all-ones, which is the same as flipping every bit | |
| Value of a pattern | Read it like plain binary, but the leftmost column counts negative | |
| Signed range | to | Half the patterns go to , the other half to |
| Unsigned range | to | All patterns used as plain binary magnitudes |
Related: Char Data Type shows these formulas in action on the char data type.