Negative numbers: two's complement

Signed 8-bit integers, and why the same adder works for them.

F8.5Data representationGCSE12 min

Do this lesson in the simulator

So far every binary number has been positive. But a robot needs negatives too: a turn to the left, a speed in reverse, a temperature below zero. Two's complement is how nearly every computer stores negative whole numbers, and it has a lovely property: the same adding circuit works for positives and negatives alike.

The idea

In an 8-bit two's complement number, the leftmost bit is worth minus 128 instead of plus 128. Every other bit keeps its usual value:

-128 64 32 16 8 4 2 1
1 1 1 1 1 0 1 1

That is -128 + 64 + 32 + 16 + 8 + 2 + 1 = -5.

So a leftmost bit of 0 means the number is zero or positive, and 1 means it is negative. Eight bits now hold -128 to 127, still 256 values, just shifted to include negatives.

Making a number negative

To turn a positive number into its negative:

  1. Write the positive number in 8-bit binary: 5 is 00000101.
  2. Flip every bit: 11111010.
  3. Add 1: 11111011, which is -5.

Or, a shortcut that gives the same answer: copy the bits from the right up to and including the first 1, then flip everything to its left.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

def to_twos(n):
    """-128 to 127 -> 8-bit two's complement string."""
    if n >= 0:
        return format(n, "08b")
    flipped = ""
    for bit in format(-n, "08b"):
        flipped = flipped + ("0" if bit == "1" else "1")
    return format(int(flipped, 2) + 1, "08b")

def from_twos(bits):
    """8-bit two's complement string -> -128 to 127."""
    value = -128 if bits[0] == "1" else 0
    return value + int(bits[1:], 2)

for n in [5, -5, 127, -128, -1]:
    print(n, "->", to_twos(n), "->", from_twos(to_twos(n)))

Run this in the simulator

Why it works so well

Add 5 and -5 in binary, ignoring the carry out of the last column:

  00000101    (5)
+ 11111011    (-5)
----------
  00000000    (0, with a carry out that is dropped)

The ordinary adder from the last lesson gives the right answer with no special case for signs. That is why processors use two's complement.

Negative speeds

A motor controller often takes a signed byte: positive forwards, negative backwards. Here the program decodes signed speeds and drives accordingly:

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

def from_twos(bits):
    return (-128 if bits[0] == "1" else 0) + int(bits[1:], 2)

commands = ["00011110", "11100010"]          # 30, then -30
for bits in commands:
    speed = from_twos(bits)
    print(bits, "->", speed)
    if speed > 0:
        forward(speed, distance=15)
    else:
        backward(-speed, distance=15)

Run this in the simulator

Task: signed readings

Write to_twos(n) yourself for -128 to 127 (you may use format(n, "08b") for the positive part). Print the 8-bit two's complement of 42, -42 and -128, one per line, in the form -42 = 11010110.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

def to_twos(n):
    return format(n, "08b")

Challenges

  1. What happens if you try to_twos(200)? Add a check that refuses numbers out of range.
  2. Add -60 and 40 as 8-bit two's complement using the adder from F8.4. Is the answer right?
  3. How many bits would a two's complement number need to hold -1,000?