Two's Complement Integers

A calculator needs to compute `5 - 8` and get `-3`. It must do this using only circuits that can add. This is not a small problem. Every processor on Earth performs subtraction, comparison, and negative-number arithmetic. Yet its arithmetic logic unit physically knows how to do almost nothing but binary addition. The bridge between "hardware that only adds" and "software that subtracts, compares, and reasons about negative quantities" is a single encoding trick called two's complement. Two's complement is the way essentially every modern computer represents signed integers in binary. It is not the only possible scheme. Sign-magnitude and one's complement are simpler to describe. But two's complement is the one that survived. It survived because it lets addition, subtraction, and even multiplication circuits ignore the sign of their operands almost entirely. A single adder circuit can add two positive numbers, add two negative numbers, or add a positive and a negative number. It gets the correct signed result every time, with no special-casing. This article builds two's complement from first principles. It explains how the encoding is defined. It explains why negation is "flip the bits and add one." It shows how sign extension lets a small number widen into a larger register without changing its value. It also shows how overflow is detected from nothing more than a pair of carry bits. Every claim here is checked against the arithmetic, not just asserted. This matters because the entire point of two's complement is that it makes hardware correctness a matter of clean modular arithmetic. That argument is worth seeing in full. The payoff is conceptual leverage. Once you understand two's complement, some things become almost inevitable. The design of the Ripple-Carry Adder and the arithmetic unit inside an Arithmetic Logic Unit both follow naturally. The same adder that computes `a + b` computes `a - b` by inverting `b` and adding one. This single idea is why processors do not need separate subtractor circuits.

Key takeaways
  • Two's complement encodes signed integers so that addition, subtraction works with one shared circuit — no separate subtractor needed.
  • An nn-bit two's complement value equals its unsigned binary reading, except the top bit carries weight −2n−1-2^{n-1} instead of +2n−1+2^{n-1}.
  • Negation is computed by inverting every bit and adding 1 — a direct consequence of the modular arithmetic definition.
  • Sign extension replicates the sign bit into new high-order positions, preserving value when widening a register.
  • Overflow occurs exactly when the carry into the sign bit differs from the carry out of it — a simple, checkable condition.
  • The scheme has one asymmetry: the most negative number (−2n−1-2^{n-1}) has no positive counterpart and cannot be negated safely.
The core insight

Two's complement makes the sign of a number "just another bit of ordinary binary place value," so that addition circuitry automatically produces correct results for both positive and negative operands.

The idea

A two's complement representation with nn bits assigns to each bit pattern bn−1bn−2⋯b1b0b_{n-1}b_{n-2}\cdots b_1b_0 the integer value

value=−bn−1⋅2n−1+∑i=0n−2bi⋅2i.\text{value} = -b_{n-1}\cdot 2^{n-1} + \sum_{i=0}^{n-2} b_i \cdot 2^i.

Every bit contributes its ordinary positive binary weight except the most significant bit (MSB), called the sign bit, which contributes a negative weight −2n−1-2^{n-1}. This single sign-flip on one weight is the entire definition — everything else in this article is a consequence of that formula. Two important ranges follow immediately. With nn bits, the representable values run from −2n−1-2^{n-1} (pattern 1000…01000\ldots0) to 2n−1−12^{n-1}-1 (pattern 0111…10111\ldots1). Unlike sign-magnitude encoding, there is exactly one representation of zero (000…0000\ldots0), which removes an entire class of hardware special cases. This asymmetric range — one more negative number than positive — is the price paid for that convenience, and it is the source of the single dangerous edge case discussed later. The encoding is equivalent to arithmetic modulo 2n2^n: an nn-bit two's complement pattern represents the unique integer in [−2n−1,2n−1−1][-2^{n-1}, 2^{n-1}-1] that is congruent, mod 2n2^n, to the pattern's unsigned binary value. This modular view is what justifies every hardware shortcut in the rest of the article: addition circuits can add bit patterns as if they were unsigned, because unsigned addition mod 2n2^n and signed addition mod 2n2^n are literally the same operation on the same bits — only the interpretation of the result differs.

How it works

Reading a two's complement number. Apply the weighted-sum formula directly. For n=8n=8, the pattern `11111011` is

−1⋅128+1⋅64+1⋅32+1⋅16+1⋅8+0⋅4+1⋅2+1⋅1=−128+123=−5.-1\cdot128 + 1\cdot64+1\cdot32+1\cdot16+1\cdot8+0\cdot4+1\cdot2+1\cdot1 = -128+123 = -5.

