MACHINE CODE CALCULATOR
part 2 of 5
by Toni Baker
from ZX Computing, August 1986

Toni Baker looks at some of the functions
of the Spectrum's built-in calculator.


This article is all about how to use that magical machine code
instruction RST 28, which controls the Spectrum Calculator. We shall
cover the principles, and most of the calculator functions (ie. the
easy ones). In next month's article I shall deal with the remainder of
these functions.

RST 28 is a machine code instruction. It is a short form of CALL 0028,
which simply calls a machine code subroutine at address 0028 (in the
ROM). But that's not the easiest way to remember it, because since
address 0028 is in the ROM it means that all Spectrum users will find
RST 28 working identically (although WARNING - the Shadow ROM of the
ZX Interface One, or the New ROM of the Spectrum 128, should not be
paged in when RST 28 is used in this way). The easiest way to remember
it is that RST 28 is an instruction meaning "Switch the calculator on".


RST 28

A RST 28 instruction must be followed by a sequence of data bytes,
each of which is interpreted as a "Calculator Instruction". This
sequence of bytes is therefore a sequence of calculator instructions,
and, as we know, any sequence of instructions constitutes a program.
RST 28 thus initiates execution of a program - not BASIC, or machine
code, but a calculator code program.

Such a "Calculator Program" must be terminated by the calculator
instruction "end_calc", which switches the calculator off. All bytes
following this end_calc instruction will be interpreted as normal
machine code instructions. The hexadecimal code for "end_calc" is 38.
This means that the shortest possible calculator program is just two
bytes long, as follows:

     EF       DEMO      RST  28            ;Switch the calculator on
     38                 end_calc           ;Switch the calculator off

As you can see from the comments, the above program has the effect of
switching the calculator on, then off again, and so effectively
achieving nothing at all. It does, however, achieve one useful
"side-effect", which is to assign DE with the contents of the system
variable (STKEND), and HL with (STKEND)-5. In other words, both HL and
DE will become pointers into the calculator stack, which was discussed
last month. HL will point to the first byte (the exponent byte) of the
topmost item on the calculator stack, while DE will point to the first
spare byte beyond the calculator stack.

Let's see what we can do with the calculator now, shall we? That is to
say, let's examine what happens when we put instructions between RST
28 and end_calc.

Figure One in this article is an Appendix, which lists those of the
calculator instructions which are covered by the information in this
article. Each instruction has a hex code (the data byte to be used in
the byte- sequence), and a name (which describes its function). The
first calculator instruction we shall learn is "add" which, as its
name implies, will add two numbers together. To use "add" the two
topmost items on the calculator stack must be numeric. The action of
this function is to remove these two numbers from the stack, and to
replace them with their sum. Thus "add" will reduce the calculator
stack by one item. "add" is called a BINARY function because it
requires two operands (although it only produces one result).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE ONE - SOME OF THE CALCULATOR INSTRUCTIONS
------------------------------------------------
CODE NAME            BEFORE AFTER            DESCRIPTION
---- ----            ------ -----            -----------
01   exchange        x,y    y,x              exchange two items
02   delete          x                       delete one item
03   subtract        x,y    x-y              subtract second number from first
04   multiply        x,y    x*y              multiply two numbers together
05   divide          x,y    x/y              divide first number by second
06   power           x,y    x^y              raise 1st number to power of 2nd
07   or              x,y    x OR y           logical OR function
08   and (no.&no.)   x,y    x AND y          logical AND function (numbers only)
0F   add (nums.)     x,y    x+y              add two numbers together
10   and (str.&no.)  x$,y   x$ AND y         empty string if number is zero
17   add (strs.)     x$,y$  x$+y$            add second string to first
19   usr (str.)      x$     USR x$           address of UDG given by string
1A   read in         x      INKEY$ #x        read in byte from given channel
1B   negate          x      -x               negate the number
1C   code            x$     CODE x$          the character code of the character
1E   len             x$     LEN x$           the number of chars in the string
1F   sin             x      SIN x            the sine of the number
20   cos             x      COS x            the cosine of the number
21   tan             x      TAN x            the tangent of the number
22   asn             x      ASN x            the arcsine of the number
23   acs             x      ACS x            the arccosine of the number
24   atn             x      ATN x            the arctangent of the number
25   ln              x      LN x             the natural logarithm of the number
26   exp             x      EXP x            the antilogarithm of the number
27   int             x      INT x            largest integer not > number
28   sqr             x      SQR x            the square root of the number
29   sgn             x      SGN x            -1, 0 or +1, according to sign
2A   abs             x      ABS x            absolute magnitude of number
2B   peek            x      PEEK x           contents of address given
2C   in              x      IN x             read in byte from input port
2D   usr (num.)      x      USR x            value determined by m.code subrt.
2E   str$            x      STR$ x           string of the number as printed
2F   chr$            x      CHR$ x           character whose code is given
30   not             x      NOT x            1 if number equals zero, else 0
31   duplicate       x      x,x              extra copy of topmost item
32   mod_div         x,y    x MOD y,x DIV y  quotient and remainder (integers)
36   less_zero       x      x<0              1 if number less than 0, else 0
37   gtr_zero        x      x>0              1 if number greater than 0, else 0
38   end_calc                                switch off calculator
39   get_argt        x      (2/PI)*ASN SIN x 
3A   truncate        x      (SGN x)*INT (x*SGN x)
3D   restack         x      x                re-stack in floating point form
A0   stk_zero               0                stack the number zero
A1   stk_one                1                stack the number one
A2   stk_half               0.5              stack the number one half
A3   stk_pi/2               PI/2             stack the number half of PI
A4   stk_ten                10d              stack the number ten
C0   store_M0        x      x                store in memory zero
C1   store_M1        x      x                store in memory one
C2   store_M2        x      x                store in memory two
C3   store_M3        x      x                store in memory three
C4   store_M4        x      x                store in memory four
C5   store_M5        x      x                store in memory five
E0   recall_M0              M0               recall from memory zero
E1   recall_M1              M1               recall from memory one
E2   recall_M2              M2               recall from memory two
E3   recall_M3              M3               recall from memory three
E4   recall_M4              M4               recall from memory four
E5   recall_M5              M5               recall from memory five
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Binary functions

