Digital Systems 3 min read

1's and 2's complement

A register has no minus sign, so negative numbers are a convention on the bits. 1's complement flips every bit; 2's complement flips them and adds 1, which removes the double zero and lets one adder handle both signs.

Left, 1's complement: 0101 (5) is inverted to 1010, which means -5, but 0000 and 1111 are both zero. Right, 2's complement: 0101 is inverted to 1010 and then 1 is added, giving 1011, which means -5, with a single zero and a range of -8 to 7.
Same value, −5, in 4 bits: 1's complement keeps two zeros; 2's complement has one.

Flip the bits: 1's complement

To negate a number, invert every bit. In 4 bits, 5 is 0101, so −5 is 1010. It is easy to build, but it has two zeros: 0000 and 1111 (+0 and −0). Arithmetic also needs a correction step, called the end-around carry, so almost no one uses it today.

Flip and add 1: 2's complement

Invert the bits, then add 1: 0101 becomes 1010, then 1011. That is −5. Read it back with the top bit weighted negatively: in 4 bits the place values are −8, 4, 2 and 1, so 1011 is −8 + 2 + 1 = −5.

There is exactly one zero, and the range is lopsided by one: n bits hold -(2^(n-1)) to 2^(n-1) - 1. For 8 bits that is −128 to 127.

BitsUnsignedSigned (2's complement)
011177
10008−8
101111−5
111115−1

Why hardware likes it

An adder does not care about signs. Adding 0101 and 1011 gives 1 0000; drop the carry out of the top and the result is 0000, so 5 + (−5) = 0. The same adder serves signed and unsigned values, which is why a CPU needs only one.

int8_t  a = -5;           // stored as 0xFB
uint8_t b = (uint8_t)a;   // 251: the same bits, read as unsigned

The bits never change, only how you read them. A uint8_t that goes below 0 wraps to 255, which is the same bit pattern as −1.