We can represent numbers in binary; we can define the operations of binary arithmetic in terms of boolean logic; and we can implement boolean logic with digital electronics. Without that, computers would (at best) be very different than they are today...
1*104 + 2*103 + 5*102 + 7*101 + 3*100
The 10 that is raised to the various powers is called the "radix" of the number. We are, of course, accustomed to working in radix 10. It turns out that there's absolutely nothing sacred about our choice of 10 for the number radix -- a few cultures have used five, pre-Columbians in Central America (the Mayans, for instance) used 20, and the ancient Babylonians used 60 (we've inherited our system of dividing circles into degrees, minutes, and seconds from this). I've seen the claim that the Hebrews used 10 for some purposes and 12 for others, but haven't been able to verify it (but notice that our division of the day into hours apears to be a vestige of a duodecimal numbering system). So, instead of 10, we could use 8, 16, 79, 13... as we please.
In general, we can define any integer number radix r greater than 1 we want: the digits go from 0 to r-1, and the base is r (so it's 10 for decimal, 2 for binary, 8 for octal, 16 for hexadecimal, and so forth).
Let's take a number like 54 10 (that's 5*101 + 4*100). It's easy to verify that 54 = 3*16 + 6, or 3*161 + 6*160. Notice that this has the same form as we used above to ``decode'' a number in radix 10; this tells us that 54 10 = 36 16 . And, it turns out that there is a simple algorithm for converting numbers between any two radixes. We'll get to that later.
A spectacularly useful radix for our purposes is binary: base 2. This is because (as I said at the start)
Let's take a look at a number like 3710. The first number less than or equal to 37 that is a power of 2 is 32 -- that's 25. So 37 = 32 + 5. The first number less than or equal to 5 that's a power of 2 is 4, so 3710 = 32+4+1. 1 is a power of 2 (it's 20), so 37 is
1*25 + 0 * 24 + 0 * 23 + 1 * 22 + 0 * 21 + 1 * 20,which we'll write as
1000112
1111 01101010 +01011001 --------- 11000011
3-input lookup table
| Inputs | Outputs | |||
|---|---|---|---|---|
| C in | X | Y | S | C out |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 |
Hey! Easily defined in terms of logic!
Cout = (Cin and X) or (Cin and Y) or (X and Y)
Subtraction in binary also works just like subtraction in decimal, but simpler. What's 37 - 19?
100101 -010011 --------- 010010
This is 16+2=18, as we'd hope.
It's going to turn out, as we go on, that the computer uses a clever representation of negative numbers called "2's complement" to avoid having to implement subtraction hardware -- it's able to perform a subtraction by adding. We'll be getting to that soon.
So, we've seen that binary is a good radix to use in a computer. But, to say the least, it's not very convenient for us - writing all those 1's and 0's gets very tedious, very quickly. Fortunately, we can use another radix for our work, which is convenient for us (though not as convenient as decimal!), and also easily converted to binary.
Let's take a look at a binary number; in particular, let's look at the rightmost four bits of that number. These are the 23 place, the 22 place, the 21 place, and the 20 place. The smallest number we can represent with four bits is 0 (that's 0000), and the largest is 15 (that's 1111). So: a four bit nybble can represent a number from 0 to 15.
Now let's look at the next four bits. They are the 27 place, the 26 place, the 25 place, and the 24 place. But wait a minute -- 24 is the same as 161. So the next four bits can represent a number from 0 to 15, multiplied by 161. This is exactly the definition of radix 16!
That's great - it means we can divide a number up into nybbles, translate each nybble, and have a radix 16 number. More compact to write, easy to translate. Consequently, we write in hexadecimal almost exclusively when we're looking at hardware.
We can see a slight problem with this scheme if we translate 43 into hexadecimal: we get 2*161 + 11*160. But if we look at the number 21116, how can we tell whether it's supposed to be 2*161 + 11*160, or 2*162 + 1*161 + 1*160?
The solution is that we need to come up with some more digits. The convention is to use letters of the alphabet for the numbers from 10 to 15, giving us:
| Decimal | Binary | Hexadecimal |
|---|---|---|
| 0 | 0000 | 0 |
| 1 | 0001 | 1 |
| 2 | 0010 | 2 |
| 3 | 0011 | 3 |
| 4 | 0100 | 4 |
| 5 | 0101 | 5 |
| 6 | 0110 | 6 |
| 7 | 0111 | 7 |
| 8 | 1000 | 8 |
| 9 | 1001 | 9 |
| 10 | 1010 | a |
| 11 | 1011 | b |
| 12 | 1100 | c |
| 13 | 1101 | d |
| 14 | 1110 | e |
| 15 | 1111 | f |
One little thing - remember that hexadecimal is a shorthand we use. All numbers internal to the computer are really binary.
Addition and subtraction in hexadecimal works (you guessed it) just like in binary or decimal. Though it's actually a little bit harder, since you've got more possible values for a digit. Let's take an example:
6a93 5746 --------- c1d9
Similarly, we can subtract:
38e7 2c65 --------- 0682
One last thing to remark on is that we'll be doing a lot of work, when controlling devices, setting and clearing individual bits in device control registers. We can do this in hex, as well: a7|13=b7.