MACHINE CODE CALCULATOR
part 3 of 5
by Toni Baker
from ZX Computing, September 1986

Part three: Toni Baker looks at some calculator instructions.


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; GOTOs; FOR/NEXTs and so
on. These things we shall now turn our attention to.


Jump!

The first control instruction you need to know about is the calculator
equivalent to GOTO - called "jump". It takes two bytes - the first
byte is hex 33, which means "jump", and the second byte is the
relative jump displacement, counted from this second byte. Notice that
although the instruction is very similar in operation to the machine
code JR instruction, the displacement is counted from a different
point. In machine code, JR 00 jumps to the next instruction (and hence
achieves nothing except to spend time). In calculator code, however,
you would need to use "jump 01" to do this. In machine code JR -2
would be an infinite loop; in calculator code, "jump -1" would be an
infinite loop; and so on.

Labels may of course be used in calculator code just as in machine
code. This means that you don't have to actually include the jump
displacement in the listing, you can just write "jump LABEL" (or
whatever), and have a separate instruction labelled LABEL elsewhere in
the program.

Calculator jumps are always relative. They can be either forward or
backward, but each jump has a maximum range of 128 bytes. Forward
jumps have displacement bytes from 01 to 7F (1 to 127), whereas
backward jumps have displacement bytes 80 to FF (-128d to -1), though
do remember of course that the displacements are counted from a
different point from what you're used to in machine code.


IF ... THEN

Next we come to the calculator equivalent of IF/THEN. There are two
parts ot an IF/THEN statement in BASIC. There's the condition which we
test (the bit between IF and THEN) and this can either be TRUE or
FALSE. Then there's the action (the bit after THEN) which is executed
only if the condition was TRUE. In calculator code we can actually
achieve all that very simply, and do just about anything we can do in
BASIC (in an IF/THEN statement, that is). The only restriction we have
to put up with is the action part of the statement, which in BASIC can
be any statement whatsoever. In calculator code, only "jump"
instructions are allowed. This means that we are allowed the
equivalent of IF/THEN GOTO, instead of IF/THEN anything; - but that's
not really a constraint, since all you have to do /s structure the
program differently and you can still get away with anything.

What about the condition part? Well, the calculator instruction in
question is called "jump-true". Like "jump" it is two bytes long. The
first byte is 00, and the second byte is a jump displacement, which is
counted in exactly the same way as "jump" described earlier. How it
works is this: "jump-true" removes one item from the top of the
calculator stack - the item is effectively deleted - IF this item was
TRUE (ie. non-zero) then the jump is taken; IF the item was FALSE (ie.
zero) then the jump is not taken, and the displacement byte is ignored.

This idea of using non-zero values to mean TRUE, and zero to mean
FALSE is more than just a convenient way of doing things - it is
fundamental to Spectrum logic. In fact you could even go so far as to
say that TRUE and FALSE are themselves data-types, being neither
numbers nor strings, but a new kind of data- type called a LOGICAL
quantity.

Let me give you an example of this, The calculator instruction "lt_z"
(less than zero) replaces the topmost item on the calculator stack by
either TRUE (if this was a negative number) or FALSE (otherwise). A
second example: the instruction "not" replaces TRUE by FALSE, and
FALSE by TRUE. This kind of thinking, rather than making things even
more complicated, actually simplifies things immensely.