There are many other binary functions. As you would expect,
"subtract", "multiply" and "divide". There is also "power" which
raises one number to the power of another.

The calculator also provides us with UNARY instructions. These work by
taking just one operand from the stack, and replacing it with a result
(for instance "sqr", which will remove the topmost item on the stack,
and replace it with the square root of that number). These leave the
length of the stack unchanged, because the number of items on the
stack is the same afterwards as it was before. Figure Two [see
MCCALC.TAP "part 2"] is a calculator program to calculate SQR(COS X +
SIN X) - you should be able to follow it quite easily.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE TWO
----------
     EF       DEMO_2    RST  28            ;Switch on calculator (assume one
                                           ;number, x, on the calculator stack)
     31                 duplicate          x,x
     20                 cos                x,COS x
     01                 exchange           COS x,x
     1F                 sin                COS x,SIN x
     0F                 add                COS x+SIN x
     28                 sqr                SQR(COS x+SIN x)
     38                 end_calc           ;Switch off calculator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Some of the functions are logical ones, for instance AND and OR. These
work the same way as they do in BASIC (ie. X AND Y equals X, unless Y
is zero, in which case it equals zero; similarly X OR Y equals X,
unless Y is non-zero, in which case it equals one). The function NOT
is available, which produces one if the original number was zero, but
zero otherwise. There are also two new logical functions:
LESS_THAN_ZERO and GREATER_THAN_ZERO. As their names suggest, LT_Z
will produce one if the original number was less than zero, zero
otherwise; and GT_Z will produce one if the original number was
greater than zero, zero otherwise.

There are five ready made constants which you can stack onto the
calculator stack automatically. Their codes run from A0 to A4
inclusive. A0 for instance is the calculator instruction "stk_zero",
and its action is to leave an additional item - the number zero - at
the top of the calculator stack. Similarly "stk_one" will stack the
number one; "stk_half" will stack the number 1/2 (remember the
calculator stack can hold full floating point numbers); "stk_pi/2"
will stack half of pi (or 1.5707963); and finally "stk_ten" will stack
the number ten.

Some of the entries in Figure One may surprise you. For instance, we
have two functions - "usr (string)" and "usr (number)". In BASIC there
is only one USR keyword. The difference is that in BASIC the same
keyword does two separate jobs. USR "J" for instance will give you the
address of user-defined graphic-J because "J" is a string, not a
number. On the other hand, PRINT 65536-USR 7962 will tell you how many
bytes of memory you've got left. The calculator is not so clever. It
has no way of knowing whether the item at the top of the calculator
stack is a number or a string. You have to tell it! For this reason
there are two separate calculator functions for the two different
operations.

"usr (number)" is by far the most confusing calculator instruction of
all. What it does precisely is this: one number is removed from the
top of the calculator stack, and stored in the BC register pair (if it
will fit, of course - if it doesn't you'll get an error report). Then
a machine code subroutine will be called at this address. On return
from such a subroutine, the value contained by the BC register pair is
placed at the top of the machine stack. The next calculator
instruction in sequence will then be executed.