Negation ("two's complement" the verb). To negate a value xx, invert every bit (forming the one's complement, xˉ=2n−1−x\bar{x} = 2^n - 1 - x) and add 1:

−x≡xˉ+1(mod2n).-x \equiv \bar{x} + 1 \pmod{2^n}.

Why this works: for any nn-bit pattern xx, its bitwise complement xˉ\bar x satisfies x+xˉ=2n−1x + \bar x = 2^n - 1 (each bit pair b,bˉb, \bar b sums to 1, contributing 2i2^i at every position). Adding 1 to both sides gives x+(xˉ+1)=2n≡0(mod2n)x + (\bar x + 1) = 2^n \equiv 0 \pmod {2^n}. So xˉ+1\bar x + 1 is, by definition, the additive inverse of xx modulo 2n2^n — exactly the negation we want under the modular interpretation above.

Subtraction via negation. Since a−b=a+(−b)a - b = a + (-b), a subtractor is built from an adder plus a negation: compute bˉ+1\bar b + 1, then add to aa. In hardware this is even cheaper: feed bb through inverters and set the adder's initial carry-in to 1, which supplies the "+1" for free while the inverters supply bˉ\bar b.

Sign extension. To widen an nn-bit two's complement value to m>nm > n bits without changing its numeric value, replicate the sign bit into all new high-order positions:

bn−1bn−1⋯bn−1 bn−1bn−2⋯b0.b_{n-1}b_{n-1}\cdots b_{n-1}\,b_{n-1}b_{n-2}\cdots b_0.

Why this works: if bn−1=0b_{n-1}=0 (non-negative), the value is unchanged because leading zeros never change a binary value. If bn−1=1b_{n-1}=1, the old sign weight was −2n−1-2^{n-1}; replicating kk extra 1-bits and moving the sign bit to position m−1m-1 changes the total weight of the "1" bits from −2n−1-2^{n-1} to

−2m−1+2m−2+⋯+2n−1=−2n−1,-2^{m-1} + 2^{m-2} + \cdots + 2^{n-1} = -2^{n-1},

a telescoping geometric sum that exactly reproduces the original sign weight. So the value is preserved either way.

Overflow detection. When adding two nn-bit two's complement numbers, let cn−1c_{n-1} be the carry into the sign bit position and cnc_n be the carry out of it. Overflow occurs exactly when

cn−1≠cn.c_{n-1} \ne c_n.

Equivalently: overflow happens if and only if two operands with the same sign produce a result with the opposite sign (adding two positives can't overflow to a negative unless it wrapped; adding two numbers of different sign can never overflow, since the true sum lies between them). ``` // Two's complement core operations, n-bit words function VALUE(bits[0..n-1]): // bits[n-1] is sign bit v = -bits[n-1] 2^(n-1) for i in 0 .. n-2: v = v + bits[i] 2^i return v function NEGATE(bits[0..n-1]): for i in 0 .. n-1: bits[i] = NOT bits[i] // one's complement return ADD(bits, ONE) // add 1, mod 2^n function ADD(a[0..n-1], b[0..n-1]): carry = 0 for i in 0 .. n-1: sum_i = a[i] XOR b[i] XOR carry carry = (a[i] AND b[i]) OR (carry AND (a[i] XOR b[i])) result[i] = sum_i carry_out = carry // carry out of MSB, c_n return result, carry_out function SUBTRACT(a[0..n-1], b[0..n-1]): return ADD(a, NEGATE(b)) function ADD_WITH_OVERFLOW(a[0..n-1], b[0..n-1]): // track carry into MSB (c_{n-1}) and carry out of MSB (c_n) carry = 0 for i in 0 .. n-2: result[i] = a[i] XOR b[i] XOR carry carry = (a[i] AND b[i]) OR (carry AND (a[i] XOR b[i])) carry_into_msb = carry // c_{n-1} result[n-1] = a[n-1] XOR b[n-1] XOR carry_into_msb carry_out_msb = (a[n-1] AND b[n-1]) OR (carry_into_msb AND (a[n-1] XOR b[n-1])) // c_n overflow = (carry_into_msb XOR carry_out_msb) return result, overflow function SIGN_EXTEND(bits[0..n-1], m): // widen n bits to m > n bits new_bits = array of size m for i in 0 .. n-1: new_bits[i] = bits[i] for i in n .. m-1: new_bits[i] = bits[n-1] // replicate sign bit return new_bits ```

Figure
Bit weights and the sign-bit flip in an 8-bit two's complement word
8-bit two's complement: 1 1 1 1 1 0 1 1 = -5 1 1 1 1 1 0 1 1 -128 +64 +32 +16 +8 +4 +2 +1 sign bit -128 + 64+32+16+8+0+2+1 = -128 + 123 = -5 Every bit has ordinary positive place value except bit 7, whose weight is negated. This single sign flip is the entire definition of two's complement.

Complexity & correctness

Time and space. Reading, negating, or adding an nn-bit two's complement number takes O(n)O(n) time on a bit-serial model (each bit is touched a constant number of times) and O(n)O(n) space to store. In real hardware this O(n)O(n) chain of carries is exactly the Ripple-Carry Adder's critical path, which is why faster adder designs (carry-lookahead, carry-select) exist — they reduce the depth of this dependency chain without changing the asymptotic bit count. Sign extension is O(m−n)O(m-n) to fill the new positions — strictly speaking O(1)O(1) additional hardware, since every new wire is simply tied to the same sign-bit wire, no computation required. Overflow detection is O(1)O(1) given the two carry bits already produced by the adder — it is literally a single XOR gate.

Correctness of negation. This was proven above: x+xˉ=2n−1x + \bar x = 2^n - 1 for any nn-bit pattern (each of the nn bit-pairs contributes exactly 2i2^i to the sum, since bi+bˉi=1b_i + \bar b_i = 1), so xˉ+1≡−x(mod2n)\bar x + 1 \equiv -x \pmod{2^n}. This is not a heuristic — it is an algebraic identity that holds for every bit pattern, including all-zeros (x=0⇒xˉ+1=2n≡0x=0 \Rightarrow \bar x + 1 = 2^n \equiv 0, correctly giving −0=0-0=0) and the boundary pattern discussed in the pitfalls below.

Correctness of unsigned-style addition on signed values. The deep reason two's complement works is that addition mod 2n2^n commutes with the encoding. If xx and yy are two's complement values represented by bit patterns X,YX, Y, then the true sum x+yx+y, reduced mod 2n2^n into the range [−2n−1,2n−1−1][-2^{n-1}, 2^{n-1}-1], is represented by the bit pattern (X+Y)  2n(X+Y) \bmod 2^n — the same bit pattern an unsigned adder would produce. Formally: the map from bit patterns to residues mod 2n2^n is a ring homomorphism, and two's complement is just a choice of representative (the "balanced" one, [−2n−1,2n−1−1][-2^{n-1}, 2^{n-1}-1]) instead of the usual unsigned one ([0,2n−1][0, 2^n-1]) for the same ring Z/2nZ\mathbb{Z}/2^n\mathbb{Z}. Addition, subtraction, and multiplication in this ring are representative-independent, which is exactly why one adder circuit serves both signed and unsigned interpretations.

Correctness of overflow detection. Overflow means the true mathematical sum falls outside [−2n−1,2n−1−1][-2^{n-1}, 2^{n-1}-1] and so cannot be represented. A case analysis on the signs of the operands shows:

  • Adding a non-negative and a non-positive number: the true sum lies between the two operands, both already in range, so it cannot overflow. This matches cn−1=cnc_{n-1}=c_n always in this case.
  • Adding two non-negative numbers: overflow occurs iff the true sum reaches 2n−12^{n-1} or more, which happens exactly when a carry is generated out of the sign bit while none was needed in sign-bit addition of two zero sign-bits (cn−1=0,cn=1c_{n-1}=0, c_n=1).
  • Adding two negative numbers (both sign bits 1): overflow occurs iff the true sum is less than −2n−1-2^{n-1}, which happens exactly when the sign-bit addition (1+1+cn−11+1+c_{n-1}) fails to generate a carry out despite two 1s (cn−1=1c_{n-1}=1 forces cn=1c_n=1 always here — the only way to get cn−1≠cnc_{n-1}\ne c_n is cn−1=0,cn=1c_{n-1}=0,c_n=1, which is exactly the underflow condition).

Working through all four sign combinations confirms cn−1⊕cnc_{n-1} \oplus c_n is true precisely on overflow and false otherwise — this is the standard proof presented in every computer-architecture treatment of the ALU's overflow flag, and it is why the flag costs one gate.

Tradeoffs. Two's complement is optimal for addition-based hardware reuse, but it is not symmetric: negation of the minimum value fails (see pitfalls), and right-shifting a negative number is not the same as dividing by a power of two in the naive sense (arithmetic shift rounds toward −∞-\infty, not toward zero). Sign-magnitude, by contrast, negates trivially (flip one bit) but requires separate addition/subtraction logic and has two zeros — a worse tradeoff for hardware, which is precisely why two's complement won.

Worked example

Compute 19−4519 - 45 in 8-bit two's complement and verify against ordinary arithmetic (19−45=−2619-45=-26).

StepValueBit pattern (8-bit)
19191919`00010011`
45454545`00101101`
Invert bits of 4545one's complement`11010010`
Add 1negation of 4545, i.e. −45-45`11010011`
Add `00010011` + `11010011`19+(−45)19 + (-45)see below

Adding `00010011` (19) and `11010011` (−45) bit by bit, right to left, tracking carry: ``` 0 0 0 1 0 0 1 1 (19) + 1 1 0 1 0 0 1 1 (-45) ------------------- carry: 0 1 1 1 1 0 1 1 0 (c7 c6 c5 c4 c3 c2 c1 c0, generated right→left) sum : 1 1 1 0 0 1 1 0 ``` Let's carry this out carefully column by column (bit 0 is rightmost):

bitabcarry-insumcarry-out
011001
111111
200110
300000
411001
500110
601010
7 (sign)01010

Result bits, MSB to LSB: `1 1 1 0 0 1 1 0` = `11100110`. Reading via the value formula: −128+64+32+0+0+4+2+0=−128+102=−26-128 + 64+32+0+0+4+2+0 = -128+102 = -26. ✓ Matches 19−45=−2619-45=-26 exactly. Carry into sign bit c6=0c_6 = 0, carry out of sign bit c7=0c_7 = 0: they're equal, so no overflow — correct, since −26-26 is well within the 8-bit signed range [−128,127][-128,127].

A second case that exposes overflow. Compute 100+50100 + 50 in 8 bits (true answer 150150, which exceeds the 8-bit signed max of 127127): ``` 0 1 1 0 0 1 0 0 (100) + 0 0 1 1 0 0 1 0 (50) ```

bitabcarry-insumcarry-out
000000
101010
210010
300000
401010
511001
6 (sign)10101

Wait — recount carefully with 8 bits, bit 7 as sign: `100 = 01100100`, `50 = 00110010`.

bitabcarry-insumcarry-out
000000
101010
210010
300000
401010
511001
610101
7 (sign)00110

Result: `1 0 0 1 0 1 1 0` = `10010110`. Carry into sign bit c6=1c_6=1, carry out of sign bit c7=0c_7=0: they differ, so overflow is correctly flagged. Reading the raw pattern as signed gives −128+16+4+2=−106-128+16+4+2=-106, nonsense as "100+50100+50" — exactly the garbage that motivates checking the overflow flag before trusting a signed sum.

Common pitfalls & traps

PitfallWhy it happensFix
Negating the minimum value (−2n−1-2^{n-1})∣−2n−1∣=2n−1\lvert -2^{n-1}\rvert = 2^{n-1} is not representable in nn-bit two's complement (max positive is 2n−1−12^{n-1}-1); negation silently wraps back to the same bit patternDetect this as a distinct overflow case; never assume ∣x∣|x| is representable for all xx
Comparing signed values using unsigned comparison hardwareThe bit pattern for −1-1 (`11111111`) is numerically larger as an unsigned value than `00000001` (1)Use a signed comparator, or invert the sign bit before an unsigned compare
Forgetting to sign-extend before widening arithmeticZero-extending a negative number changes its value (turns it positive)Always sign-extend signed operands, zero-extend unsigned ones — the two are different operations
Confusing arithmetic and logical right shiftLogical shift fills with 0s, destroying the sign of a negative numberUse arithmetic shift (replicate sign bit) for signed division-like operations
Assuming overflow can be detected from the result's sign aloneA same-sign result can still be produced when overflow occurs, in certain multi-operand chainsAlways check cn−1⊕cnc_{n-1}\oplus c_n from the adder, not the output pattern alone

Pioneers & history

Two's complement arithmetic predates electronic computers: mechanical calculating machines used "complement" tricks to turn subtraction into addition on adding machines long before transistors existed. Its formalization for binary electronic computers is closely tied to the design decisions behind the earliest stored-program machines.

Origins · 1 minJohn von Neumann and how this was discovered
JV
John von Neumann
1903–1957 · Institute for Advanced Study, mid-20th-century stored-program computer design

Von Neumann's 1945 "First Draft of a Report on the EDVAC" articulated the architectural principles — including binary arithmetic representations — that shaped how early digital computers encoded numbers, laying groundwork for the complement-based schemes that followed.

History
1945
— Von Neumann's EDVAC report discusses binary representations for a stored-program computer.
1949
— EDSAC and early binary machines experiment with complement arithmetic to avoid separate subtractor circuits.
1951
— UNIVAC I era: various computers adopt one's complement or two's complement schemes for signed arithmetic in hardware.
1960
s — Two's complement becomes the dominant convention across minicomputers as hardware designers favor its single-zero, single-adder efficiency.
1985
— The IEEE 754 floating-point standard and prevailing processor ISAs (including early x86 and MIPS) cement two's complement as the near-universal integer format, still standard today.

Where this matters

Every general-purpose CPU — x86, ARM, RISC-V, MIPS, PowerPC — represents signed integers in two's complement in its registers and memory. The C, C++, Java, and Rust `int` types are defined (or, in practice, universally implemented) using two's complement semantics, including its wraparound behavior on overflow. Compilers rely on two's complement equivalence to translate `a - b` into `a + (~b + 1)`, reusing the same adder hardware described here — the same circuitry inside an Arithmetic Logic Unit handles addition and subtraction as one operation. Sign extension instructions (`sign extend byte`, `movsx`, etc.) exist in every instruction set specifically to implement the sign-replication rule from this article when a smaller signed type is promoted to a wider register. Overflow flags in condition-code registers, used by conditional branches after arithmetic instructions, are computed by exactly the cn−1⊕cnc_{n-1}\oplus c_n rule derived above. Cryptographic and checksum code that deliberately exploits modular wraparound (e.g., hash mixing functions) also depends on the modular-arithmetic identity underlying two's complement.

Ask the tutor: How does the same two's complement circuitry extend to building a full ripple-carry adder and subtractor in hardware?

Why not just use a sign bit plus magnitude, like ordinary written numbers?

Sign-magnitude stores the sign separately from the absolute value (e.g., `10000101` might mean "negative, magnitude 5"). It's intuitive, but it forces hardware to implement two different addition algorithms: one for same-sign operands (add magnitudes) and one for different-sign operands (subtract magnitudes and figure out which sign wins). It also produces two representations of zero (`+0` and `-0`), complicating comparisons. Two's complement collapses all of this into a single circuit, because addition mod 2n2^n automatically produces the correct signed result regardless of the operands' signs — no special-casing, one adder, one zero.

Does two's complement work the same way for any word size, like 16, 32, or 64 bits?

Yes — the definition, negation rule, sign-extension rule, and overflow condition are all parameterized only by nn, the word size, and hold identically for every n≥1n \ge 1. A 32-bit int and a 64-bit long both follow −bn−12n−1+∑bi2i-b_{n-1}2^{n-1} + \sum b_i 2^i; only the range [−2n−1,2n−1−1][-2^{n-1}, 2^{n-1}-1] changes. This uniformity is why the same adder design, scaled to different widths, works across 8-bit microcontrollers and 64-bit server processors alike.

What happens when a two's complement addition overflows — is the result just wrong, or is it wrong in a predictable way?

It is wrong in a precisely predictable way: the hardware still computes the correct sum modulo 2n2^n, but that residue, reinterpreted as a signed value in [−2n−1,2n−1−1][-2^{n-1},2^{n-1}-1], no longer equals the true mathematical sum. The overflow flag (cn−1⊕cnc_{n-1}\oplus c_n) tells software that the bit pattern should not be trusted as the true signed result. Many languages (C, in particular) treat signed overflow as undefined behavior precisely because relying on the wraparound pattern is fragile and compiler-dependent, even though the hardware behavior itself is deterministic.

How is two's complement related to modular arithmetic?

Directly: an nn-bit two's complement pattern represents the unique integer in [−2n−1,2n−1−1][-2^{n-1}, 2^{n-1}-1] congruent to its unsigned value modulo 2n2^n. Addition and subtraction of two's complement numbers are literally addition and subtraction in the ring Z/2nZ\mathbb{Z}/2^n\mathbb{Z}, just using a "balanced" (centered around zero) choice of representative rather than the usual [0,2n−1][0, 2^n-1] range. This is why the exact same adder circuit computes correct results whether its bit patterns are interpreted as signed or unsigned — the ring operations don't care which representative convention you use.

Why does an arithmetic right shift replicate the sign bit instead of filling with zeros?

A right shift by kk positions is meant to approximate division by 2k2^k. For a negative number, filling the vacated high bits with 0 would turn it positive, destroying the value's sign entirely. Filling with copies of the original sign bit (arithmetic shift) preserves negativity and computes floor division by 2k2^k — consistent with how sign extension preserves value when widening a register, since both operations are "propagate the sign bit outward."

Is two's complement used for floating-point numbers too?

No — floating-point formats like IEEE 754 use a separate sign bit plus a biased exponent and a magnitude-only significand (sign-magnitude style for the fraction), not two's complement. Two's complement is specific to fixed-point signed integers. However, floating-point hardware still often reuses two's complement integer adders internally, for example when adding or comparing exponents, so the two schemes coexist inside the same processor.

Before this

Where this leads