In Chapter 1, we represented numbers from 0 to 255 using eight bits, where each bit contributed a positive power of 2. But this only covers the number 0 and positive numbers. How can we efficiently represent negative numbers in binary?
Perhaps a common answer would be something like: “let's treat the leftmost bit as the sign and let the remaining seven bits represent the magnitude.” This sign-magnitude encoding is intuitive, but it does not scale well, as addition does not work seamlessly. For example, adding +3 and −3 in sign-magnitude does not produce zero without special-casing the signs.
So what would be a good alternative? We can keep the same formula, where each bit position contributes a power of 2, but instead we let the leftmost bit contribute a negative power of 2. In an 8-bit representation, the leftmost bit, also called the most significant bit, is bit 7, and it contributes −128 instead of +128. Bits 0–6 continue to contribute their usual positive values. The full conversion formula for an 8-bit signed integer is:
value = −b7·128 + b6·64 + b5·32 + b4·16 + b3·8 + b2·4 + b1·2 + b0·1
Notice the sign change in the first term of this equation! That single sign change is enough to produce any integer from −128 to 127. When bit 7 is off, the formula is identical to the unsigned case, giving 0 to 127. When bit 7 is on, we start from −128 and then add back whatever the lower seven bits contribute: from 0 (giving −128) up to 127 (giving −1). This type of encoding is called two’s complement. The advantage of two’s complement is that +3 and −3 add to 0 directly in binary with no special cases.
The demo below lets us visualize how signed integers are represented in binary using two’s complement.
For a negative value, the sign bit contributes −128 and the remaining bits add back toward zero. For a positive value, the sign bit is off and the lower bits work exactly as in the unsigned case.
In the demo, try the preset −1 to see how all eight bits are on, giving
−128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = −128 + 127 = −1.
We can now better understand the difference between signed and unsigned representations.
In the unsigned case, the all-ones pattern means 255; whereas in the signed case, it means −1.
The bit pattern is identical, but the interpretation changes.
Based on this intuition, we can notice that the range of signed integers is asymmetric: −128 to 127. There are 128 negative values but only 127 positive values (plus zero). The reason is that we split the number line, and the number zero occupies one of the 256 slots, leaving 128 for negatives and only 127 for positives. The all-zeros pattern is 0, not −0, so −128 has no positive counterpart. This asymmetry is why negating the minimum value (−128) overflows and produces −128 again.
This is best understood through a circular diagram. In two’s complement, the integers are arranged in a circle. Moving clockwise adds 1; moving counter-clockwise subtracts 1. The transition from −1 to 0 (crossing the top) is normal and requires no special handling. The transition from 127 to −128 (crossing the bottom) is overflow. The value wraps around to the opposite extreme.
The demo below explores this idea interactively. We'll use a 4-bit pattern for convenience. The range of a signed 4-bit representation is −8 to 7. In the circular diagram below, 0 sits at the top, transitioning from negative to positive numbers. Crossing the bottom means transitioning from 7 to −8.
Ordinary binary addition works out of the box in two’s complement. When we add the bit patterns for +1 and −1 with standard carry rules, we get all-zeros, which represents the number 0. We do not need special hardware or special handling of bit positions and values.
11111111 (−1)
+ 00000001 (+1)
────────
00000000 (0, carry out discarded)
Another key property of two’s complement is that we can find the negative of any number by simply flipping all the bits, then adding 1. “Flipping all the bits” is also the same as taking the bitwise complement, or applying the NOT bitwise operation, which we will cover in the following chapter. For example, take x = 5 (00000101). ˜x is 11111010 = −6. −6 + 1 = −5. In general, if we want −x, we can simply compute it as ˜x + 1, where ˜ means flip all bits. The reason is that x + ˜x is always all-ones (each pair of opposite bits sums to 1), which equals −1 in two’s complement, so ˜x = −x − 1, and therefore ˜x + 1 = −x.
In this chapter, we introduced two’s complement to illustrate how negative integers are encoded in binary. The main takeaway is that, in two’s complement, the sign bit is worth −128 instead of +128. Negative numbers are not stored as “a sign plus a magnitude”. Instead, they are stored as a bit pattern whose value, computed by the exact same formula, happens to be negative.