This chapter is divided into two sections - one for the OLD, and one for the NEW ROM. We'll tackle the OLD ROM first because it's easier.
[OLD ROM ARITHMETIC]
Numbers are represented in two bytes, and as such it is possible to store them in register pairs BC, DE, and HL. First of all we shall take a look at the five major arithmetic routines.
1) Addition. The address to call is 0D3E, or more intelligibly, CALL ADD. The subroutine adds together the number stored in DE and the number stored in HL. The result is then placed in HL. This may be demonstrated by the following program:
Here DE is loaded with the number fifty-seven, and HL with seventeen. On return to BASIC the result stored in HL should be fifty-seven plus seventeen, so the command PRINT USR(adddemo) should generate the number seventy-four.
2) Subtraction. Just the same - DE is subtracted from HL and the result stored in HL. The address is 0D39. Thus to prove it:
113900 SUBDEMO LD DE,0039
211100 LD HL,0011
CD390D CALL SUB
3) Multiplication. Up until now we have ignored multiplication completely, since there is no simple instruction which will multiply two numbers together. However, thanks to Uncle C, the ROM will do it for us. Simply CALL MULT, which is stored at address 0D44, and as if by magic DE will be multiplied by HL, the result as usual being stored in HL. Watch out for what happens to BC and DE though! They're not unaltered.
4) Division. As you'd by now expect, the instruction CALL DIV will divide HL by DE (ignoring any remainder of course, since we are dealing in integers). The address of DIV is 0D90.
5) Powers. Is raising one number to the power of another going to be any more difficult? No of course not. With elegant simplicity the instruction CALL POWER (at 0D0C [0D70]) will do just that, raising HL to the power of DE, and putting the answer away in HL, using repeated multiplication to compute the answer.
One very important function is the RANDOM NUMBER GENERATOR. This is held at location 0BED. To generate a random number between one and six (say to simulate the roll of a die), simply load HL with six and CALL RND. This is of course the same thing as RND(6). The number in the brackets should be placed in HL, and the final answer will end up in HL.
See if you can work out what this program does. What we're interested in is the number that it returns to BASIC.
Let's see if you got it right. HL is loaded with 14 and RND is called, so HL is replaced by a new value, RND(20) (note that 14 (hex) is 20 (dec)). 0A is stored in DE, and the two are then multiplied together. We then have 10*RND(20). Finally 64 (hex) is added, giving 10*RND(20)+100.
We could use this routine in a games program. Suppose we needed to jump to a random destination. We could use the by now famous Tim Hartnell method of GOTO 10*RND(20)+100. Alternatively, if the above machine code were in a REM statement, say at address 16427, we could instead simply say GOTO USR(16427). This would do exactly the same job, except a little bit faster.
We'll leave the OLD ROM now, and turn to the rather more complex field of arithmetic on the NEW ROM.
NEW ROM ARITHMETIC
The first and most important point to note is that NEW ROM numbers are stored as five bytes, not two, and so they can't fit into a register-pair as they stand. Nor are the numbers in simple form, for while it is true that zero is, as you'd expect, 00 00 00 00 00, it is not true that one is 00 00 00 00 01! In fact one is represented by 81 00 00 00 00. Here is a list of the Sinclair representaion of the first few integers.
As you can see, you can instantly change a number from positive to negative just by adding 80 to the second byte. This doesn't apply to zero by the way - zero is represented uniquely to help speed the ROM up a little.
Knowing how the Sinclair Form is built up will slightly help your understanding of the ROM, so I will give here a brief explanation of how to turn decimal numbers into Sinclair numbers. It only takes a few simple steps.
STEP ONE: If the number is zero, then its Sinclair representation is 00 00 00 00 00.
STEP TWO: Ignoring the sign of the number, write it in binary (but without any leading zeroes). For example:
Notice that the binary form has a BINARY point, not a DECIMAL point! 100.01 means one 4 plus no 2's plus no 1's (here we reach the binary point) plus no halves plus one quarter. The next column would have been an eighth.
STEP THREE is to work out a quantity called the EXPONENT. This is done as follows:
If the part of the number to the left of the binary point is not zero then the exponent is the number of digits to the left of the point.
If the part of the number to the left of the point is zero and the first digit after the point is one then the exponent is zero.
If the part of the number to the left of the point is zero and the first digit after the point is zero, then count the number of zeroes between the point and the first 1 - the exponent is minus this number.
The first byte of the Sinclair representation is 80 plus this exponent.
STEP FOUR: Now we can ignore the binary point altogether - that is what the exponent is for - to tell the computer where the point is supposed to go. So ignoring the point, write the binary form starting with the first 1 and then add sufficient zeroes to the right to make the whole thing thirty-two binary (bits) in length.
STEP FIVE: It is here that we remember the sign of the original number. If the original number was negative then do nothing. If the original number was positive then replace the first one by a zero. Thus:
This is the form in which the ROM will be working. The largest exponent you may have is FF, so the largest positive number that can be stored is FF 7F FF FF FF. This turns out to be 1.7014118E38 (if you can't understand the "E" notation the E means "with the decimal point shifted (in the above case) 38 places to the right"[)]. In other words the number 170,141,180,000,000,000,000,000,000,000,000,000,000 which is a pretty vast quantity. It can still only store ten decimal places accurately though. The smallest positive number you can have (apart from zero) is 01 00 00 00 00, which happens to represent 2.9387359E-39. To you and me that's 0.000,000,000,000,000,000,000,000,000,000,000,000,002,938,735,9 which I'd say was pretty microscopic.
You can check all of this with the following BASIC program.
10 LET A=0
20 LET B=PEEK 16400+256*PEEK 16401
30 FOR I=1 TO 5
40 INPUT A$
50 POKE B+I,16*CODE A$+CODE A$(2)-476
60 NEXT I
70 PRINT A
[Download available for 16K ZX81 -> chapter17-floatchk. I have heavily modified this downloadable version so that not only can you enter Sinclair Form numbers in one go, you can also convert from decimal to Sinclair Form too.]
The program sets up a variable A, and then overwrites its previous contents by POKEing into the variables area, one byte at a time (that's a letter I in line 50, not a number 1). If you run it and enter "82"/"40"/"00"/"00"/"00" (where / means newline) you'll find the number three printed. And so on.
An interesting little quirk is that if you input "00"/"80"/"00"/"00"/"00" (in theory this is minus-zero) the machine actually prints -C.6E-56 ! The letter C in mid-number, and an exponent of -56! Don't panic! This doesn't really happen in the ROM. We made it happen by POKEing something that doesn't make sense. The ROM does behave slightly more sensibly than human beings.
HOW TO USE FLOATING POINT NUMBERS PROPERLY
Having seen that a five byte number is too big to store in the registers, the next question is undoubtedly "Well where does it store them then?". Answer - it stores them in an area of RAM called the CALCULATOR STACK, which works very much like the ordinary stack except for two points: 1) It can store both floating point numbers and strings, and 2) it is the right way up, not upside down like the machine stack.
To push a number stored in the BC register onto the calculator stack all you need to do is call up a subroutine in the ROM. CALL STACKBC, as I've called it, will change BC into five byte form as described above and then push this number onto the top of the calculator stack. You can do the same for a number stored in A (i.e. a number between 0 and 255) by calling STACKA. The addresses to call are: 1519 151D (STACKA) and 151C 1520 (STACKBC).
Incidentally the first two instructions in the STACKA routine are LD C,A and LD B,00. It then leaps straight into STACKBC!
Conversely, to retrieve a number from the calculator stack we can CALL UNSTACK (address 0EA7), which removes a number from the calculator stack and stores it in the BC register.
[Thunor: 0EA7 is the 'FIND INTEGER' subroutine which does actually call the 'FLOATING-POINT TO BC' subroutine at 158A but it also quits to BASIC with an error code if there's a problem. Now this behaviour is not what you'd normally expect in machine code programs as they should be in control of these situations, and so I think that CALL UNSTACK should really be calling 158A directly and in fact it works because I've tried it in the 6 * 7 multiplication test below. I suggest that when you see CALL UNSTACK at 0EA7 you should bare in mind that calling 'FLOATING-POINT TO BC' at 158A might be a better idea.]
Arithmetic is quite straightforward. The addresses are:
ADD 1754 addition
ADD 1755 addition
SUB 174B subtraction
SUB 174C subtraction
MULT 17C5 multiplication
MULT 17C6 multiplication
DIV 1881 division
DIV 1882 division
They work like this: The five-byte number stored at an address specified by HL (this means the number is stored in locations (HL), (HL)+1, (HL)+2, (HL)+3, and (HL)+4) is added to, multiplied by, divided by, or has a second number subtracted from it. The second number is stored at an address specified by DE. After the calculation the result is stored in the five bytes beginning at address HL.
To multiply together the two numbers at the top of the calculator stack one method would be as follows:
2A1C40 LD HL,(STKEND)
11FBFF LD DE,FFFB
19 ADD HL,DE
E5 PUSH HL
221C40 LD (STKEND),HL
19 ADD HL,DE
D1 POP DE
CDC517 CALL MULT
CDC617 CALL MULT
Can you follow exactly what is going on? HL is loaded with the contents of the system variable STKEND - which gives the address of the first byte after the end of the calculator stack. DE is loaded with minus five, thus HL is decreased by five. This new value is loaded back into STKEND because we start off with two items on the stack and want to end up with only one. This is the address of one of the numbers to be multiplied. If you follow the listing through carefully you'll see that DE ends up with this value. First though HL is decreased by five again, to find the start of the other number to be multiplied.
To check that it really does work, run this program.
Run it by typing PRINT USR start. What do you get?
But surely there must be easier ways to multiply six by seven. After all, the above program does look very complicated, and not something you'd easily remember. Well it's here that we really do start making full use of the ROM. The following program does exactly the same job, and I shall shortly explain why:
In the NEW ROM, RST 28 means "perform floating point arithmetic". The data that follows tells it precisely what calculations it's supposed to do. The byte 04 means multiply - all of the shuffling around of the calculator stack is done automatically. The byte 34 is used after a RST 28 instruction to indicate that there is no more data to come, and the next machine code instruction should follow.
The RST 28 data codes are:
Don't forget you'll need a byte 34 as well though, to end the data.
You may be wondering what happens if the number on the top of the calculator stack is not an integer between 0 and 65535 (the maximum value any two byte register can hold). Well my first answer would be "try it for yourself and find out". Write a program that adds 8001 to 8001. Write a program that divides eight by three, then a program that divides seven by three. Write a program that subtracts five from zero, and another that subtracts a thousand from zero. But for those of you who are impatient I'll tell you anyway.
If the number at the top of the calculator stack is greater than 65535 then attempting to "unstack" it into BC will result in the program returning to command mode in fact - stopping with error code B (which means out of range).
If the number is a decimal then it will be rounded up or down (not just INTed) to the nearesr whole number. If the decimal part is less than 0.5 it will be rounded down, and if the decimal part is greater than or equal to 0.5 it will be rounded up.
If the number is negative then error B will result, causing an immediate return to BASIC and stopping the program, if there is one.
RST 28 allows you to do much, much more than just simple arithmetic. All of the functions of the ZX81 are available to you. The data code for any particular function is just the character code of that function minus AB. For instance, the character code of SIN is C7. C7 minus AB is 1C (if you don't believe me we'll do it in decimal - 199 minus 171 is 28). This means we can find the SIN of the number at the top of the calculator stack using the sequence:
EF RST 28
1C DEFB 1C (SIN)
34 DEFB 34 (Exit)
To multiply two numbers (at the top of the calculator stack) together and then find the square root of the result we can use the sequence:
You'll notice that this is the first time we've used more than one code in the RST 28 data. In fact you can use as many as you like, provided you end the list with 34.
To save you working it out for yourselves here is a list of the available functions that we are ready to use, together with their appropriate RST 28 code:
CODE 19 EXP 23
VAL 1A INT 24
LEN 1B SQR 25
SIN 1C SGN 26
COS 1D ABS 27
TAN 1E PEEK 28
ASN 1F USR 29
ACS 20 STR$ 2A
ATN 21 CHR$ 2B
LN 22 NOT 2C
Some of the entries in that list may surprise you. For instance we have the use of USR. This is very confusing - being allowed to use USR in the middle of a USR routine - but it's not really. Here's how it works. You've worked your way through a lot of RST 28 data, done a lot of calculation, and now you come across the code 29. What happens next is that the number at the top of the stack should be an integer between 0 and 65535 - or else you'll get an error B. This address is treated as a subroutine and CALLed. This subroutine will run exactly as you'd expect it to. When it's over (i.e. when a RET instruction is reached) the machine will go back to interpreting the RST 28 data from the next byte. USR will of course leave a new value at the top of the stack - the value held by BC at the end of the subroutine.
PEEK works in the same way, finding an address, PEEKing there, then pushing the result to the calculator stack.
All of the functions when used in this way will remove the number currently at the top of the calculator stack and replace it by a new one. For instance, if the number at the top of the stack is 3.5 and the function INT is called, the 3.5 will be removed and replaced by a new value, 3.
The string functions CODE, VAL, and LEN, also CHR$ and STR$ need a small amount of explaining. You see, as well as storing numbers, the calculator stack can also store strings, so if you start off with the number 2000 on the top of the stack, and you then call STR$ (by using code 2A in a RST 28 instruction) then the item at the top of the calculator stack will now be the string "2000". You can demonstrate this with the following:
This should produce the result of CODE STR$ 2000. Does it?
USING THE CALCULATOR'S MEMORY
If you take a quick glance at the manual you'll see that one of the system variables, MEMBOT, is thirty bytes long. This is the calculator's memory area. A quick calculation involving dividing by five, if you're up to it, shows that this leaves enough room to store six different five byte numbers. The six different areas of memory may each be used by RST 28 to store, or retrieve, numbers (but numbers only) from the top of the calculator stack. There are twelve different codes to achieve this - these are:
C0 stores number in memory location 0
C1 stores number in memory location 1
C2 stores number in memory location 2
C3 stores number in memory location 3
C4 stores number in memory location 4
C5 stores number in memory location 5
E0 recalls number from memory location 0
E1 recalls number from memory location 1
E2 recalls number from memory location 2
E3 recalls number from memory location 3
E4 recalls number from memory location 4
E5 recalls number from memory location 5
Storing a number copies it from the top of the stack, and recalling a number simply places it at the end of the stack - it doesn't overwrite the previous top item.
Let's see how we can use this. Suppose we want to find SIN X+COS X. We must use the following technique. Assume that X is at the top of the stack.
Note that the SIN routine changes X into SIN X. When we again recall X there are now two items on the stack: SIN X and X. The COS routine changes X into COS X, so that the two items on the stack, are now SIN X, and COS X. The ADD routine will replace both of these by one single number - the answer we want - SIN X plus COS X.
We have now performed a fairly complex trigonometric function in just eight bytes!
Let's see how we can remove a floating point number from the stack without restricting ourselves to integers less than 65536. The way the ROM does it is like this:
2A1C40 LD HL,(STKEND)
2B DEC HL
46 LD B,(HL)
2B DEC HL
4E LD C,(HL)
2B DEC HL
56 LD D,(HL)
2B DEC HL
5E LD E,(HL)
2B DEC HL
7E LD A,(HL)
221C40 LD (STKEND),HL
As you can probably see for yourself, a five byte number is removed from the stack and stored in the registers A, E, D, C, and B (in that order). You can CALL this routine from address 13E4 13F8.
If the first item in the variable store is X then having popped SIN X plus COS X from the stack you can then store the result back in X as follows:
You can see that it takes more bytes to store the answer than it does to find it in the first place!
Let's see what else we can do with RST 28. We can use the logical functions AND and OR (that is BASIC AND and BASIC OR). Both of these are available from RST 28, having byte codes 08 and 07 respectively. Also you can SWAP the two numbers at the top of the stack. Code 01 will do this.
The following sequence will raise one number to the power of another. Can you see why? After RST 28: 01 22 04 23 34.
[Thunor: Well that's it folks! After 61 days I've finally finished my HTML transcription of this book, on the same day that BBC4 showed the comedy docudrama Micro Men , dramatising the rivalry between Sir Clive Sinclair and Chris Curry of Acorn in the early 80s - fantastic stuff :)]
[Thunor: Just as I started to transcribe this chapter I received an email from tabbycat <tibbytab AT tesco DOT net> warning me about the forthcoming ROM address errors, therefore I was extra vigilant and I've (hopefully) flushed them all out; many thanks for the heads-up.]