Unsigned addition

For non-negative integers, binary arithmetic is so simple that a machine can carry it out. First we memorize a very small number of addition facts (the addition table for binary):

The last fact may seem strange until you recall that 10 is the binary representation of 2. To add larger numbers, you keep these binary addition facts at the tip of your neurons, add pairs of digits from right to left, and "carry" the excess. For example, you can convert 37 and 23 to binary, and then add them as follows:

  100101
 + 10111
 _______
  111100

Notice that in each column the most work we need to do is to add three binary digits: the two digits in that column, plus the "carry" (which is either a 0 or a 1). We could write down a table for all the possible inputs that such a column could have, and all the possible outputs. We'll call the inputs b1 and b2 (for bit 1 and bit 2), and ci (for carry-in, the number carried from the previous column). We'll call the outputs s (for sum, the digit that should appear below the bottom line, and co (for carry-out, the carry that this column creates). Here's a table, which you should check to see whether you're convinced it covers all possibilities:

b1b2cisco
00000
00110
01010
01101
10010
10101
11001
11111

Earlier I claimed that for any sequence of bits, we can build a set of AND, OR, and NOT gates that will give the desired outputs for any input. So if the first three columns, with headings b1, b2, and ci, are our sequence of input bits, we are able (if you believe the earlier claim) to build a set of gates that produces the column headed s, and a set of gates that produces the column co. You can look at just such a collection of logical gates at [Wikipedia page on adders], the section on full adders. The circuit that looks a bit like an OR with an extra curved line is an XOR.

XOR
b1 XOR b2 is 1 (true) if either b1 or b2, but not both, are 1 (true). You can build an XOR gate using AND, OR, and NOT.

It's not necessary for this course to understand adders (unless you're curious), but you should understand that they can be reduced to just AND, OR, and NOT.

Once you can add two binary numbers together, further addition is simply a matter of storing the result and repeating...

Unsigned multiplication

Binary multiplication of non-negative integers is similarly easy. The basic multiplication table has four facts:

Use these facts to multiply the digits from your factors, and then add the results (this is analogous to base-ten multiplication). Try this out with 5x7 (represent these factors in binary). So long as we have a circuit that implements the four-line multiplication table (does it remind you of one of our logical functions?), and an adder, we can do multiplication of non-negative integers.

Unsigned integer division

The division that we learned in grade school, with quotients and remainders, is what needs to be implemented for binary division. Try out 19 divided by 5 (in binary) (you should get a quotient of 3 with a remainder of 4). In integer division we only care about the quotient. In order to carry out this algorithm, you'll need to carry out subtraction (with "borrows" from columns to the left).

Representing negative numbers

So far we have discussed arithmetic on binary numbers that are non-negative integers. How should we represent negative numbers? A common convention is to:

  1. Decide on a fixed length (number of bits) for our numbers.
  2. Reserve the left-most bit for the sign (often 1 for negative, 0 for non-negative).
This is enough to write any negative or positive integer you want. Suppose the fixed length you decide on is 32 bits (64 bits is becoming common these days). That means you have 31 bits to express the integer in binary, and 1 bit to say whether it's positive or negative.

An additional wrinkle is added to make subtraction efficient. If you already have binary adder circuits (see above), it would be nice to keep using them for subtraction. In other words, it would be nice if, for numbers n and m, n-m was the same as n+(-m). In particular, we would like m-m=m+(-m)=0. This leads to a representation of negative numbers called two's complement. The non-negative numbers begin at 0...0 (as many zeros as there are bits in our representation), and run to 01...1 (left-most zero, and the remaining bits 1). So the largest positive number that can be represented is 01...1. We want our negative numbers to satisfy the rule: n+(-n) = 0. Let's try to find -3 so that this works:

  00000000000000000000000000000011
 +11111111111111111111111111111101
 _________________________________
  00000000000000000000000000000000

Of course, we discarded the last "carry", since it exceeded the 32-bit limit on our numbers. If you try a few more, you'll find that if n is positive, you write -n by copying all the digits of n, from left to right, up to and including the first 1, and then "flip" (complement) the bits thereafter. With this transformation, subtraction simply becomes addition.

Representing fractions

Another representation is needed for numbers other than integers (numbers that have a fractional part), and also numbers that are very large or very small. One way to do this is with floating point representation. Once again, you fix the number of bits, and use the left-most bit to represent the sign (0 for positive, 1 for negative). Then use a few of the remaining bits to represent the magnitude (the power of 2, or the number of times you multiply by 2), and the other remaining bits to represent the digits in the number (if you are familiar with scientific notation, this will be the binary version of that). For example, suppose we restrict the our size to eight bits, using one bit for the sign, 3 bits for the magnitude, and 4 bits for the digits.

In floating point representation, the powers of 2 are arranged a little differently --- we use three-bit excess notation so that our powers of three range from the most negative, 000, to the most positive, 111, so we have

The four bits that represent the digits (sometimes called the mantissa) have the convention that they must begin with a 1. This is interpreted as the digit to the right of the radix point (like a decimal point), and this value is multiplied by 2 raised to the power indicated by the exponent. Putting this all together, let's figure out what

 01011100
...represents. The left-most bit is a 0, so the number is non-negative. The next 3 bits are 101, so the exponent is 1, and we multiply by 2^1. The next four bits are 1100, so we place a radix point and multiply: 2^1*.1100, or 1.100 (1.5 in base 10). Notice that the exponent tells you how far left or right to move the radix point.

Since we have only 8 bits, we can represent only a fairly small range (32 or 64 bits would be more realistic). The largest positive number we can represent is 01111111, which is 2^3*.1111, or 111.1 in binary (7.5). The smallest non-zero positive number is 00001000, which is 2^(-4)*.1000, or .00001, (or 1/32 in base ten).

Overflow!

What happens when our bits don't fit? For example, if we only have 2 bits the only non-negative binary numbers we can represent are 00, 01, 10, and 11 (0 to 3 in base 10). With 2 bits there is no way to write the binary number for 7. What happens when we're adding big and small magnitudes? They are aligned on the decimal point, and the digits representing the largest magnitude are kept, so information may be lost.

Try repeatedly incrementing the value referred to by big in the Error Console in Firefox. Here are three repeats, but you should do quite a few more:

 big = 11; big = big * big; big = big * big; 

Precision

Check the value in precise in this javascript code from the Error Console:

  precise = 1.23456789012345678901234567890;

Numbers that have too much precision (significant digits of information) cannot be represented as fixed-length numbers, so some information is lost.

other ways of storing numbers

Fixed-length integers and fractions are widely used for fast computation in computer hardware. These aren't the only ways to represent numbers. It is possible to have a program allocate extra memory as it is needed to represent very large integers. It is also possible to store and manipulate fractions as ratios --- 3/5, for example --- and carry out arithmetic on them.