The calculator instruction "not", when viewed in this light, now has a
double meaning. If the item at the top of the stack is a logical
quantity (either TRUE or FALSE) then it will change TRUE to FALSE and
vice versa; but if on the other hand the item at the top of the
calculator stack is a number then you can think of the instruction as
being "equals_zero" whereby the number is removed from the stack, and
replaced by TRUE if the number equals zero, or FALSE if it doesn't. In
fact, this identity between "not" and "equals zero" carries right
through to BASIC. IF NOT (anything) is exactly the same as IF
0=(anything). Try it if you don't believe me. Similarly you can
replace all of your IF (anything)<>0 THEN statements by simply IF
(anything) THEN. But we digress - back to the calculator ...

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 1
CODE NAME            BEFORE AFTER          CORRUPTS COMMENTS
---- ----            ------ -----          -------- --------
00ee jump true       x                              jump if x is true
01   exchange        x,y    y,x
02   delete          x
03   subtract        x,y    x-y
04   multiply        x,y    x*y
05   divide          x,y    x/y
06   power           x,y    x^y            B,M0-M3
07   OR              x,y    x OR y
08   n_and           x,y    x AND y
09   n_le            x,y    x<=y                    B must equal 09
0A   n_ge            x,y    x>=y                    B must equal 0A
0B   n_ne            x,y    x<>y                    B must equal 0B
0C   n_gt            x,y    x>y                     B must equal 0C
0D   n_lt            x,y    x<y                     B must equal 0D
0E   n_eq            x,y    x=y                     B must equal 0E
0F   add             x,y    x+y
10   s_and           x$,y   x$ AND y
11   s_le            x$,y$  x$<=y$                  B must equal 11
12   s_ge            x$,y$  x$>=y$                  B must equal 12
13   s_ne            x$,y$  x$<>y$                  B must equal 13
14   s_gt            x$,y$  x$>y$                   B must equal 14
15   s_lt            x$,y$  x$<y$                   B must equal 15
16   s_eq            x$,y$  x$=y$                   B must equal 16
17   s_add           x$,y$  x$+y$
18   val$            x$     VAL$ x$                 B must equal 18
19   usr_s           x$     USR x$
1A   read_in         x      INKEY$ #x
1B   negate          x      -x
1C   code            x$     CODE x$
1D   val             x$     VAL x$                  B must equal 1D
1E   len             x$     LEN x$
1F   sin             x      SIN x          B,M0-M2
20   cos             x      COS x          B,M0-M2
21   tan             x      TAN x          B,M0-M2
22   asn             x      ASN x          B,M0-M2
23   acs             x      ACS x          B,M0-M2
24   atn             x      ATN x          B,M0-M2
25   ln              x      LN x           B,M0-M2
26   exp             x      EXP x          B,M0-M3
27   int             x      INT x          M0
28   sqr             x      SQR x          B,M0-M3
29   sgn             x      SGN x
2A   abs             x      ABS x
2B   peek            x      PEEK x
2C   in              x      IN x
2D   usr_n           x      USR x          ?
2E   str$            x      STR$ x         M0-M5    (MEM) must equal MEMBOT
2F   chr$            x      CHR$ x
30   eq_z            x      x=0                     TRUE if x=0; false otherwise
30   not             x      NOT x                   TRUE becomes fales & v.versa
31   duplicate       x      x,x
32   mod_div         x,y    x-y*INT(x/y), 
                            INT(x/y)       M0       Will not work if x negative
33ee jump                                           Jump by displacement ee
34   stk_data               x                       Byte 34 must be followed by
                                                    value x in compressed form
35ee djnz                                           B= B-1; jump unless B=0
36   lt_z            x      x<0                     TRUE if x<0; FALSE otherwise
37   gt_z            x      x>0                     TRUE if x>0; FALSE otherwise
38   end_calc                                       Switch off calculator
39   get_argt        x      2*ASN SIN x/PI M0       See Figure 3 [MCCALC3.GIF]
3A   truncate        x      SGN x*INT ABS x
3B   execute B                                      Execute calc.instr. in B reg
3C   e_to_fp         x      ?                       Doesn't work because of bug
3D   restack         x      x                       Restack floating point form
8n   series n        x      Pn(x)          B,M0-M2  Evaluate Tchebyshev p'nomial
9n   series n+10h    x      Pn+10h(x)      B,M0-M2  (see Part 5 of this series)
A0   const_zero             0
A1   const_one              1
A2   const_half             0.5
A3   const_pi/2             1.5707963269
A4   const_ten              10
Cn   store_Mn        x      x                       Store x in memory n (M6-M1F
Dn   store_M(n+10h)  x      x                       not poss. if (MEM)=MEMBOT)
En   recall_Mn              Mn                      M6 to M1F not possible
Fn   recall_M(n+10h)        M(n+10h)                if (MEM)=MEMBOT)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


