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.
- Two's complement encodes signed integers so that addition, subtraction works with one shared circuit — no separate subtractor needed.
- An -bit two's complement value equals its unsigned binary reading, except the top bit carries weight instead of .
- 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 () has no positive counterpart and cannot be negated safely.
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 bits assigns to each bit pattern the integer value
Every bit contributes its ordinary positive binary weight except the most significant bit (MSB), called the sign bit, which contributes a negative weight . 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 bits, the representable values run from (pattern ) to (pattern ). Unlike sign-magnitude encoding, there is exactly one representation of zero (), 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 : an -bit two's complement pattern represents the unique integer in that is congruent, mod , 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 and signed addition mod 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 , the pattern `11111011` is
Negation ("two's complement" the verb). To negate a value , invert every bit (forming the one's complement, ) and add 1:
Why this works: for any -bit pattern , its bitwise complement satisfies (each bit pair sums to 1, contributing at every position). Adding 1 to both sides gives . So is, by definition, the additive inverse of modulo — exactly the negation we want under the modular interpretation above.
Subtraction via negation. Since , a subtractor is built from an adder plus a negation: compute , then add to . In hardware this is even cheaper: feed through inverters and set the adder's initial carry-in to 1, which supplies the "+1" for free while the inverters supply .
Sign extension. To widen an -bit two's complement value to bits without changing its numeric value, replicate the sign bit into all new high-order positions:
Why this works: if (non-negative), the value is unchanged because leading zeros never change a binary value. If , the old sign weight was ; replicating extra 1-bits and moving the sign bit to position changes the total weight of the "1" bits from to
a telescoping geometric sum that exactly reproduces the original sign weight. So the value is preserved either way.
Overflow detection. When adding two -bit two's complement numbers, let be the carry into the sign bit position and be the carry out of it. Overflow occurs exactly when
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 ```
Complexity & correctness
Time and space. Reading, negating, or adding an -bit two's complement number takes time on a bit-serial model (each bit is touched a constant number of times) and space to store. In real hardware this 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 to fill the new positions — strictly speaking additional hardware, since every new wire is simply tied to the same sign-bit wire, no computation required. Overflow detection is given the two carry bits already produced by the adder — it is literally a single XOR gate.
Correctness of negation. This was proven above: for any -bit pattern (each of the bit-pairs contributes exactly to the sum, since ), so . This is not a heuristic — it is an algebraic identity that holds for every bit pattern, including all-zeros (, correctly giving ) 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 commutes with the encoding. If and are two's complement values represented by bit patterns , then the true sum , reduced mod into the range , is represented by the bit pattern — the same bit pattern an unsigned adder would produce. Formally: the map from bit patterns to residues mod is a ring homomorphism, and two's complement is just a choice of representative (the "balanced" one, ) instead of the usual unsigned one () for the same ring . 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 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 always in this case.
- Adding two non-negative numbers: overflow occurs iff the true sum reaches 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 ().
- Adding two negative numbers (both sign bits 1): overflow occurs iff the true sum is less than , which happens exactly when the sign-bit addition () fails to generate a carry out despite two 1s ( forces always here — the only way to get is , which is exactly the underflow condition).
Working through all four sign combinations confirms 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 , 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 in 8-bit two's complement and verify against ordinary arithmetic ().
| Step | Value | Bit pattern (8-bit) |
|---|---|---|
| `00010011` | ||
| `00101101` | ||
| Invert bits of | one's complement | `11010010` |
| Add 1 | negation of , i.e. | `11010011` |
| Add `00010011` + `11010011` | 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):
| bit | a | b | carry-in | sum | carry-out |
|---|---|---|---|---|---|
| 0 | 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 | 1 |
| 2 | 0 | 0 | 1 | 1 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 |
| 4 | 1 | 1 | 0 | 0 | 1 |
| 5 | 0 | 0 | 1 | 1 | 0 |
| 6 | 0 | 1 | 0 | 1 | 0 |
| 7 (sign) | 0 | 1 | 0 | 1 | 0 |
Result bits, MSB to LSB: `1 1 1 0 0 1 1 0` = `11100110`. Reading via the value formula: . ✓ Matches exactly. Carry into sign bit , carry out of sign bit : they're equal, so no overflow — correct, since is well within the 8-bit signed range .
A second case that exposes overflow. Compute in 8 bits (true answer , which exceeds the 8-bit signed max of ): ``` 0 1 1 0 0 1 0 0 (100) + 0 0 1 1 0 0 1 0 (50) ```
| bit | a | b | carry-in | sum | carry-out |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | 1 | 0 |
| 2 | 1 | 0 | 0 | 1 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 |
| 4 | 0 | 1 | 0 | 1 | 0 |
| 5 | 1 | 1 | 0 | 0 | 1 |
| 6 (sign) | 1 | 0 | 1 | 0 | 1 |
Wait — recount carefully with 8 bits, bit 7 as sign: `100 = 01100100`, `50 = 00110010`.
| bit | a | b | carry-in | sum | carry-out |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | 1 | 0 |
| 2 | 1 | 0 | 0 | 1 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 |
| 4 | 0 | 1 | 0 | 1 | 0 |
| 5 | 1 | 1 | 0 | 0 | 1 |
| 6 | 1 | 0 | 1 | 0 | 1 |
| 7 (sign) | 0 | 0 | 1 | 1 | 0 |
Result: `1 0 0 1 0 1 1 0` = `10010110`. Carry into sign bit , carry out of sign bit : they differ, so overflow is correctly flagged. Reading the raw pattern as signed gives , nonsense as "" — exactly the garbage that motivates checking the overflow flag before trusting a signed sum.
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Negating the minimum value () | is not representable in -bit two's complement (max positive is ); negation silently wraps back to the same bit pattern | Detect this as a distinct overflow case; never assume is representable for all |
| Comparing signed values using unsigned comparison hardware | The bit pattern for (`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 arithmetic | Zero-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 shift | Logical shift fills with 0s, destroying the sign of a negative number | Use arithmetic shift (replicate sign bit) for signed division-like operations |
| Assuming overflow can be detected from the result's sign alone | A same-sign result can still be produced when overflow occurs, in certain multi-operand chains | Always check 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
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.
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 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 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 , the word size, and hold identically for every . A 32-bit int and a 64-bit long both follow ; only the range 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 , but that residue, reinterpreted as a signed value in , no longer equals the true mathematical sum. The overflow flag () 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 -bit two's complement pattern represents the unique integer in congruent to its unsigned value modulo . Addition and subtraction of two's complement numbers are literally addition and subtraction in the ring , just using a "balanced" (centered around zero) choice of representative rather than the usual 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 positions is meant to approximate division by . 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 — 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.