!pr0
!lm0
!rm79
18-Digit Arithmetic, Part 5................Bob Sander-Cederlof

There is a lot of ground to cover in this installment, so I have been forced to use smaller type to squeeze it all in.  I want to describe and list the code for the linkage to Applesoft, and for handling arithmetic expressions.


Loading and Linking to Applesoft

The ampersand (&) statement, according to the Applesoft Reference Manual (page 123, top of page) is

!lm+5
!rm-5
"intended for the computer's internal use only; it is not a proper Applesoft command.  This symbol, when executed as an instruction, causes an unconditional jump to location $3F5.  Use reset ctrl-C return to recover."
!lm-5
!rm+5

Not so!  The &-statement is intended for adding extensions to the Applesoft language!  It does cause a jump by the Applesoft interpreter to $3F5.  If you have not set up any extensions you will get a syntax error when you use "&".  But if you have extensions installed, you can work all manner of miracles.  DP18 is one such miraculous extension.  There are many more around, both in the public domain and in the form of commercial products.

This of course leads to a problem.  What if you want to use two or more such extensions?  I have written DP18 so that you can chain together one or more additional extension packages as you see fit.

It is very important to decide where the DP18 package will reside in memory.  I spent weeks tossing around various options, back when I was designing the DPFP 21-digit package.  Of course, at that time, Apples came equipped with anywhere from 16K to 64K RAM; now you can depend on almost all Apples having at least 48K RAM.  I still favor the decision I made four years ago, to load the double precision code at $803, after shifting the Applesoft program far enough up in RAM to leave room.

I have a program I call ML LOADER, which is included on the S-C Macro Assembler disk as a sample program.  It performs the function of moving an already- executing Applesoft program up higher in RAM.  By including the following line at the beginning of my Applesoft program, I can load DP18 and link it to the & hook at $3F5:

10 IF PEEK(104)=8 THEN PRINT CHR$(4)"BLOADB.ML LOADER"
  :POKE 768,0 : POKE769,30 : CALL770
  :PRINT CHR$(4)"BLOAD DP18"
  :POKE 1014,PEEK(2051) : POKE 1015,PEEK(2052)

PEEK(104) looks at the high byte of the starting address of the Applesoft program.  Normally Applesoft programs begin at $801, so PEEK(104)=8.  If DP18 has not yet been loaded, then PEEK(104) will still be equal to 8.  If it has already been loaded, then the rest of line 10 is skipped.

B.ML LOADER loads at $300.  Its function is to shove the Applesoft program higher in RAM.  You POKE the distance to shove into 768 (low byte) and 769 (high byte), than CALL 770.  When you wake up an instant later, you have been relocated.  The Applesoft program keeps on executing as though nothing happened.  Only now there is a gaping hole between $800 and whatever.

DP18 loads at $803 and extends well into page $25.  I grabbed 30 pages, moving the Applesoft program to $2601.It thus clobbers hires screen 1 memory.  If you want to use hires screen 2 and the program is too large to fit under it, use POKE 769,88 instead of POKE 769,30 in line 10.  This makes the program start at $6001, and leaves $2600-3FFF totally unused.

If you want to use other ampersand routines, POKE the link address at locations 2053 and 2054 ($805 and $806).  If DP18 finds an ampersand command not starting with "DP", it jumps indirectly through this vector.  The vector initially contains the address of Applesoft's SYNTAX ERROR routine, but it can be changed to allow using more than one set of &-routines.


Calling DP18

Whenever you want to execute a DP18 feature, you use the "&DP" statement.  If DP18 has been properly connect to the & hook at $3F5, then the & will send the computer to DP18 (at line 2430 in the listing which follows).   At this point DP18 begins to analyze and execute the characters that follow the ampersand.

If the first two characters after the ampersand are not "DP", the program will jump to a vector at $805 & $806.  This normally points to Applesoft's SYNTAX ERROR routine.  However, this location can easily be patched to point to your own ampersand routine.

If the first two characters are correct, DP18 will analyze succeeding statements separated by colons on the same line.  There must be a colon immediately after the "&DP" statement.  All of the rest of the statements on the line will be executed by DP18, rather than by the normal Applesoft interpreter.  If you want to shut off DP18 before the end of the line, two colons in a row with nothing between will do so.

     150  & DP: INPUT X(0)
     160  & DP:Y(0) = X(0) * X(0) * PI: PRINT Y(0) :: GOTO 150

It is not necessary that the "&DP:" be the first statement in a line.  For example, the following statement will take the square root of a number if the two strings are equal.  It uses an Applesoft string comparison, and a double precision square root.

     170  IF A$ = "SQR" THEN  & DP:Y(0) =  SQR (X(0))

You can also type double precision statements as direct commands in Applesoft once DP18 has been loaded.

     ]&DP:PRINT X(0): PRINT X(0) ^ 2

Four types of statements can be executed by the DP18 package:  assignment, INPUT, PRINT, and IF statements.  INPUT and PRINT statements will be covered in a later installment.

The DP18 IF statement evaluates a logical expression in 18-digit precision, and then reverts to normal Applesoft processing:

     180 &DP : IF A(0) < 1.52345678976543 THEN X = 3

