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.
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.
| Bits | Unsigned | Signed (2's complement) |
|---|---|---|
| 0111 | 7 | 7 |
| 1000 | 8 | −8 |
| 1011 | 11 | −5 |
| 1111 | 15 | −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.