Objectives
The aim of this chapter is to help you understand how to use CPU registers to handle communication with the Windows operating system. To do this, you will need to be able to:
Number systems
Now that we are reasonably comfortable with compiling programs in EasyCode, let's move on to the heart of assembly programs - number systems. Yes, I know this chapter is called "Registers and data" but "Data and registers" didn't sound as good! We need to deal with numbers first or the section on registers won't make as much sense. If you are already fully familiar with binary and hexadecimal and how to covert between these and decimal, you could skip over this section. For the rest of you, this is really rather important, so I recommend you take a look. By all means, skim through it and re-visit it later, but you should have a basic idea of what's going on with numbers if you are to understand what the registers are doing for us.
1 x 10000 | ||
5 x 1000 | ||
1 x 100 | ||
3 x 10 | ||
4 |
We say, then, that 10 is the number base and that the digit in each box is multiplied by the base raised to the power of "n", where "n" is the box's position in the number line, starting from zero (on the right) and going in steps of one to infinity (on the left).
Binary. Computers, sad to say, do not work in decimal. Why should they? They have no fingers. Instead they operate on what is termed a "Boolean" basis (named after the famous mathematician George Boole). This means "True" or "False", "one" or "zero", "present" or "absent". Since data is represented inside your PC as little blips of electricity on the various tracks of the motherboard, a specific track either has a blip present or is doesn't. There's is nothing in between. This may sound a bit limited but, in fact, it imparts great robsutness to the system. If the voltage should drop a little, it doesn't really matter. What is important is that there is or isn't a charge on this line or that one. A number system which has only two digits - 0 or 1 - is known as binary. The digits 2-9 simply do not exist in the binary world. So, how do we represent larger numbers? Well, think about it a little. If we want to represent a number greater than "9" (the largest digit) in the decimal system, we (in effect) divide the number by 10 and take the remainder. This becomes the least siginificant digit. We then take the dividend (the result of the division) and divide it again, taking the remainder and putting that in the next box, and so on until the result of the division is zero. In the case of a binary number, we would divide by 2 instead of 10. That's all.
Binary to decimal. Let's try an example. Suppose we wanted to translate the binary number 100112 into decimal - how would we do it? The first thing is to use our number boxes as before but, this time, instead of the number base being 10, it would be 2. Like so:
This would give us 1 x 16 (4 twos multiplied together) + 0 x 8 (3 twos multiplied together) + 0 x 4 (2 twos multiplied together) + 1 x 2 + 1, thus giving us 1910 in decimal (note the little number at the end which denotes which base we are in).
Decimal to binary. As you can see, converting from binary into decimal is relatively straightforward - just multiply each digit by the power of two for the box that it occupies. In fact, this process works for any base into decimal. For example, there is another base used in computing - octal. This means that each box is equivalent to a power of 8 and the possible range of digits in each box is 0-7. All we would do is multiply each digit by the power of 8 for that box and total up all the results. But what about converting from decimal to a number base like binary (or any other)? I gave you a clue to this earlier in the chapter - instead of multiplying, we perform repeated division. Let's take our long-suffering "156" and turn it into decimal:
Prove to yourself that this approach works by taking the binary number we have just created and turn it back into decimal.
Two's complement. The thing about decimal numbers in the outside world is that you can express any number, however large, simply by appending more and more digits to it. Inside computers, however, things are considerably different. You will be familiar with the term byte. This is a binary number made up of 8 bits and so the maximum number which can be stored in it is 255. If we try to store 256 in it, all the bits will be set to zero. Do the conversion yourself - you'll end up with a ninth bit. Since the byte only has eight bits, the ninth is lost in the ether. Well, not quite. You see, the CPU has these registers, as we know, but it also has tiny, single-bit pieces of information called flags. We'll be looking at these falgs in much more detail when we deal with branching commands. Some instructions affect these flags and one of them is called the "carry" flag. If the result of an operation causes an overflow, the carry flag will be set to "1" to let us know that the result was too big for the byte.
Bytes are also a bit "schizophrenic" in that they can be regarded as negative or positive at any time. Think about it. If we store 255 in a byte, it will be set to "111111112". If we add another 1 to this byte, it will carry over and we will be left with "000000002" and the carry flag set. Thus, you could say that "111111112" is 1 less than "000000002". In other words, "111111112" could be regarded as -1 in decimal. Wierd. Check it out for yourself. If you add 1 to -1 in decimal, what do you get? Zero, of course. Add 1 to "111111112" and see what you end up with! And so, we can either regard "111111112" as 255 or as -1. If we choose to do the former, we are said to regard the number as "unsigned". If the latter, we say it is "signed".
The leftmost (most significant) bit of a chunk of data is regarded as the "sign" bit when we choose to handle the values as signed numbers. In the case of bytes, this means bit 7 but there are larger chunks as we shall shortly see. A "word", for example, is two bytes joined together to make 16 bits (0-15). In the case of a word, the sign bit would be bit 15. Making a number negative is a little more complicated than you would think. The process is as follows:
We should, however, try to prove that this is the case. Think in decimal for a moment. If we add -114 to +114, what would we get? Zero. So let's add "011100102" to "100011102", starting at bit 0 (the rightmost one) ...
So, there you are. The proof. Negating numbers like this is often called "two's complement" and it can cause problems for newcomers to programming. There is absolutely no difference in the assembly commands used when dealing with signed numbers. It's all down to how the programmer wants to regard them. For a little more depth on this subject, you should read Jeremey's article on two's complement in the goAsm help file.
Of course, treating the value inside our byte means that we are using one of the bits which used to store a value. That menas that, if we are using signed numbers, we can only store the range -128 to +127 in our byte. That means we cannot use our old friend "156" again. It would be too big to store in a signed form in a single byte so let's try "114". We know from our exercise above that it is "011100102", so the first step would be to exchange all the ones and zeros to give us "100011012". Now, we add 1 to this value. How do we do that? Well, take each bit in turn. Bit 0 is currently "1" and we add 1 to it. That would give us "2" and we know that is "102" in binary, so bit zero now becomes "0" and we carry one over to bit 1. Bit 1 is "0", so when we add "1" to it, it becomes "1" and there's nothing to carry over into the next bit. Thus, -114 is "100011102".
-114  +  +114 Result 0  +  0 = 0 1  +  1 = 0, carry 1 0  +  1 + carry = 0, carry 1 0  +  1 + carry = 0, carry 1 1  +  0 + carry = 0, carry 1 1  +  0 + carry = 0, carry 1 1  +  0 + carry = 0, carry 1 0  +  1 + carry = 0, carry 1