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.

Guidefree, runs in your browser

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.

The byte 11100010 read as an unsigned number, as sign and magnitude, and as two's complement: 226, -98 and -30one byte: 11100010unsigned128 + 64 + 32 + 2128643216842111100010= 226sign and magnitudethe 1 means minus, 1100010 is 98sign643216842111100010= -98two's complement-128 + 64 + 32 + 2-128643216842111100010= -30Sign and magnitude has two zeros: 00000000 and 10000000 both mean 0.
The same eight bits, read by three different rules. Only the leftmost column changes: worth 128, or a minus sign, or minus 128. Two's complement is the one nearly every processor uses, and it reads this byte as -30.

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 gap falls from 51 cm to -9 cm and comes back to 27 cm. The byte reads back exactly, so the two lines lie on top of each other all the way.
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"))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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. 00000000 and 10000000 both 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 than 01111111 (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.

Every reading is packed into a byte and added to the byte for -5 with plain binary addition. The two lines stay 5 cm apart whether the gap is 51 cm or -9 cm.
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))))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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:

  1. Write the number in 8-bit binary. 60 is 00111100.
  2. Flip every bit: 11000011.
  3. 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.

Negating 60: flip every bit of 00111100 to get 11000011, add 1 to get 11000100, which is minus 606000111100flip every bitflip11000011then add 1-6011000100check00111100 + 11000100 = 1 0000000060 + (-60) = 0, once the ninth bit is dropped: one adder, both signs
Flip and add 1 turns 60 into -60. It works because a number and its flipped copy always add to 11111111, which is -1, so flipping gives -60 - 1 and the +1 finishes the job. Demo 3 prints these same three bytes.

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.

00111100 flips to 11000011, and adding 1 gives 11000100, which is -60. The robot closes the 51 cm gap, then holds 30 cm to within 2 cm by switching between the two bytes.
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")))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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 byte climbs 0, 25, 50, 75, 100, 125, and the next add gives 150, which as a signed byte is -106. The robot drives forwards, then reverses, then does it all again.
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()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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.

The 256 byte patterns drawn as a ring: counting up past 127 does not stop, it carries on at -1280326496-96-64-32one byte256 patternsclockwise is add 1127-128the one step where the value falls0: startdemo 4 adds 25 each time roundsigned: 0, 25, 50, 75, 100, 125, -106 ...as a plain byte: 0, 25, 50, 75, 100, 125, 150 ...the same eight bits, both times
Adding 1 moves one step clockwise, and the ring has no end. The green dots are demo 4's bytes. The only place the signed value goes down is the step from 127 to -128, which is where the robot's speed of 125 turns into -106 and it reverses.

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. 101 is not an answer. Write 11111011.
  • 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. 11111011 is 251 or -5 depending on what the program says it is. Say which you are using.

Where this is taught

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.

  1. F8.2 Binary and denary Data representation, GCSE
  2. F8.4 Binary addition, overflow and shifts Data representation, GCSE
  3. F8.5 Negative numbers: two's complement Data representation, GCSE
  4. A7.1 Number sets, bases and units Data representation, A level
  5. A7.2 Binary arithmetic and signed integers Data representation, A level
  6. A7.4 Errors, range and precision Data representation, A level
  7. A7.5 Bitwise operations and characters Data representation, A level
  8. A9.2 The processor and its registers Computer architecture, A level
Open the lessons