The DP18 assignment statement takes two forms:  real assignment, and string assignment.  String assignment is used to convert DP18 values to strings, so that they can be used by normal Applesoft:

     190 &DP : A$ = STR$ (X(0))
  
Real assignments are the normal computational statements, like:

     200 &DP : A(0) = (4*PI*R(0)^3)/3


DP18 Variables

All variables referenced by DP18 must consist of two adjacent array elements.  The array must be a REAL array, that is, it must not be INTEGER or STRING.

Remember that Applesoft array subscripts begin with 0 and go up to the limit defined in the DIM statement.  An array dimensioned "3,11,11" has three dimensions.  The first runs from 0 to 3; the second from 0 to 11; and the third also from 0 to 11.  It could contain 4*12*12=576 real elements, or 2*12*12=288 double precision elements.

Applesoft arrays are stored in memory with the leftmost subscript varying the fastest.  For example, in the array XY(3,10,10), element XY(0,j,k) comes immediately before element XY(1,j,k).  Therefore you may, in effect, create an array of double precision values by merely prefixing an extra dimension to the dimension list.

If you wish to set up separate variables, you may do so by dimensioning them to have two real elements each.  For example, the statement

     10  DIM A(1),B(1),C(1),X(1)

will set up four separate variables for use with DP18.  You reference the variables within double precision statements with the subscript 0.  For example:

     20  & DP:X(0) = (A(0) + B(0)) * C(0)

Note that you don't have to dimension these variables, since Applesoft will default to a dimension of 10.  However, it is a good idea to dimension all double precision variables because it saves memory (only 2 real elements are allocated instead of 11) and it makes it easier for someone else to follow your program.

If you wish to create an array of double precision values, you do so by dimensioning the array with one extra dimension.  The extra dimension comes first and should be "1"; this dimension generates two real items, or one double precision item.  For example,

     10  DIM A(1,12),B(1,5,6)

creates two arrays that can be used for double precision values.  The array A can be thought of as an array of 13 double precision values from A(0,0) to A(0,12).  The array B could store 42 double precision values from B(0,0,0) to B(0,5,6).  If you always remember to use one extra dimension, to put that extra dimension first, to set that dimension to "1", and to refer to items with the first subscript = 0, then you will succeed in using DP18.


DP18 Constants

Double precision constants are entered in the same way as single precision constants.  The differences between standard Applesoft and the DP18 constants are that DP18 converts and stores 18 significant digits rather than 9, and that exponents may be in the range of +/- 63 rather than +/- 38.

Conversion of constants is very fast in DP18.  DP18 will convert constants over 4 times faster than normal Applesoft, even using more digits!  It is quicker to convert a constant than it is to find and use a DP18 variable, especially multi-dimensioned variables.  This is completely opposite from normal Applesoft, where variables are quicker than constants.


Conversion Between Single and Double Precision

You will often need to convert a single precision value into a double precision one for purposes of computation.  This is easily done by first converting it to a string and then using DP18's VAL function as shown here.

     100  REM CONVERT X TO DOUBLE PRECISION VALUE
     110  DIM DP(1)
     120  INPUT "VALUE TO BE CONVERTED? ";X
     130  &DP:DP(0) =  VAL ( STR$ (X))
     140  &DP: PRINT DP(0)
     150  GOTO 120

You will also want to convert from double precision back to single precision.  This also involves converting to a string, but takes more than one statement.

     100  REM CONVERT DP(0) TO SINGLE PRECISION VALUE
     110  DIM DP(1)
     120  &DP:INPUT "VALUE TO BE CONVERTED? ";DP(0)
     130  &DP:A$ =  STR$ (DP(0))
     140  X =  VAL (A$) : PRINT X
     150  GOTO 120

Note that lines 130 and 140 could be combined onto one line if there were two colons separating the statements.  See the section on functions for more information about the STR$ and VAL functions.


DP18 Arithmetic Expressions

Expressions in DP18 are very much like expressions in Applesoft.  Except for AND and OR, they are evaluated using the standard rules of precedence as found on page 36 of the Applesoft manual.  AND and OR have the same precedence in DP18 and are executed left to right.  The order of precedence is listed below.  Operations on a higher line are executed before operations on a lower line.  Operators on the same line are executed left to right.

!lm+5
( ) function calls
+ - NOT        unary operators
^
* /
+ -
<  >  =  <=  >=  =>  =<  <>  ><
AND OR
!lm-5
These all work the same as they do in Applesoft, except that they operate on double precision numbers.

DP18 supports many of the numerical functions that Applesoft does:  SIN, COS, TAN, LOG, EXP, SGN, ABS, INT, SQR, ATN, VAL, and the string function STR$.  There is also a special function, PI, which has no arguments.  You don't even write parentheses after it.  You just use it like it was a constant.  Wherever you use it, you get the value pi accurate to 20 digits.


Explanation of the Code

As in previous installments of this series on DP18, I cannot show everything at once.  A whole series of subroutines which have either already been printed or will be printed in future installments are represented in this listing by ".EQ $FFFF" in lines 1330-1550.  All the data areas actually used in the code listed this month are included, so that you can see what the code is working with and on.

