Two's Complement: Signed Binary Arithmetic Explained
TL;DR: Two’s complement is the encoding every modern CPU uses for signed integers. The most significant bit carries weight instead of ; the rest of the bits keep their normal positive weights. The result: there is exactly one zero, addition and subtraction use the same hardware as for unsigned numbers, and negation is “flip the bits and add one.” Overflow is detected by comparing the carry into the MSB with the carry out.
Computers do not store negative signs the way humans write them. There is no minus-sign bit, no separate “negative” register. The whole signed-integer system is a clever interpretation of plain unsigned bits — and the interpretation everyone settled on is two’s complement. It is the reason your processor can add two numbers without first asking “are these signed or unsigned?” It is the reason in 8-bit hardware is 11111111 and not 10000001. And it is the reason CPUs do not have a special “subtract” circuit — only an “add” circuit and an inverter.
This post unpacks two’s complement from the bit level up: how the encoding works, why negation is bitwise-NOT-plus-one, how sign extension preserves value across widths, how overflow is detected with a single XOR, and why every alternative encoding (sign-magnitude, one’s complement) was abandoned. Once the encoding clicks, a lot of “weird” CPU behavior — like why has no positive twin, or why integer overflow wraps the way it does — turns out to be a direct consequence of the math.
Why Did Computers Need a Way to Represent Negative Numbers?
Plain binary handles non-negative integers naturally: each bit position has weight , every weight is positive, and the n-bit range is . Subtraction is where the trouble starts. Subtracting two positive numbers can produce a negative result. The hardware has to either refuse to compute it (terrible), provide a flag indicating “the result is negative, please re-read the bits in some other format” (also terrible), or pick an encoding where the same adder produces the correct bit pattern regardless of sign. Two’s complement is the third option, and it won decisively.
But before two’s complement won, two competitors were tried.
Attempt 1: Sign-Magnitude
Reserve the top bit for the sign (0 = positive, 1 = negative); the remaining bits hold the magnitude. In 4 bits:
| Bits | Sign-Magnitude |
|---|---|
| 0011 | |
| 1011 | |
| 0000 | |
| 1000 |
Easy to read by eye. Disastrous in hardware. Two problems:
- Two zeros. and are different bit patterns but mathematically equal. Equality circuits get more complex.
- Different addition for different sign combinations. Adding and requires the hardware to detect that the signs differ, then subtract magnitudes, then pick the sign of the larger. That is not addition; it is a four-way conditional with a comparator inside.
Attempt 2: One’s Complement
Negate by flipping every bit. In 4 bits:
| Bits | One’s-Complement |
|---|---|
| 0011 | |
| 1100 | |
| 0000 | |
| 1111 |
Better — at least both zeros XOR to the same bit pattern under most operations — but it still has two zeros, and addition needs an “end-around carry” (the carry-out of the MSB must be added back into the LSB) which adds a cycle of latency. Some early machines (the CDC 6600, the PDP-1) used it. None of their descendants do.
Attempt 3 (The Winner): Two’s Complement
Negate by flipping every bit and adding one. In 4 bits:
| Bits | Two’s-Complement |
|---|---|
| 0011 | |
| 1101 | |
| 0000 | |
| 1000 |
Two key properties pop out immediately. There is only one zero (0000 in 4-bit, since flipping 0000 gives 1111 and adding 1 wraps back to 0000). And the range is asymmetric: the most negative number ( in 4-bit, in 8-bit) has no positive twin. We will see why in a moment.
The Mental Model: Negative MSB Weight
The fastest way to internalize two’s complement is to flip how you read the most significant bit. In unsigned binary, every bit has weight :
In two’s complement, the MSB has negative weight:
That is the entire definition. Every other property is a consequence.
Worked example, 4-bit 1011:
So 1011 in 4-bit two’s complement is . The same bit pattern in unsigned is . Two interpretations, one set of bits — that is the whole game.
The Range
For n bits, the range is:
| Width | Range |
|---|---|
| 4-bit | |
| 8-bit | |
| 16-bit | |
| 32-bit | |
| 64-bit |
The asymmetry is structural. The MSB alone (1000...0) gives the most negative value: . There is no bit pattern with the same magnitude but positive — it would require and the lower bits to sum to , which is one greater than the lower bits can produce. That is why in 8-bit has no positive twin.
How to Negate: Flip Bits, Add One
The negation rule for two’s complement is:
Where is the bitwise inversion of (every 0 becomes 1, every 1 becomes 0 — exactly what a NOT gate does, applied bit by bit).
Why This Works
If you read the encoding formula above for and again for , every bit’s contribution flips sign. Add the two:
Wait — that is only the algebra for the MSB term. Let me be more careful. For each lower bit, , so those bits contribute to the sum. For the MSB, the unsigned sum of , but in two’s-complement reading the MSB has weight . The total:
So , which means , which means . Done.
Worked Examples (4-bit)
| Bits | ||||
|---|---|---|---|---|
| 0101 | 1010 | 1011 | ||
| 0001 | 1110 | 1111 | ||
| 0000 | 1111 | 0000 (overflow) | ✓ | |
| 1000 | 0111 | 1000 | — see below |
The last row is the asymmetric-range gotcha. should be , but does not fit in 4-bit two’s complement (the range tops out at ). The negation operation overflows silently and you get back. Real CPUs typically set an overflow flag in this case; the result is still wrong if you use it.
This connects directly to the XOR gate: the bitwise NOT can be implemented with XOR(x, 1111...1), which is how subtractor circuits often invert their second operand — XOR each B bit with a global “subtract” control signal.
Addition: Why Sign Doesn’t Matter
Here is the headline property. For two’s complement, the same n-bit adder produces the correct result whether you treat the operands as signed or unsigned (as long as the result fits in n bits). The only thing that differs is how you interpret the result and how you detect overflow.
Worked Example:
In 4-bit two’s complement:
7 = 0 1 1 1
-3 = 1 1 0 1
---------
1 0 1 0 0
↑
carry-out (discarded)
Reading the bottom 4 bits: 0100 = . Correct.
The carry-out of the MSB is discarded. In unsigned arithmetic, that carry would indicate a wrap-around; in signed arithmetic, it does not signal overflow on its own — we will get to overflow detection in a moment.
Why It Works
Two’s-complement addition is unsigned addition modulo . The encoding formula maps each signed value to an equivalent residue class mod :
Adding two residues mod is the same operation regardless of the underlying interpretation. So a full adder chain gives correct results for both signed and unsigned values, with the proviso that the result must fit in the same n bits — otherwise overflow rears its head.
This is precisely why the 4-bit ripple-carry adder doubles as a signed adder with no modifications.
Subtraction: Add the Negative
If addition handles signed and unsigned uniformly, subtraction is just . To compute , we flip the bits and add 1. Most ALUs implement this efficiently:
- Invert B by feeding it through XOR gates with a “subtract” control wire.
- Force the carry-in to 1 when subtracting — this adds the “+1” needed for two’s-complement negation.
That is the complete subtractor. There is no separate subtractor circuit; there is an adder with an XOR bank on one input and the carry-in tied to the subtract wire. We described this in the ALU section of Build a CPU from Scratch in a Simulator, and it is the implementation pattern used in essentially every commercial chip.
Worked Example:
5 = 0 1 0 1
3 = 0 0 1 1
3 inverted = 1 1 0 0
add 5 + 1100 + carry-in 1:
0 1 0 1
1 1 0 0
+ 1
---------
1 0 0 1 0
↑
carry-out (discarded)
Result: 0010 = . Correct.
Sign Extension: Preserving Value Across Widths
When a value moves from a narrow register to a wider one — say, an 8-bit value loaded into a 16-bit accumulator — the unused upper bits must be filled with something. For two’s complement, the rule is sign extension: copy the MSB into all the upper bits.
| 8-bit | Sign-extended to 16-bit |
|---|---|
0000 0101 () | 0000 0000 0000 0101 () |
1111 1011 () | 1111 1111 1111 1011 () |
1000 0000 () | 1111 1111 1000 0000 () |
Verify the second row by reading the 16-bit value with the encoding formula:
With all upper bits 1 except bit 2, the positive-weight sum is . Then the total is . Correct.
Sign extension contrasts with zero extension (used for unsigned values), which fills the upper bits with 0 regardless of the source MSB. CPUs typically have separate load instructions for each — the MIPS lb (load byte, sign-extend) versus lbu (load byte unsigned, zero-extend), or x86’s MOVSX versus MOVZX.
Overflow Detection: One XOR Tells the Truth
Signed overflow happens when the true mathematical result of an addition or subtraction does not fit in the n-bit signed range. For two’s-complement addition, there is a clean detection rule:
Overflow occurs if and only if the carry into the MSB differs from the carry out of the MSB.
Equivalently, , where is the carry into bit (the MSB) and is the carry out of bit .
That is one XOR gate. This is why the upcoming CPU flags register post can describe the V (overflow) flag in a single sentence.
Why This Works
Think about what overflow means for signed addition. The two operands have a sign each (positive or negative). Two cases:
- Same sign. Both positive or both negative. The true result has the same sign — but if the magnitudes sum past the n-bit range, the result wraps to the opposite sign. That wrap is detectable: when both operands are positive, the carry into the MSB is 1 (because adding two positive bottom-halves can overflow to the MSB) but the carry out is 0 (both MSBs are 0). When both are negative, vice versa. Either way, .
- Different signs. The true result fits within the range no matter what (the magnitudes can only cancel, not amplify). And in this case, always.
A truth table makes it concrete (4-bit, focusing on MSB bits and the two relevant carries):
| Op A MSB | Op B MSB | Result MSB | Overflow? | ||
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | no |
| 0 | 0 | 1 | 1 | 0 | yes |
| 1 | 1 | 0 | 0 | 1 | yes |
| 1 | 1 | 1 | 1 | 1 | no |
| 0 | 1 | 0 | 1 | 0 | no |
| 0 | 1 | 1 | 0 | 1 | no |
The “yes” rows are exactly those with .
Worked Examples (4-bit, range )
| Operation | Bits | Result | Carry-in to MSB | Carry-out of MSB | Overflow |
|---|---|---|---|---|---|
| (out of range) | (wrong) | 1 | 0 | yes | |
| (out of range) | (wrong) | 0 | 1 | yes | |
| 1 | 1 | no | |||
| 0 | 0 | no |
Note that signed overflow and unsigned carry are different. Adding and as unsigned 4-bit values gives 8, which is in range and produces no carry-out. Adding them as signed values overflows because exceeds . The same bit pattern, two different verdicts. The CPU keeps both as separate flags — C for unsigned, V for signed — and lets the program pick the right one.
Two’s Complement and Boolean Algebra
Two’s complement is, at heart, a number system imposed on top of a bit field. The bit-level operations that implement it — XOR for inversion, AND for masking, OR for combining — are pure Boolean algebra. The “+1” step in negation is a chain of half-adders. Every step you see in this post can be expressed in the same language as a truth table.
That equivalence matters because it makes signed arithmetic cheap. A CPU that already has an unsigned adder, an XOR bank, and a control wire to flip operand B gets signed arithmetic for free — no additional silicon. That is the reason every commercial CPU since the late 1960s has used two’s complement, and it is the reason you can write int x = -5; in C and trust that the compiler does not need a 30-instruction subroutine to handle the negative.
Floating-Point: A Different Beast
Two’s complement covers integers. Real-valued arithmetic — fractions, irrationals, scientific notation — uses an entirely different encoding called IEEE 754, which keeps a separate sign bit, a biased exponent, and a normalized mantissa. We will work through that encoding in the upcoming IEEE 754 Floating Point: From Bits to Numbers post. The short version: floating-point’s sign-magnitude-style approach makes sense there because comparisons benefit from the lexicographic ordering it produces, and the hardware cost of the special “subtract magnitudes” path is paid once inside the FPU rather than on every integer add.
Summary Table: Every Negation Rule You Need
| Quantity | Rule |
|---|---|
| n-bit range | |
| Most negative | 1000...0 () — no positive twin |
| Negation | |
| Subtraction | |
| Sign extension | Copy MSB into all upper bits |
| Zero extension | Fill upper bits with 0 (unsigned) |
| Carry flag (C) | Carry out of MSB — meaningful only for unsigned |
| Overflow flag (V) | — meaningful only for signed |
| Negative flag (N) | MSB of result — meaningful only for signed |
| Zero flag (Z) | Result is all zeros — meaningful for both |
Try It Yourself
Open the 4-bit ALU demonstration template in DigiSim. Set the operation to ADD. Wire () and (). Step the clock and read the result: . Now try () and () — the result will be , with the V flag asserted. The same circuit gives a correct unsigned answer () and an incorrect signed answer (); the V flag tells you which interpretation is broken.
For the connecting context — how the flags ride along into the CPU’s control flow, and how branch instructions read them — head to CPU Flags Register: Carry, Zero, Overflow, Sign next. After that, the natural follow-up is How an ALU Works: Arithmetic Logic Unit from Gates, which breaks down the ALU’s internals at the gate level and shows where exactly the C, V, N, and Z signals come out. The deepest follow-up — once you are ready to leave integers behind — is IEEE 754 Floating Point: From Bits to Numbers.
The core takeaway: two’s complement is not a “trick.” It is the only encoding that lets a single adder serve both signed and unsigned operands while keeping zero unique. Once you accept the negative MSB weight, every other property in this post falls out as basic algebra. Components like the ALU and the ADDER can stay simple precisely because the encoding is doing the heavy lifting.