And AND

There are two instructions for AND. "n_and" works with logical
quantities in a very straightforward way: TRUE AND TRUE equals TRUE;
TRUE AND FALSE equals FALSE; and so on. It can also be used in the
form "number AND logical". We can do this in BASIC, for instance, as
in the expression (7 AND (x>9)) - whereby the number is replaced by
zero if the logical quantity is FALSE. The second AND instruction is
"s_and", for which the two items at the top of the stack must be a
string followed by a logical value. The logical quantity is deleted
from the stack, and the string is replaced by the empty string if, and
only if, the logical value was FALSE.

OR also applies to logical quantities, and is extremely simple. TRUE
OR TRUE equals TRUE; TRUE OR FALSE equals FALSE; and so on - it's
exactly what you'd expect. [Not like that, it isn't. TRUE OR FALSE
equals TRUE. JimG]

With this understanding of TRUE and FALSE, we can now use the
calculator to evaluate an expression which leads to a logical result.
In other words, we can produce a value of TRUE or FALSE from an
expression. The conditional part of an IF/THEN statement can now be
simulated. For instance, take a look at Figure Two. This is part of a
calculator program which evaluates the expression "X = 3 OR X > 5" -
in other words it produces a value of TRUE if X equals three, or if X
is greater than five (X is assumed here to mean the value at the top
of the calculator stack). Once the condition is simulated we can
follow this by a "jump_true" instruction, and our IF/THEN construction
is now complete.

Although a FOR/NEXT type construction could be built using the
calculator memories and the IF/THEN construction, there is a built-in
structure which may be useful here. The calculator instruction "djnz".
But before we can cover the use of this instruction, we must first
examine the concept of the calculator's B register.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 2
To begin, X is at the top of the calculator stack
     31                 duplicate          X,X
     3440B00003         stk data 3         X,X,3
     03                 subtract           X,X-3
     30                 equals zero        X,X=3
     01                 exchange           X=3,X
     3440B00005         stk data 5         X=3,X,5
     03                 subtract           X=3,X-5
     37                 gt zero            X=3,X>5
     07                 or                 X=3 OR X>5
     00xx               jump true          jump if X=3 OR X>5
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Registers

Machine code has loads and loads of registers; the calculator has but
one. This is the calculator's 'B' register, which can store a value of
between zero and 255d.

When the calculator is switched on, by use of the machine code
instruction RST 28, the value of the B register is automatically
carried through from machine code. This means that whatever B
contained prior to RST 28, the calculator's B register will contain
immediately after RST 28. Similarly, when the calculator is switched
off, by the calculator instruction "end_calc", the value of the B
register is again carried through - whatever the calculator B register
contained before "end_calc", then the machine code B register will
contain this value after "end_calc". Furthermore, the value which the
calculator B register contained whilst "end_calc" was being executed
will in addition be stored in the system variable B_REG.

There is a calculator instruction called "execute_B". This assumes
that B contains a calculator code value (eg. 0F for "add" or 03 for
"subtract", et cetera). Its purpose is to execute the calculator
instruction indicated. The instruction sequence RST 28 / "execute_B" /
"end_calc" will therefore execute any desired calculator instruction
from machine code. Obviously this will only work for single-byte
calculator instructions. This instruction sequence actually occurs in
the ROM at address 2756h, and is a key element in evaluating BASIC
expressions.

B Register Dependent Instructions

There are many calculator instructions which actually depend upon the
value of the B register in order to work. The most notable and
important of these are the conditional operators: =, <, >, <=, >= and
<>. Figure One contains a list of all calculator instructions, and
their codes. Some instructions, which are commented upon in Figure
One, will not work correctly unless B contains the code of the
instruction. For instance, the instruction "n_less_than" compares the
two topmost items on the stack, which must be numeric (note that
"s_less_than" can be used for string items) and will delete both items
from the stack, replacing them with TRUE if the first was less than
the second, FALSE otherwise. The code of this instruction is 0D. This
instruction will not work correctly unless B contains hex 0D! It is
not practical to assign the B register from calculator code, so the
only really sensible way of using "n_less_than" would be to use the
instruction sequence "end calc" / LD B,0D / RST 28 / "n_less_than".