As mentioned above, the "&" statement sends Applesoft to line 2430.  Lines 2430-2500 check for "DP" following the ampersand.  If not "DP", then lines 2370-2390 branch to the next ampersand interpreter in your chain.  If you have not set up another &-interpreter, then the SYNTAX ERROR message will pop out.

DP.NEXT.CMD (lines 2520-2800) begins by looking for a colon or end-of-line.  End of line means you are through with DP18, so an RTS carries you back to the Applesoft interpreter.  A colon means you are ready with a DP18 statement.  If the next character is also a colon, however, you are sent back to Applesoft (lines 2570-2580).  Next I check for the three legal tokens (IF, INPUT, and PRINT) and branch accordingly.

Since IF is simple and IF is included in this listing, let's look at IF now.  Lines 3130-3280 handle the IF statement.  First I evaluate the expression, which is considered to be a logical expression with a true-or-false value.  Zero means false, non-zero means true.  Following the expression I must find either a THEN or GOTO token.  The truth value is found in DAC.EXPONENT, because a $00 exponent means a zero value.  AS.IF.JUMP in the Applesoft ROMs can handle the rest, because the THEN or GOTO pops us out of DP18 back to normal Applesoft.  Neat!

Meanwhile, back in DP.NEXT.CMD, if the statement is not IF-INPUT-PRINT it must be an assignment statement.  If I am successful at getting a variable name next, it may be either a DP18 variable or a string variable.  If AS.VALTYP is negative, it is a string variable and DP.STR takes over.  If not, CHECK.DP.VAR will verify that it is a real array variable.  The address is saved at RESULT, the DP18 expression evaluated, and then the answer saved at RESULT.  And back to the top of DP.NEXT.CMD.

DP.STR handles statements like A$=STR$(xxx) where xxx is a DP18 expression.  You can probably follow the comments in this section.

GET.A.VAR checks to see that the current character from your program is a letter, because all variables must start with a letter.  If so, AS.PTRGET will search the variable tables and return with an address in the Y- and A-registers.  CHECK.DP.VAR compares this address with the beginning of the array variable table.  If it is inside the array table, and if the variable is real (not string or integer), it is a valid DP18 variable.

DP.EVALUATE cracks and calculates a DP18 expression.  A special stack is used for temporary values, and it is deep enough to hold 10 of them.  If your expression is so complicated that more than 10 temporary values need to be stacked (very unlikely), then the FORMULA TOO COMPLEX message will scream.  Applesoft uses the hardware stack in page 1 for the same purpose, but it only has to stack 5-byte values; DP18 stacks 12 bytes for each value.  EVALUATE starts by emptying the stack, zeroing a parenthesis level count, and clearing the accumulator (DAC).  After DP.EXP finishes all the dirty work, The stack must be empty and the parenthesis level zero or there was a SYNTAX ERROR.

Actually parsing and computing an expression can be done in many ways.  I chose a recursive approach that breaks the job up into little independent pieces small enough to understand.  First, let's allow all expressions to be a series of relational expressions connected with ANDs and ORs.  The simplest case of this is merely a relational expression alone.  And the simplest relational expression is an expression all by itself with no relations.  If the expression does have relational operators or ANDs or ORs, the result will be a true or false value.  If not, it will have a numerical value.

Comment blocks atop DP.EXP, DP.RELAT, DP.SUM, etc. show the continued breakdown of parts of an expression.  DP.RELAT connects one or more sums with relational operators.  DP.SUM connects one or more terms with "+" and "-" operators.  DP.TERM connects one or more factors with "*" and "/" operators.  DP.FACTOR connects one or more elements with the exponentiation operator (^).  DP.ELEMENT cracks a constant, searches for a variable's value, calls a function, or calls on DP.EXP recursively to handle an expression in parentheses.  DP.ELEMENT also handles the unary operators "+", "-", and "NOT".

If DP.ELEMENT determines that the element is a function call, there are several types.  The VAL function is supervised by lines 5800-5830.  Since the argument of the VAL function is a string expression, it is significantly different from the other functions.  The ATN function is also given special treatment, because DP18 allows the ATN function to be called with one or two arguments.  All the rest of the functions have one DP18 expression for an argument, so they are handled as a group.  A table of addresses at lines 2160-2310 directs us to the appropriate processor.  The code for all these functions will be revealed in future installments.

DP.VARNUM is called upon to handle variables and numbers.  First lines 6130- 6280 check for and handle the special DP18 constant "PI".  Lines 6300-6350 handle DP18 variables, and lines 6370-6470 handle numbers.

PUSH.DAC.STACK pushes the 12-byte value in DAC on the special expression stack, unless there is not enough room.  POP.STACK.ARG pulls a 12-byte value off the stack and plops it into ARG.


And Next Month...

There are three major areas left for future installments:  INPUT, PRINT, and the math functions.  Some of you have been diligently studying and entering each installment as we go, and are gradually obtaining a powerful package.  Others are waiting for the Quarterly Disks, to conserve their fingertips.  Remember, all the source code each three months is available on disk for only $15.
