1. Two's complement gives the top bit a negative place value
A bus holds only 0s and 1s, so a negative number has to be a way of reading a pattern. Two's complement is that way: in an n-bit number, bit n−1 is worth −2^(n−1) instead of +2^(n−1), and every other bit keeps its usual place value. In four bits, bit 3 is worth −8. The pattern 1111 is −8 + 4 + 2 + 1 = −1; 1000 is −8; 0111 is 7. The range is −8 to 7.
| Width | Unsigned | Two's complement |
|---|---|---|
| 4 | 0 to 15 | −8 to 7 |
| 8 | 0 to 255 | −128 to 127 |
| n | 0 to 2^n − 1 | −2^(n−1) to 2^(n−1) − 1 |
The experiment starts at 1111 and shows both readings; the place value above bit 3 is −8. Press bits and confirm that the two's complement readout is the sum under the 1s.
Concept experiment · 4-bit
A browser model of the circuit, not a compiled design. The real toolchain runs in the examples.
- Unsigned
- 15
- Two's complement
- -1
- Hex
- 0xF
To negate a number, invert every bit and add 1. Take 5 = 0101: inverting gives 1010, adding 1 gives 1011, and 1011 reads −8 + 2 + 1 = −5. The same two steps turn −5 back into 5. The most negative value is the exception: in four bits, negating 1000 leaves 1000 because +8 is outside the signed range. Widen first if the positive result must be represented.
Two's complement is used because the previous unit's adder needs no change for it: 1111 + 0001 is 10000, and without the fifth bit that is 0000, as −1 + 1 should be.
Check
Read the 4-bit pattern 1010 as two's complement.
2. One pattern, two numbers
The circuit stores the pattern and nothing else. Whether 1111 means 15 or −1 is decided by whoever reads it. In a signed reading the top bit tells the sign, 1 for negative. Patterns whose top bit is 0 mean the same thing either way: 0000 to 0111 are 0 to 7 in both readings.
Where the readings disagree, comparisons disagree too: unsigned, 1000 is larger than 0111; signed, −8 is smaller than 7. Which reading a signal gets is not stored on the bus; it is part of the signal's type or declaration and any explicit conversions, which each language expresses in its own way.
3. Widening keeps the value only if the new bits fit the reading
Moving a 4-bit value onto an 8-bit bus adds four bits on the left, and they have to be given values. Zero extension fills them with 0 and preserves the unsigned reading. Sign extension fills them with copies of the old top bit and preserves the two's complement reading.
When the old top bit is 0, both extensions produce the same pattern and preserve both readings. The table shows a top bit of 1, where the results differ.
| 4-bit pattern | Zero-extended | Sign-extended |
|---|---|---|
| 1010 | 00001010 | 11111010 |
| read unsigned | 10, as before | 250 |
| read signed | 10 | −6, as before |
Going the other way, truncation drops the top bits and keeps the lower ones. The value survives only when every dropped bit was 0 for an unsigned reading, or a copy of the new top bit for a signed one. 17 is 10001 in five bits; kept to four bits it is 0001, which is 1, because the dropped bit was worth 16.
Caution. Truncation is silent in the circuit: four wires carry four bits, and nothing records that a fifth existed. Whether a language warns, refuses or extends automatically when widths differ is that language's rule, taught in its path.
Check
Sign-extend the 4-bit pattern 1010 to 8 bits.
Check
17 is kept in a 4-bit unsigned signal. What value does the signal hold?
4. A lost carry and an overflow are different failures
A carry-out shows that an unsigned sum needs an extra bit; it is not itself an error. 9 + 8 is
1001 + 1000 = 10001; the fifth bit is the last stage's
cout from the previous unit. Kept in four bits the result is 0001, which is 1, and the 16 the carry
stood for is lost.
Signed overflow means that the true sum is outside the signed result range. 7 + 1 is
0111 + 0001 = 1000 with cout = 0, and read signed 1000
is −8: 8 is outside −8 to 7, so two positives summed to a negative. cout says nothing here; signed
overflow shows in the signs, both operands one sign and the result the other.
| Reading | The result did not fit when | What shows it |
|---|---|---|
| Unsigned | the true sum is 2^n or more | cout = 1 |
| Signed | the true sum is outside −2^(n−1) to 2^(n−1) − 1 | operands one sign, result the other |
The fix is the same for both: give the result one more bit than the operands, keeping cout as bit n
for unsigned, or sign-extending both operands by one bit before adding for signed.
Check
A 4-bit signed adder computes 7 + 1 and the result reads −8. What happened?
Check
A 4-bit unsigned adder computes 9 + 8 and stores 1. What is the smallest result width that would have stored 17 correctly?
5. Variation: a width for a signed range
A signal must hold every value from −20 to 20. Both endpoints must fit: −2^(n−1) must be at most −20, and 2^(n−1) − 1 must be at least 20. Five bits give −16 to 15 and miss both endpoints; six bits give −32 to 31. Unsigned, 0 to 20 would fit in five bits; the sign costs one bit.
Check
What is the smallest width that holds every two's complement value from −20 to 20?
6. Summary
- In two's complement the top bit is worth −2^(n−1). n bits hold −2^(n−1) to 2^(n−1) − 1.
- The bus stores the pattern; signed or unsigned interpretation comes from types, declarations and explicit conversions.
- Zero extension preserves the unsigned value, sign extension the signed value. Truncation drops the top bits silently.
- For addition,
coutshows an unsigned sum outside the original width. Signed overflow occurs when same-sign operands produce the opposite sign. An extra result bit preserves the full sum when the operands are extended appropriately before adding.
Every circuit so far has no stored state. The next chapter introduces the register, which holds a value from one clock edge to the next.