Two's complement explained
How a byte holds a negative number: sign and magnitude and why it is awkward, two's complement, negating by flipping and adding 1, and what overflow looks like when a value wraps past 127. Four demos pack a robot's sensor readings into bytes, and they all run on the page.
A computer stores every number as a pattern of bits. Eight bits make a byte, and a byte has 256 patterns, from 00000000 to 11111111. If those patterns are read as ordinary binary they mean 0 to 255, and there is nowhere to put a minus sign. But a robot needs negative numbers: a speed in reverse, a turn to the left, a sensor reading below the value it was aiming for.
Two's complement is the rule nearly every processor uses to fit negative numbers into the same bits. This page explains the rule it replaced (sign and magnitude), how to negate a number, and what happens when a value goes past the top of a byte and wraps round. Each demo is a real program running on a simulated BugBot. You can change the numbers and press Run, and the chart under the robot shows what the bytes actually held.
A byte is 256 patterns and nothing more
The bits do not know what they mean. The meaning comes from the rule you agree to read them by. Three rules are worth knowing:
| Rule | What the leftmost bit is worth | Range in 8 bits | Zeros |
|---|---|---|---|
| Unsigned binary | 128 | 0 to 255 | one |
| Sign and magnitude | a minus sign | -127 to 127 | two |
| Two's complement | -128 | -128 to 127 | one |
Every rule gets 256 values, because there are only ever 256 patterns. Making room for negatives does not create new patterns, it only moves the meaning of the ones you have.
Sign and magnitude, and why it is awkward
The obvious idea is to steal the leftmost bit for the sign: 0 for positive, 1 for negative. The other seven bits hold the size of the number, 0 to 127. So 5 is 00000101 and -5 is 10000101.
In this demo the robot drives at the wall and backs off again. The reading it stores is the gap: how far it is from 30 cm, which is positive when it is too far away and negative when it is too close. Each reading is packed into a sign and magnitude byte and read straight back.
The program
from bugbot import *
connect()
# change this and press Run
WANT = 30 # the gap the robot is measuring against, in cm
def pack(n):
# sign and magnitude: the top bit is the sign, the other 7 hold the size
sign = "1" if n < 0 else "0"
return sign + format(min(abs(n), 127), "07b")
def unpack(bits):
size = int(bits[1:], 2)
return -size if bits[0] == "1" else size
for i in range(50): # 12.5 seconds
if i < 30:
drive(45, 0) # towards the wall
else:
drive(-45, 0) # and back
gap = round(distance() - WANT) # positive when far, negative when too close
byte = pack(gap)
plot("gap, cm", gap)
plot("read back, cm", unpack(byte))
if i % 10 == 0:
print(gap, "is stored as", byte, "and read back as", unpack(byte))
wait(0.25)
stop()
print("00000000 reads back as", unpack("00000000"))
print("10000000 reads back as", unpack("10000000"))
print("5 is", pack(5), "and -5 is", pack(-5))
print("added as plain binary that is 10001010, which reads back as", unpack("10001010"))
Storing the reading works. The two lines on the chart lie on top of each other, because nothing is lost. The trouble is everything else:
- There are two zeros.
00000000and10000000both read back as 0. One pattern is wasted, and a test for zero has to check for both. - Adding does not work. The last line of the printout adds the bytes for 5 and -5 as if they were plain binary. The answer is
10001010, which reads back as -10. A processor would need a second circuit that compares the signs, compares the sizes, decides whether to add or subtract, and works out the sign of the answer. - The order is broken. As a plain binary count,
10000001(which is -1) is bigger than01111111(which is 127), so a simple comparison circuit gives the wrong answer.
Sign and magnitude is how you would write it on paper, and it is how the sign bit works in a floating point number. For whole numbers, processors use something better.
Two's complement: the top bit is worth minus 128
Keep the columns of ordinary binary, but make the leftmost one worth -128 instead of +128:
| -128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
That byte is -128 + 64 + 32 + 2 = -30.
A leftmost bit of 0 still means the number is zero or positive, so nothing below 128 changes: 5 is 00000101 in both rules. A leftmost bit of 1 now means "start at -128 and add the rest". There is one zero, the range is -128 to 127, and the count runs in order the whole way from 10000000 (-128) up to 01111111 (127).
The property that matters is this one. The ordinary adding circuit works for negatives with no changes at all. Pack -5 as a byte, add it to the byte for 51 exactly as if both were plain binary, throw away any carry out of the leftmost column, and the answer is right.
The program
from bugbot import *
connect()
# change these and press Run
WANT = 30 # the gap the robot is measuring against, in cm
TRIM = -5 # a correction added to every reading, in cm
def pack(n):
# two's complement: a negative number gets the pattern for 256 + n
return format(n + 256 if n < 0 else n, "08b")
def unpack(bits):
# the top bit is worth -128, the other seven are worth what they always are
return (-128 if bits[0] == "1" else 0) + int(bits[1:], 2)
def add(a, b):
# plain 8-bit addition: add the two patterns and keep the bottom 8 bits
return format((int(a, 2) + int(b, 2)) % 256, "08b")
trim = pack(TRIM)
for i in range(50): # 12.5 seconds
if i < 30:
drive(45, 0) # towards the wall
else:
drive(-45, 0) # and back
gap = round(distance() - WANT)
byte = pack(gap)
plot("gap, cm", unpack(byte))
plot("gap + trim, cm", unpack(add(byte, trim)))
if i % 10 == 0:
print(gap, "=", byte, " + ", trim, "=", add(byte, trim),
"=", unpack(add(byte, trim)))
wait(0.25)
stop()
print("127 + 1 =", add(pack(127), pack(1)), "=", unpack(add(pack(127), pack(1))))
print("5 + -5 =", add(pack(5), pack(-5)), "=", unpack(add(pack(5), pack(-5))))
The printout shows the same sum going right at both ends of the drive: 00110011 + 11111011 is 46 when the gap is 51, and 11110111 + 11111011 is -14 when the gap is -9. The adder never looks at the signs. That is the whole reason two's complement won.
pack() uses a shortcut here: on a byte, -5 is stored as the pattern for 256 - 5 = 251, which is 11111011. That is the same pattern you get by flipping and adding 1, which is the method an exam asks for, and the next section does it the long way.
Negating a number: flip the bits and add 1
To turn a positive number into its negative:
- Write the number in 8-bit binary. 60 is
00111100. - Flip every bit:
11000011. - Add 1:
11000100, which is -60.
It works because a number and its flipped copy always add up to 11111111, and 11111111 is -1. So flipping gives you -60 - 1, and adding 1 finishes the job. The same three steps turn a negative back into a positive, so negating twice gets you back where you started.
There is a shortcut that gives the same answer in one pass: copy the bits from the right up to and including the first 1, then flip everything to the left of it.
A motor controller is a good reason to want this. It takes a signed byte: positive drives forwards, negative drives backwards. This robot holds a 30 cm gap from the wall. It has one speed byte for forwards, and it makes the reverse one by flipping and adding 1.
The program
from bugbot import *
connect()
# change these and press Run
WANT = 30 # the gap to hold, in cm
SPEED = 60 # how hard to push, as a signed byte
def pack(n):
return format(n + 256 if n < 0 else n, "08b")
def unpack(bits):
return (-128 if bits[0] == "1" else 0) + int(bits[1:], 2)
def flip(bits):
flipped = ""
for bit in bits:
flipped = flipped + ("0" if bit == "1" else "1")
return flipped
def negate(bits):
# flip every bit, then add 1
return format((int(flip(bits), 2) + 1) % 256, "08b")
forwards = pack(SPEED)
backwards = negate(forwards)
print(forwards, "flips to", flip(forwards),
"and adding 1 gives", backwards, "=", unpack(backwards))
for i in range(60): # 15 seconds
gap = round(distance() - WANT)
if gap > 0:
command = forwards # too far away
elif gap < 0:
command = backwards # too close
else:
command = pack(0)
drive(unpack(command), 0)
plot("gap, cm", gap)
plot("speed byte", unpack(command))
wait(0.25)
stop()
print("negate(negate(" + forwards + ")) =", negate(negate(forwards)))
print("negate(10000000) =", negate("10000000"), "=", unpack(negate("10000000")))
The "speed byte" line on the chart is the number the motors were given: +60 while the robot is closing in, then flicking between +60 and -60 as it holds the gap. A controller that is always at full push one way or the other is called bang-bang control, and the wobble it leaves is why most robots use a PID controller instead.
The last line of the printout is the one odd case. Negating 10000000 gives 10000000 back. That pattern is -128, and +128 does not fit in a byte, so -128 is the one number with no positive twin. Every exam question about the range of a two's complement byte comes back to that missing pattern.
Overflow: what the top of the byte looks like
The bits cannot count past 11111111. Add 1 to 01111111 and the columns carry all the way along, giving 10000000. As unsigned binary that is 127 going up to 128. Read as two's complement it is 127 going down to -128.
That is overflow: the true answer needed a bit that is not there, so what comes back is 256 too small. In this demo the robot's speed byte starts at 0 and 25 is added every 0.4 seconds. The chart shows the same eight bits read both ways, and you can watch the robot get faster and faster and then slam into reverse.
The program
from bugbot import *
connect()
# change this and press Run
STEP = 25 # how much is added to the byte each time round
def unpack(bits):
return (-128 if bits[0] == "1" else 0) + int(bits[1:], 2)
byte = "00000000"
for i in range(24): # 9.6 seconds
plain = int(byte, 2) # the same 8 bits read as 0 to 255
speed = unpack(byte) # and read as -128 to 127
plot("as 0 to 255", plain)
plot("as -128 to 127", speed)
drive(speed, 0)
print(byte, "is", plain, "unsigned, or", speed, "signed")
wait(0.4)
byte = format((plain + STEP) % 256, "08b") # add STEP, keep 8 bits
stop()
The "as 0 to 255" line climbs and drops back to the bottom. The "as -128 to 127" line climbs and drops off the top to the bottom at the same moment, because it is the same event seen through a different rule. The robot's motors top out at 100, so 125 and 150 both mean full speed, but the sign is not hidden: at 10010110 the motors are handed -106 and the robot goes backwards.
In an exam, carry and overflow are two different flags and the marks go to whoever keeps them apart:
- A carry is a 1 coming out of the leftmost column. It matters when you are reading the bits as unsigned.
- An overflow is when the answer has the wrong sign. It matters when you are reading them as two's complement.
The test for overflow is worth learning as a sentence: adding two numbers with the same sign and getting the other sign is overflow. Adding a positive to a negative can never overflow, because the answer is always somewhere between the two.
How to answer the usual exam questions
Denary to 8-bit two's complement. For a positive number, write it in binary as usual. For a negative number, write the positive version, flip every bit, and add 1. Always write all eight bits.
Two's complement to denary. Look at the leftmost bit. If it is 0, read the byte as ordinary binary. If it is 1, the number is negative, and you have two ways to finish: add up the columns with the leftmost worth -128, or flip and add 1 to find the size and then put a minus sign in front of it.
The range in n bits. A two's complement number in n bits holds -2n-1 to 2n-1 - 1. The negative end goes one further than the positive end, because of the missing twin for -128.
| Bits | Unsigned | Two's complement |
|---|---|---|
| 4 | 0 to 15 | -8 to 7 |
| 8 | 0 to 255 | -128 to 127 |
| 16 | 0 to 65,535 | -32,768 to 32,767 |
| 32 | 0 to 4,294,967,295 | -2,147,483,648 to 2,147,483,647 |
Subtraction. To work out a - b, negate b and add. That is the only way processors do it, which is why there is no subtracting circuit inside a simple ALU.
Mistakes that lose marks
- Writing fewer than eight bits.
101is not an answer. Write11111011. - Flipping but forgetting the +1, or adding 1 before flipping. Flip first, then add.
- Flipping the sign bit and nothing else. That is sign and magnitude, and it is a different rule.
- Saying an 8-bit two's complement number goes from -127 to 127. It goes to -128 at the bottom.
- Calling a carry an overflow. Two positives that give a negative is overflow. A 1 falling off the left is a carry.
- Reading a byte as negative without being told the rule.
11111011is 251 or -5 depending on what the program says it is. Say which you are using.
Where this is taught
- Binary and denary builds the columns that everything here rests on.
- Binary addition, overflow and shifts adds bytes column by column and shows a ninth bit going missing.
- Negative numbers: two's complement is the GCSE lesson for this page, with a task that packs signed readings.
- Number sets, bases and units sets out the ranges at A level.
- Binary arithmetic and signed integers is the A level lesson, with the carry and overflow flags and a signed adder to write.
- Errors, range and precision is what happens when a value will not fit.
- Bitwise operations and characters uses AND, OR, XOR and shifts on the same bytes.
- The processor and its registers is where the N, Z, C and V flags live.
Questions
What is two's complement in simple terms?
A way of storing negative whole numbers in a fixed number of bits, by making the leftmost column worth minus its usual amount. In a byte the columns are -128, 64, 32, 16, 8, 4, 2 and 1, so 11100010 is -128 + 64 + 32 + 2 = -30.
How do you convert a negative denary number to two's complement?
Write the positive version in binary with the full number of bits, flip every bit, then add 1. For -30 in 8 bits: 30 is 00011110, flipping gives 11100001, adding 1 gives 11100010.
How do you convert two's complement back to denary?
If the leftmost bit is 0 the number is positive, so read it as ordinary binary. If it is 1, either add up the columns with the leftmost worth -128, or flip the bits and add 1 to get the size and write a minus sign in front.
What is the range of an 8-bit two's complement number?
-128 to 127. That is still 256 values, the same as unsigned binary's 0 to 255. In n bits the range is -2n-1 to 2n-1 - 1.
Why is there one more negative number than positive?
Zero takes up one of the patterns that would otherwise be a positive number, and in two's complement it only takes one pattern instead of the two that sign and magnitude wastes. So the positive side runs out at 127 while the negative side reaches -128.
Why do computers use two's complement instead of sign and magnitude?
Because one adding circuit then works for both signs. There is also only one zero, and the patterns stay in order, so comparing two numbers is the same circuit as comparing two unsigned ones. Sign and magnitude needs extra hardware for all three jobs.
What is the difference between carry and overflow?
A carry is a bit coming out of the leftmost column, and it tells you an unsigned answer did not fit. Overflow is when a signed answer comes out with the wrong sign, which happens when two numbers of the same sign are added and the answer has the other sign. An addition can set one, both or neither.
What happens when a signed byte overflows?
It wraps round. One more than 127 is -128, and one less than -128 is 127, because the bits carry on counting and the leftmost column flips from 0 to 1. The value that comes back is 256 too small, as the last demo on this page shows when the robot's speed of 125 turns into -106.
Is two's complement on the GCSE specification?
On Pearson Edexcel's GCSE, yes: it asks for negative numbers in 8-bit two's complement and conversions both ways. AQA's and OCR's GCSE specifications use unsigned binary only. At A level all the boards include it: AQA asks for conversions, the range in n bits and subtraction by adding the negative, and OCR asks for both sign and magnitude and two's complement, with addition and subtraction.
How do you subtract binary numbers with two's complement?
Negate the number you are taking away, then add. For 50 - 25 in 8 bits, negate 25 to get 11100111, add it to 00110010, and drop the carry out of the leftmost column, which leaves 00011001, or 25.
Learn it step by step
These lessons build the same ideas one at a time, each with tasks the simulator marks.
- F8.2 Binary and denary Data representation, GCSE
- F8.4 Binary addition, overflow and shifts Data representation, GCSE
- F8.5 Negative numbers: two's complement Data representation, GCSE
- A7.1 Number sets, bases and units Data representation, A level
- A7.2 Binary arithmetic and signed integers Data representation, A level
- A7.4 Errors, range and precision Data representation, A level
- A7.5 Bitwise operations and characters Data representation, A level
- A9.2 The processor and its registers Computer architecture, A level