Select page Next page

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.

Decimal. In our everyday life, you and I use numbers in a particular way - in groups of ten. This is because we have evolved this sort of numbering system due to the number of fingers we have. Over the centuries, and by courtesy of Arab mathematicians, we have developed the unique digits 0-9 into the system we know as decimal.

But what if we wanted a number larger than 10? Well, we can look upon numbers as being made up of an infinite series of empty boxes, each of which is capable of holding a single digit in the range 0-9. For example, the "number one hundred and fifty-six" would look like this:

We know that the six is just a six but that the five stands for "5 x 10" and the one is really "1 x 100" - but how? Through years of practice in school and in adult life, we get to be pretty good at looking at a number and subconsciously working out the value of the maximum occupied box - is it 100? 1000? 10000? - and then going back down the line, giving scale to all the other digits until we can state the value of the entire number. Anything up to a million, we can usually handle pretty quickly but once it goes beyond that number things start to slow down. Take, for example, this number - 1001236745699 - time yourself to see how long it takes you to "decode" it and note how you did it. You had to say "that one is Units, that one's Tens, that one's Hundreds ..." and so on until you had reached the leftmost (highest) digit. Once you had done that, you could move back down the line again. This dependence on calculating the highest digit in a number has led to the convention of calling the leftmost digit the most significant and the rightmost the least significant.

Let's take another look at our first number:

The powers of ten above the boxes show the value by which the contents of the box must be multiplied to give the correct number. So, the "1" must be multiplied by 10 x 10 (two tens multiplied together), the "5" by 10 (one ten) and the "6" by no tens at all. This will give us:

1 x 100 + 5 x 10 + 6 = 156

Easy! Let's try something more difficult, say, 15134. Put each digit into a box as we have done above and we should get:

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:

  1. Change all the ones to zeros, and vice versa.
  2. Add 1.
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".

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) ...

-114 +  +114Result
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

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.

Hexadecimal. Lastly in this trot through number systems, I want to talk about hexadecimal. Since computers deal naturally in powers of two (with the binary system), it is often convenient to express them in larger "chunks" of this approach. Octal (powers of eight) are sometimes used but, since we will not really deal with octal values, I will not discuss it further. However, chunks of 16 are very often used. The reason why I particularly want to discuss hexadecimal is that it presents us with a problem right away - we know about the values 0-9 because we use them in decimal but how to we express a single digit which can hold the value 10-15? The convention in hexadecimal (or "hex", for short) is to use the letters "A" - "F". That would mean that the value "FF16" would be equivalent to "255" (the rightmost "F" is 15 times no 16's and the next one is 15 x one 16's - work it out for yourself). Converting from binary to hex is quite simple. Take your byte and split it into two sets of 4 bits (these are called "nibbles", believe it or not). Convert these to their decimal equivalents, which will lie in the range 0-15, and change these into the value 0-F. Easy.

That's all we need to know for now about number systems. It is important for you to understand them, as I have already said. Practise converting from one to the other until you are fairly confident in them. But for now, let's look at chunks of data beyond the byte since that has an impact on our what we will be discussing when we come to registers.

Packets of data. We should be fairly familiar by now with the concept of bytes and I have already mentioned that we often treat data in chunks of double-bytes, called a "word". See if you can work out the maximum unsigned value that could be stored in a word - yes, 65,535 (or 64K, as it is often termed). There is also a dword ("double word") consisting of 4 bytes. That would allow us to code a staggering 4,294,967,295 (4 Gb). There is even a qword ("quad-word") which is made up of 8 bytes and also a tword ("ten-word") which has 10 bytes. Huge numbers. Finally, there are even occasions where 16 bytes are used.

Select page Next page