Similarly, "s_equal", which tests whether two strings are equal, will
not work unless B contains 16h (since the code of "s_equal" is 16h).
There are a number of instructions which fall into this trap, so
beware of them.

B Register Altering Instructions

There are, in addition, many calculator instructions which corrupt the
B register. For instance, after execution of the instruction "sin"
(which calculates the sine of the topmost number on the stack) the B
register will always contain zero. Figure One also illustrates which
of the instructions will corrupt the B register.

DJNZ

We have now looked at the B register in great detail. With this
knowledge we can look at the calculator "djnz" instruction. Like
"jump" and "jump true" this is a two byte instruction. The first byte
is always 35h. The second byte is a jump displacement, which is
counted in exactly the same way as for "jump". The effect of the
instruction is to decrement the B register. If, after being
decremented, B contains zero, then the jump is taken - otherwise the
jump is not taken and the displacement byte is ignored.

The STK-DATA INSTRUCTION

The "stk data" instruction which was demonstrated in Figure Two is
also very easy to use. It can be used to stack either strings or
numbers onto the calculator stack without having to leave the
calculator. The exact format of the instruction code depends upon what
you want to stack, and the rules are as follows:

Integers between zero and 255d:
"stk data nn" (with nn being an integer between 00 and FF) has hex
code format 34 40 B0 00 nn.

Integers between zero and 65535d:
"stk data mmnn" (with mmnn being a positive integer between 0000 and
FFFF) has hex code format 34 80 B0 00 nn mm.

Negative Integers between -65535d and -1:
Writing -1 as FFFF, -2 as FFFE, and so on, the format is 34 80 BO FF
nn mm (if you can't work out how to write the negative numbers in hex
just add 65536d to the negative number, and write the result in hex).

The empty string:
To stack the empty string don't bother to use "stk data" at all. The
instruction "const zero" (code A0) will stack the number zero at the
top of the calculator stack. This is equivalent to the empty string
(although the reverse is not necessarily true).

Strings less than 256d bytes long:
With the text of the string stored at some fixed address in memory
(ppqq, say), if the length of the string is nn, you can stack the
string using the code format 34 B0 qq pp nn.

Strings of any length:
If the address of the string is ppqq, and the length of the string is
mmnn, then you can stack the string with the "stk data" code format 34
F0 qq pp nn mm.

All other numbers:
Well, step one is to convert the number into floating point form. The
mechanism for doing this was explained in part one of this series.
Suppose the normal five byte form is "aa ee dd cc bb" (with aa
representing the first byte, ee the second, and so on). Now everything
depends upon the value of aa. There are two cases to consider.
Firstly, if the byte is in the range 51 to 8F then you should refer to
the table of Figure Four. Just select whichever of the four forms uses
the least bytes. You have to calculate aa* according to which of the
four forms you require. The second case is when aa falls outside the
range 51 to 8F. In this case you should refer to Figure Five, and as
before you just select which of the four forms uses the least bytes.
You still have to calculate aa*, but in this case you simply subtract
50h from aa for all forms, instead of having a different rule for each
form.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 4
To stack a number where the first byte is in the range 51 to 8F:

Five byte form  Calculator code     Meaning of aa*  Possible range of aa*
--------------  ---------------     --------------  ---------------------
aa ee 00 00 00  34 aa* ee           aa - 50h        01 to 3F
aa ee dd 00 00  34 aa* ee dd        aa - 10h        41 to 7F
aa ee dd cc 00  34 aa* ee dd cc     aa + 30h        81 to BF
aa ee dd cc bb  34 aa* ee dd cc bb  aa + 70h        C1 to FF
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 5
To stack a number where the first byte is in the range 00 to 50, or 90 to FF:
Five byte form  Calculator code
--------------  ---------------
aa ee 00 00 00  34 00 aa* ee
aa ee dd 00 00  34 40 aa* ee dd
aa ee dd cc 00  34 80 aa* ee dd cc     where aa* = aa - 50h
aa ee dd cc bb  34 C0 aa* ee dd cc bb
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Miscellaneous Instructions

