Two's Complement: How Computers Store Negative Numbers in Binary

Two's Complement: How Computers Store Negative Numbers in Binary

Binary is elegant in its simplicity — ones and zeros, on or off, true or false. But that elegance runs headlong into a practical problem: real-world numbers go negative. Temperatures drop below zero. Bank accounts go into overdraft. Altitude readings dip below sea level. From the moment engineers started building digital computers in the 1940s, they needed a way to represent negative values using nothing but bits.

Several schemes were tried. Sign-magnitude notation simply borrowed the leftmost bit as a flag — 0 for positive, 1 for negative — so that 0000 0101 meant +5 and 1000 0101 meant −5. One's complement flipped every bit to negate: 1111 1010 for −5. Both approaches work well enough on paper but create genuine headaches in hardware. They produce two representations of zero (+0 and -0), require the adder circuit to detect the sign bits before deciding what arithmetic to do, and generally make chip designers miserable.

Two's complement sidesteps all of that. It is the encoding that virtually every modern processor — from the ARM chip in your phone to the server cluster running machine-learning workloads — uses to represent signed integers. Once you understand why it works, you will find it hard to imagine anything else making sense.

The Core Idea: Counting Around a Circle

Think of an 8-bit register as a number wheel with 256 positions: 0 at the top, counting clockwise up to 255, then wrapping back to 0. That wrap-around is not a bug — it is the mechanism that makes two's complement work.

In unsigned 8-bit arithmetic, 255 + 1 = 0 (the wheel wraps). If we agree to treat positions 128 through 255 as negative numbers, the wheel becomes a signed number line: 0 to 127 on one half, −128 to −1 on the other. Specifically, the bit pattern 1000 0000 (128 unsigned) becomes −128, and 1111 1111 (255 unsigned) becomes −1.

The payoff is enormous: the same addition circuit that adds unsigned numbers also correctly adds signed ones. No special case, no sign-detection logic. The silicon just counts around the wheel, and the programmer decides whether the result should be interpreted as signed or unsigned.

Calculating Two's Complement: The Mechanics

To negate any number in two's complement, you follow two steps:

  1. Invert all the bits (one's complement).
  2. Add 1 to the result.

Let us work through a concrete example. Take the 8-bit representation of +19:

+19  =  0001 0011

Step 1 — invert every bit:

     →  1110 1100

Step 2 — add 1:

     →  1110 1101

So 1110 1101 is −19 in 8-bit two's complement. You can verify this by adding +19 and −19 together:

  0001 0011   (+19)
+ 1110 1101   (−19)
───────────
  0000 0000   (0, with a carry out that gets discarded)

The carry bit falls off the end of the 8-bit register and disappears. What remains is all zeros — exactly zero. That is the algebraic guarantee two's complement makes: n + (−n) = 0, verified in pure hardware addition with no tricks required.

Reading a Negative Value from Its Bit Pattern

When you are handed a bit pattern and told it is a signed 8-bit integer, the quick diagnostic is the most significant bit (MSB). If it is 0, the number is non-negative and its value is just the normal binary interpretation. If it is 1, the number is negative.

To find the magnitude, apply the same two-step process in reverse (or identically — the procedure is its own inverse):

Suppose you see 1101 0110 and need its signed value.

Step 1 — invert:

1101 0110  →  0010 1001

Step 2 — add 1:

0010 1001  →  0010 1010  =  42

So 1101 0110 represents −42. You can double-check by confirming that 42 + 42 in unsigned 8-bit arithmetic gives you 84 (0101 0100), not 0 — but 0010 1010 + 1101 0110 = 0000 0000 with carry. That checks out.

The Range and the Asymmetry

An important and sometimes surprising property: the range of representable values is not symmetric. For an n-bit two's complement integer, the range is −2n−1 to +2n−1 − 1. In 8-bit arithmetic that means −128 to +127. In 16-bit, −32768 to +32767. In 32-bit, −2,147,483,648 to +2,147,483,647.

There is always one more negative number than positive. The reason is straightforward: zero occupies a slot on the positive side of the wheel. The bit pattern 0000 0000 is unambiguously zero, so the 128 positive slots (including zero) are 0 through 127, while the 128 negative slots run from −1 down to −128.

This asymmetry creates a notorious edge case. What happens when you try to negate −128?

−128 =  1000 0000
Invert: 0111 1111
Add 1:  1000 0000

You get −128 again. There is no +128 in an 8-bit two's complement system — the bit pattern that would represent it is already taken by −128. This is not a quirk to work around; it is a fundamental property of the encoding. Languages like C and C++ actually classify signed integer overflow (including negating INT_MIN) as undefined behavior precisely because of this. In practice, most hardware will silently wrap, but you cannot count on it.

Why Subtraction Becomes Addition

Here is where two's complement earns its reputation. To subtract a number, the CPU simply negates it and adds. Compute 53 − 29:

+29 = 0001 1101
−29 = 1110 0011   (two's complement negation)

  0011 0101   (+53)
+ 1110 0011   (−29)
───────────
  0001 1000   (+24, carry discarded)

53 − 29 = 24. No subtraction circuit needed — the adder handles it. This is why early processors could be built with surprisingly simple arithmetic units. The ALU's subtraction mode is literally just negation followed by addition, and negation costs only one inversion and an increment.

Sign Extension: Widening a Value

When you promote a signed integer to a wider type — say, a signed 8-bit value into a 16-bit register — two's complement specifies exactly how to do it: replicate the sign bit into all the new high-order positions. This is called sign extension.

Take −5 in 8 bits: 1111 1011. In 16 bits:

1111 1111 1111 1011

The value is still −5. All those leading ones are not garbage — they are carrying forward the information that this is a negative number. Contrast with zero-extension, which applies to unsigned values and fills new bits with zeros. Getting these confused in code (classic C pitfall: casting a char to an int when the platform has an unsigned char) produces subtle bugs that can take hours to trace.

Two's Complement in Real Tools

Binary, hex, and decimal converters that handle signed integers all hinge on this representation. When a number-base converter shows you that 0xFFFFFFFF as a signed 32-bit integer equals −1, it is applying two's complement interpretation. When a timestamp converter tells you that Unix epoch offset -86400 means "one day before January 1, 1970," the arithmetic the system did to get there — subtracting a day's worth of seconds — used two's complement internally. Color data, pixel channels, audio samples in signed PCM format — two's complement is the substrate under essentially all of it.

Debugging embedded systems or reverse-engineering binary protocols, you will regularly encounter hex dumps where values look enormous — 0xFFFE, 0xFF80 — but actually represent small negative numbers. Recognizing the MSB pattern and knowing the invert-and-add-one procedure turns what looks like noise into legible data instantly.

The One Thing Most Explanations Skip

Most tutorials stop at the mechanical procedure. What they omit is the deeper insight: two's complement works because −x and (2n − x) produce identical bit patterns when stored in an n-bit register. The modular arithmetic of finite-width registers and the concept of additive inverses align perfectly. You are not really storing a negative number — you are storing a positive number whose arithmetic behavior, under modular addition, is indistinguishable from what a negative number would do. The interpretation is a human agreement layered on top of a beautifully simple mathematical reality.

That is the deeper reason one arithmetic circuit handles both signed and unsigned math: there is no difference at the bit level. The difference is entirely in how you read the result.

Once that clicks, the whole landscape of binary arithmetic — overflow flags, signed versus unsigned comparisons, bitwise tricks for fast absolute values — becomes not a collection of rules to memorize but a set of consequences following from a single elegant design decision made seven decades ago and never seriously revisited since.