There are other instructions which discriminate between strings and
numbers. ADD for instance has two different calculator codes: "add
(numbers)" will add numbers together in the normal way (so that
1+2=3), whereas "add (strings)" will concatenate two strings (so that
"CAT" + "FISH" = "CATFISH").

PEEK (byte 2B) is possibly confusing. This works by POPping an address
from the calculator stack, PEEKing there, and PUSHing the result back
onto the stack.


Calculator Memories

The Spectrum Calculator has six memories (though this number may be
increased, as we shall see later), each of which is capable of storing
either a number or a string. The calculator instruction set includes a
set of instructions for storing the item at the top of the stack in
one of the memories (codes C0 upwards) - these instructions do not
remove the number from the top of the stack, they just make an
additional copy in one of the memories. Conversely, codes E0 upwards
will retrieve a number or string from one of the memories. Figure
Three [see MCCALC.TAP "part 2"] is an alternative way of calculating
SQR(COS X + SIN X), but this time using memories.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE THREE
------------
     EF       DEMO_3    RST  28            ;Switch on calculator (assume one
                                           ;number, x, on the calculator stack)
     C3                 store_M3           (Memory 3 contains x)
     20                 cos                COS x
     E3                 recall_M3          COS x,x
     1F                 sin                COS x,SIN x
     0F                 add                COS x+SIN x
     28                 sqr                SQR(COS x+SIN x)
     38                 end_calc           ;Switch off calculator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Before I go on to tell you all about how you can increase the memory
space, I'd like to add a word or two of warning: The functions SIN,
COS, TAN, ASN, ACS, ATN and LN will corrupt memories 0, 1 and 2. The
function EXP will corrupt memories 0, 1, 2 and 3. The functions INT,
MOD_DIV and GET_ARGT corrupt memory 0. The function STR$ corrupts all
six memories, and the function USR (number) may or may not corrupt
absolutely anything.

Also, the machine code subroutine PRlNT_FP at address 2DE3 which
prints a floating point number, will corrupt all six memories, and
printing any of the built-in graphics characters (CHR$ 128 to CHR$
143) will corrupt memories 0 and 1.

It is certainly important to remember if memories are corrupted, since
otherwise the end result will be wrong. The Spectrum, being a machine
of very many bugs, gives us a ready made example of this malpractice.
As I've already stated, the function INT corrupts memory zero. In
point of fact it will only corrupt memory zero if the number being
INTed is negative, otherwise memory zero is unchanged. The function
MOD_DIV is supposed to remove two numbers (x and y, say) from the
calculator stack, and to replace them with two new numbers:
x-y*INT(x/y), INT(x/y). Unfortunately, the ROM routine fails to take
into account the fact that INT may corrupt memory zero. The
consequence is that if x is negative then MOD_DIV will produce the
wrong answer! (the first of the two new numbers will be incorrect.)


Adding Calculator Memories

Giving yourself more than six calculator memories all hinges on the
system variable MEM. You see, each item on the calculator stack takes
five bytes. Therefore, each of the calculator memories must also take
five bytes. Memory zero is stored at address (MEM), memory one at
(MEM)+5, memory two at (MEM)+0A, and so on. The address of MEM is
5C68, and it normally contains the value 5C92. This means that it
normally points to the system variable MEMBOT. Since MEMBOT contains
thirty bytes, it follows that there is room to store exactly six
memories (since five times six equals thirty).

Suppose you wanted to give yourself thirty-two memories (this is the
maximum number of memories possible). Firstly you would need to create
six hundred and forty bytes of spare memory. You could use space above
RAMTOP for this purpose, by CLEARing enough space in BASIC. Another
way of doing it would be to load BC with the number of bytes needed
(in this case 640d, or 280h), then use the instruction RST 30. This
will create the required number of spare bytes in the workspace.
Following the use of RST 30, DE will point to the first byte, and HL
will point to the last. Thus, all you now need do is to load (MEM)
with the value in DE.

The calculator codes to operate the new memories will be C6
(store_M6), C7 (store_M7), and so on up tb DF (store_M1F); also E6
(recall_M6), E7 (recall_M7), and so on up to FF (recall_M1F).

Warnings are attached to moving (MEM) as well. If (MEM) contains any
value other than 5C92 then the calculator function STR$ will not work,
and neither will the machine code subroutine PRINT_FP which prints
floating point numbers. (MEM) must be restored to 5C92 before either
of these are used.


More complex programs

As I have already stated, the sequence of instructions between RST 28
and "end_calc" constitutes a "Calculator Program" written in a
language called "Calculator Code", but no language would be complete
without controlling instructions: IF/THENs; GO TOs; FOR/NEXTs; and so
on. These things we shall now turn our attention to. To be continued ...


