binary-arithmetic

Two's Complement: Signed Binary Arithmetic Explained

Denny Denny
13 min read
Signed binary number representation with sign bit highlighted, positive and negative values wrapping around a circular bit ring.

TL;DR: Two’s complement is the encoding every modern CPU uses for signed integers. The most significant bit carries weight 2n1-2^{n-1} instead of +2n1+2^{n-1}; 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 1-1 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 128-128 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 2i2^i, every weight is positive, and the n-bit range is [0,2n1][0, 2^n - 1]. 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:

BitsSign-Magnitude
0011+3+3
10113-3
0000+0+0
10000-0

Easy to read by eye. Disastrous in hardware. Two problems:

  1. Two zeros. +0+0 and 0-0 are different bit patterns but mathematically equal. Equality circuits get more complex.
  2. Different addition for different sign combinations. Adding +3+3 and 3-3 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:

BitsOne’s-Complement
0011+3+3
11003-3
0000+0+0
11110-0

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:

BitsTwo’s-Complement
0011+3+3
11013-3
0000+0+0
10008-8

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 (8-8 in 4-bit, 128-128 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 2i2^i:

value=i=0n1bi2i\text{value} = \sum_{i=0}^{n-1} b_i \cdot 2^i

In two’s complement, the MSB has negative weight:

value=bn12n1+i=0n2bi2i\text{value} = -b_{n-1} \cdot 2^{n-1} + \sum_{i=0}^{n-2} b_i \cdot 2^i

That is the entire definition. Every other property is a consequence.

Worked example, 4-bit 1011:

123+022+121+120=8+0+2+1=5-1 \cdot 2^3 + 0 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0 = -8 + 0 + 2 + 1 = -5

So 1011 in 4-bit two’s complement is 5-5. The same bit pattern in unsigned is 1111. Two interpretations, one set of bits — that is the whole game.

The Range

For n bits, the range is:

[2n1,  2n11][-2^{n-1},\; 2^{n-1} - 1]

WidthRange
4-bit[8,+7][-8, +7]
8-bit[128,+127][-128, +127]
16-bit[32,768,+32,767][-32{,}768, +32{,}767]
32-bit[2,147,483,648,+2,147,483,647][-2{,}147{,}483{,}648, +2{,}147{,}483{,}647]
64-bit[9.22×1018,+9.22×10181][-9.22 \times 10^{18}, +9.22 \times 10^{18} - 1]

The asymmetry is structural. The MSB alone (1000...0) gives the most negative value: 2n1-2^{n-1}. There is no bit pattern with the same magnitude but positive — it would require bn1=0b_{n-1} = 0 and the lower bits to sum to 2n12^{n-1}, which is one greater than the lower bits can produce. That is why 128-128 in 8-bit has no positive twin.

How to Negate: Flip Bits, Add One

The negation rule for two’s complement is:

x=x+1-x = \overline{x} + 1

Where x\overline{x} is the bitwise inversion of xx (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 xx and again for x\overline{x}, every bit’s contribution flips sign. Add the two:

x+x=12n1+i=0n22i=2n1+(2n11)=1x + \overline{x} = -1 \cdot 2^{n-1} + \sum_{i=0}^{n-2} 2^i = -2^{n-1} + (2^{n-1} - 1) = -1

Wait — that is only the algebra for the MSB term. Let me be more careful. For each lower bit, bi+bi=1b_i + \overline{b_i} = 1, so those bits contribute 2i2^i to the sum. For the MSB, the unsigned sum of bn1+bn1=1b_{n-1} + \overline{b_{n-1}} = 1, but in two’s-complement reading the MSB has weight 2n1-2^{n-1}. The total:

x+x=2n11+i=0n22i1=2n1+2n11=1x + \overline{x} = -2^{n-1} \cdot 1 + \sum_{i=0}^{n-2} 2^i \cdot 1 = -2^{n-1} + 2^{n-1} - 1 = -1

So x+x=1x + \overline{x} = -1, which means x=1x\overline{x} = -1 - x, which means x+1=x\overline{x} + 1 = -x. Done.

Worked Examples (4-bit)

xxBitsx\overline{x}x+1\overline{x} + 1x-x
+5+50101101010115-5
+1+10001111011111-1
00000011110000 (overflow)00
8-81000011110008-8 — see below

The last row is the asymmetric-range gotcha. (8)-(-8) should be +8+8, but +8+8 does not fit in 4-bit two’s complement (the range tops out at +7+7). The negation operation overflows silently and you get 8-8 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: 7+(3)7 + (-3)

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 = +4+4. 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 2n2^n. The encoding formula maps each signed value to an equivalent residue class mod 2n2^n:

xunsigned=xsignedmod2nx_{\text{unsigned}} = x_{\text{signed}} \mod 2^n

Adding two residues mod 2n2^n 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 AB=A+(B)A - B = A + (-B). To compute B-B, we flip the bits and add 1. Most ALUs implement this efficiently:

  1. Invert B by feeding it through XOR gates with a “subtract” control wire.
  2. 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: 535 - 3

   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 = +2+2. 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-bitSign-extended to 16-bit
0000 0101 (+5+5)0000 0000 0000 0101 (+5+5)
1111 1011 (5-5)1111 1111 1111 1011 (5-5)
1000 0000 (128-128)1111 1111 1000 0000 (128-128)

Verify the second row by reading the 16-bit value with the encoding formula:

1215+i=014bi2i-1 \cdot 2^{15} + \sum_{i=0}^{14} b_i \cdot 2^i

With all upper bits 1 except bit 2, the positive-weight sum is (2151)22=2155(2^{15} - 1) - 2^2 = 2^{15} - 5. Then the total is 215+2155=5-2^{15} + 2^{15} - 5 = -5. 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, V=Cn1CnV = C_{n-1} \oplus C_n, where Cn1C_{n-1} is the carry into bit n1n-1 (the MSB) and CnC_n is the carry out of bit n1n-1.

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:

  1. 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, Cn1CnC_{n-1} \neq C_n.
  2. Different signs. The true result fits within the range no matter what (the magnitudes can only cancel, not amplify). And in this case, Cn1=CnC_{n-1} = C_n always.

A truth table makes it concrete (4-bit, focusing on MSB bits and the two relevant carries):

Op A MSBOp B MSBCn1C_{n-1}Result MSBCnC_nOverflow?
00000no
00110yes
11001yes
11111no
01010no
01101no

The “yes” rows are exactly those with Cn1CnC_{n-1} \neq C_n.

Worked Examples (4-bit, range [8,+7][-8, +7])

OperationBitsResultCarry-in to MSBCarry-out of MSBOverflow
5+3=85 + 3 = 8 (out of range)0101+0011=10000101 + 0011 = 10008-8 (wrong)10yes
5+(4)=9-5 + (-4) = -9 (out of range)1011+1100=01111011 + 1100 = 0111+7+7 (wrong)01yes
5+(3)=25 + (-3) = 20101+1101=00100101 + 1101 = 0010+2+211no
7+0=77 + 0 = 70111+0000=01110111 + 0000 = 0111+7+700no

Note that signed overflow and unsigned carry are different. Adding 55 and 33 as unsigned 4-bit values gives 8, which is in range and produces no carry-out. Adding them as signed values overflows because +8+8 exceeds +7+7. 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

QuantityRule
n-bit range[2n1,  2n11][-2^{n-1},\; 2^{n-1} - 1]
Most negative1000...0 (2n1-2^{n-1}) — no positive twin
Negationx=x+1-x = \overline{x} + 1
SubtractionAB=A+B+1A - B = A + \overline{B} + 1
Sign extensionCopy MSB into all upper bits
Zero extensionFill upper bits with 0 (unsigned)
Carry flag (C)Carry out of MSB — meaningful only for unsigned
Overflow flag (V)Cn1CnC_{n-1} \oplus C_n — 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 A=0101A = 0101 (+5+5) and B=1101B = 1101 (3-3). Step the clock and read the result: 0010=+20010 = +2. Now try A=0111A = 0111 (+7+7) and B=0001B = 0001 (+1+1) — the result will be 1000=81000 = -8, with the V flag asserted. The same circuit gives a correct unsigned answer (+8+8) and an incorrect signed answer (8-8); 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.