It is the purpose of this month's article to cover all of the
calculator instructions (except one, but more of that later), other
than those already covered of course. For this reason I now present
some miscellaneous trivia about those remaining calculator
instructions which don't really fall into any pattern.

There is a calculator instruction called "read in", which evaluates
INKEY$ #X (assuming X is the number at the top of the calculator
stack). Now the Spectrum is a machine of very many bugs, and it's
worth pointing out that the same bug which you find in BASIC is also
present in the calculator. The action of "read in" is twofold.
Firstly, stream X is selected as the current stream, and then an
attempt is made to input a single character from this stream. If the
attempt fails then the empty string is returned. The bug occurs when X
is zero or one. Streams zero and one both select channel "K" - the
keyboard. Thus, firstly, channel "K" is selected - this causes bit
five of (FLAGS) to be reset signalling "ready for a new key". This is
the bug, for immediately after this an attempt is made to input a
character from channel "K" via the subroutine at address 10A8. In the
subroutine bit five of (FLAGS) is tested, and control returns
immediately (indicating failure to detect a key) if this bit is reset.
Therefore - INKEY$ #0 will almost invariably return the empty string -
the only exception to this being the very rare circumstance whereby an
interrupt occurs between the resetting and the testing of the flags
bit, and then only if a new key is detected at that point (courtesy of
the repeat facility). INKEY$ #0, which could have been extremely
useful, has been made completely useless by a pretty stupid bug.

"argt" is a rather strange function. It returns (2/PI)*ASN (SIN(X)).
It's pretty useless as far as you or I are concerned. It is used by
the ROM to help it calculate SIN and COS. Figure Three [MCCALC3.GIF]
shows argt(X) diagrammatically.

"truncate" could be useful, however. It's a bit like INT, except that
whereas INT always truncates downwards, "truncate" will truncate
towards zero. Thus "truncate" literally removes everything to the
right of the decimal point, whether the number is positive or
negative. It is in fact equivalent to SGN(X)*INT(ABS(X)).

"e to fp" is completely and utterly useless, due to a bug even more
stupid than the one in "read-in". Its purpose is to calculate X*10^A,
where X is the number at the top of the calculator stack, and A is the
ordinary machine code A register (which may contain either a positive
or a negative number). The problem is that when RST 28, or any of the
calculator instructions, is executed, the A register is corrupted,
making it completely impossible to use this function! (Note: You can
however use CALL 2D4F to use this subroutine from machine code).

"restack" can be thought of as the opposite of INT. Whereas INT
converts all numbers to integers, "restack" converts all numbers to
floating point numbers. You may recall that the number four (for
example) is stored in five byte form as 00 00 04 00 00. It may,
however, also be stored as 83 00 00 00 00, since this is the floating
point form of the number. All small integers (except zero) may be
written in full floating point form, with the first byte containing
the exponent, and the remaining bytes the mantissa. Zero remains
unchanged as 00 00 00 00 00. You can use "restack" in BASIC - to
restack the variable N just use LET N=(N/2)*2.

There are thirty two "'series" instructions, with codes from 80 to 9F.
Their action is too involved for me to cover in one small paragraph,
so I'll save that one until part five (the final part) of this series.


Communicating with BASIC

Well that covers all of the calculator instructions, but it's not all
that the calculator can do. It can be much more powerful, even than we
have seen so far.

There are many questions still unanswered. How do you get random
numbers (the RND function)? How can you calculate SCREEN$(X,Y)? How do
you read the value of a BASIC variable? How do you slice a string?
These questions, and many more, will soon be answered. To be continued
...
