home   |   the links mine   |   6502 primer
intro: integer math & tables   |   math table files' descriptions   |   rational-number approximations   |   how Intel Hex files work   |   how the files were calc'd & formed

Large Look-up Tables for Fast, Accurate, 16-Bit Fixed-Point/Scaled-Integer Math

For the who are interested in the details:
How the Intel Hex files for 16-bit math look-up tables were formed
(with individual functions further down)
```

Using the HP-71 hand-held computer to calculate the numbers, form the Intel Hex lines, and feed them to the PC over RS-232:
The 71 should display the current cell number and how many cells it's going to do (both in hex).  "At cell XXXX of XXXX"
At any time, you should be able to press a key to pause it to check status.  Then it would stop at the end of the main loop
before doing the next cell, and turn on the "SUSP" annunciator.
After it senses the key to stop the calculation of cells, it should beep and give half a second to get your finger off the key
before looking again, to avoid doing something you didn't want.
Pressing Cont gets it going again.

Intel Hex lines in a text file go like this (minus the spaces):

data line:
:09 514E 00 50AA55A55A20202020 29       Len, 09, is how many bytes in just the data field.  Rel addr=514E.  Rec type=00.  Cksum
covers everything between the colon and the cksum field itself, non-inclusive.
:02 0000 02 58A2 02        Len is 02, normal addr field is 0000, rec type is 02, addr offset in this case is 58A20, & cksum, 02.

EOF line:
:00 0000 01 FF                          (always exactly like this, so you can quote it (without the spaces).)

Remember that the checksum is the number you have to add to the sum of all the previous pairs of digits to get a result that
ends in "00".  So a sum of 6FE would need a checksum of 02 to bring it up to 700.

Use GWBASIC file INFL.BAS to bring the info into the computer.  Batch file INFL.BAT says,
C:\DOS\GWBASIC INFL
( INFL.BAS is the name of the BASIC program that should be in this directory, not in the DOS directory.)
The batch file doesn't seem to work to invoke BASIC, so you might have to type C:\DOS\GWBASIC INFL at the DOS prompt.  If it
still doesn't load the BASIC file after it gets into BASIC, type ' LOAD "INFL ' (without the single quotes).
The BASIC program will append new input to the end of existing ROMIN.HEX file contents.  Old contents are not overwritten.
After that's up and running, you can start the HP-71 program below.
To get out of GWBASIC, type "SYSTEM" (without the quotes).
The resulting file will be called ROMIN.HEX.

Here's the GWBASIC program for the PC:

10 SCREEN 0,0:WIDTH 80:KEY OFF:CLOSE:DEFINT A-Z
100 PRINT "Text input over COM2:9600,N,8,1 hardware handshake only"
110 PRINT "Text file will be ROMIN.HEX"
112 PRINT "Start transfer now."
115 BEEP
120 COMFIL\$="COM1:9600,N,8,1"
130 OPEN COMFIL\$ AS #1
135 BEEP
330 OPEN "ROMIN.HEX" FOR APPEND AS #2
335 BEEP
500 IF EOF(1) THEN GOSUB 600
520 A\$=INPUT\$(LOC(1),#1)
530 PRINT A\$; :REM Prints data on screen
540 PRINT #2,A\$;
560 GOTO 500
600 FOR I=1 TO 31999 : FOR J=1 TO 3000 : NEXT J : IF NOT EOF (1) THEN I=32000 : REM The J loop is set for 1GHz Pentium.
610 NEXT I : IF I>32000 THEN RETURN
620 CLOSE #2:PRINT "Timed out or finished"
999 CLOSE : STOP

HP-71 BASIC file B3EPROM:             ! (The "B3" in "B3EPROM" stands for "Bench-3" workbench computer.)

START: STD
DIM F\$[8]       ! Label name entered by user for the function routine desired.  More details below.
DIM I\$[52]      ! Intel Hex line, long enough for a complete \$20-digit data record, plus 8 digits margin.
DIM I1\$[8]      ! The function routine's output is I1\$, a hex number str of usually 2, 4, or 8 chrs to add to I\$.
DIM A           ! Beginning addr for EPROM.  The 64K block number does not get sent with every line.
DIM N,I         ! N is max cell number + 1 for the table we're working on.  I is which cell we're on (usually starting w/ 0).
DIM J           ! Since I must be preserved, FINISHLN uses J as an incrementer.
DIM R           ! HPIL addr of the RS-232 interface convertor.
DIM S           ! Size of each cell, in bytes.  Most will be 2, but some may be 1 or 4.    3, 5, 6 etc. are not allowed.
DIM T           ! Temporary variable for routines to calculate a value to send out, including checksum.

INPUT "Routine name: "; F\$           ! Enter name of setup subroutine.  (Gets changed later to the one that calculates the values.)
INPUT "decimal ROM bank #",STR\$(0);A ! EPROM page number, 0 being the lowest.  Notice that EPROM bank number doesn't have to be an
A=A*65536                            ! integer, so you can put 2 small tables in 1 bank.  For example, for the 2nd one, enter 7.5.
! Enter the bank of the BEGINNING of the table even if the new file doesn't start there.
INPUT "Beg at cell#:"; I             ! Since the PC crashes occasionally and we want to be able to do portions of a table, allow
INPUT "Last cell#+1="; N             ! for starting someplace besides the beginning and ending someplace besides the end.
! Enter the bank # of the beginning of the table, even if the file does not start at the beg.

RESTOREIO @ R=DEVADDR("RS232") @ REMOTE @ OUTPUT R;"C0;SBE;R0;R1;SL0" @ LOCAL ! Set up the RS-232 interface convertor & HP-71
PRINTER IS RS232                                                              ! for outputting to PC.  (9600,8,N,1, hdwr handshake)
GOSUB F\$                                                                      ! Do setup for the particular table being started.
"Set up PC & press CONT" @ PAUSE
GOSUB CELLDISP                                                                ! Init the running address announcement.
I\$="000002"&DTH\$(A+65536*(I*S)DIV65536)[1,4] @ GOSUB FINISHLN                 ! Send the extended-address line.
FOR I = I TO N-1                                         ! Run this loop once per cell of the table being made.
DTH\$(I)[2]                                            ! Display the number of the cell we're working on.
IF LEN(I\$)=0 THEN I\$=DTH\$(I*S)[2]&"00"                ! If hex data line not started, beg w/ addr & rec type.

GOSUB F\$                                              ! Do the function on I and put the
IF S=1 THEN I\$=I\$&I1\$[4]                              ! answer in the data line,
IF S=2 THEN I\$=I\$&I1\$[4]&I1\$[2,3]                     ! reversing bytes if 2 bytes per answer, or
IF S=4 THEN I\$=I\$&I1\$[7,8]&I1\$[5,6]&I1\$[3,4]&I1\$[1,2] ! reversing bytes in each word and leaving high word to read out last if
! 32-bit, since Forth puts the high cell on the top of the stack.
IF ( LEN(I\$) < 38 ) AND MOD(I+1,8) THEN GOTO L1       ! 38 is for 32 data digits (\$10 bytes) + addr & rec type.
GOSUB FINISHLN
IF FP((I+1)*S/65536) THEN GOTO L1                                 ! If a data line was just sent, see if it's time to send
I\$="000002"&DTH\$(A+(I+1)*S)[1,4] @ GOSUB FINISHLN  @   L1:     ! new extended addr line, and if yes, do so now.
IF NOT KEYDOWN THEN GOTO L2
BEEP 2000,.03 @ GOSUB WAIKEYUP @ WINDOW 1 @ PAUSE @ GOSUB CELLDISP @ DTH\$(I)[2]     ! Pause requested (by pressing a
BEEP 2000,.03 @ GOSUB WAIKEYUP @                              L2:                   ! key?)  If not, continue w/ loop.
NEXT I
OUTPUT R; ":00000001FF"  @  BEEP @ WINDOW 1 @ "Finished "&F\$&" table" @ STOP

WAIKEYUP: IF KEYDOWN THEN WAIKEYUP ELSE WAIT .5
RETURN
CELLDISP: WINDOW 1 @ "At cell xxxx of "&DTH\$(N-1)[2] @ WINDOW 9,12 @ RETURN      ! Init the running addr announcement.

! When you GOSUB FINISHLN, I\$ must already have the addr, rec type, & data.
FINISHLN:                        ! This routine adds the :, rec len, & cksum, and sends the line out over RS-232.
I\$=":"&DTH\$((LEN(I\$)-6)/2)[4]&I\$ ! Put the : on the front, followed by the data length byte (in hex, of course).
T=0                              ! Init the checksum accumulator.
FOR J=2 TO LEN(I\$) STEP 2        ! In this loop, add up the hex value of each pair of digits.
T=T+HTD(I\$[J,J+1])
NEXT J
T=256-MOD(T,256)                 ! Since HP-71 BASIC is not really nimble at hex arithmetic, get the mod-256 negative here.
I\$=I\$&DTH\$(T)[4]                 ! Add that to the end of the line.
OUTPUT R; I\$ @ BEEP 5000,.01     ! Send the line out and make a tick sound to report progress.
I\$=""
RETURN

! (F\$) :                        ! Function uses I as input, and outputs I1\$, which gets added to I\$ to be sent out.  See below.
--------------------

NOTES: DTH\$ in the routines below is used to generate output for the Intel Hex lines.
DTH\$ does rounding; so if you want truncation, you'll have to do that separately.  You don't have to do rounding separately.
Each subroutine should be commented about rounding and truncating.

Routine names are chosen to avoid conflict with names of existing HP-71 functions like SIN and COS.

The first subroutine of each pair below is called by the loop above to set up the info on number and size of cells, first
cell number, and the name of the following routine to do each time through the loop.  I is the number to start on, which
is usually (but not always) 0.  The last cell number will be N-1.  S is the number of bytes per cell.

Unless otherwise noted, cells output by routines below are the normal-for-looking-at high-byte-first, instead of the
regular 6502 bus method.  BASIC program B3EPROM above then reverses the bytes within each word, and puts the low cell
first in the case of 4-byte outputs.  The reason for putting the least-significant cell first is that it allows you to
just increment the address as you read, and the high cell will be read last and put at the top of the stack in Forth.  The
subroutines return their output through I1\$, which is dimensioned to 8 characters max.  Here it will always have either 5
or 8 characters.  In the case of one- or two-byte outputs, I1\$ should have all 5 HP-71 DTH\$ output digits.  In the case of
four-byte outputs, the subroutine will have to use DTH\$ twice and piece together a string of 8 characters.  B3EPROM will
reverse the byte order in each word of I1\$ before adding it to the Intel hex data line.  "12345678" becomes "78563412".
Leading zeroes will often be necessary for keeping I1\$ at a length of either 5 or 8  characters.  No other length is
allowed.

square, with unsigned and unscaled I/O:  (table is 4 banks, 64K 32-bit words)

SQUARE:  S=4  @  F\$="SQUARES"  @  RETURN
SQUARES: T=I*I
I1\$=DTH\$(IP(RES/65536))[2]     ! Form the high 4 hex digits of the answer.
I1\$=I1\$&DTH\$(MOD(T,65536))[2]  ! Form the low 4 hex digits of the answer
RETURN                         ! There is no rounding or truncating here, as all answers are exact.

square here will speed up the SQRT word when you want the 16-bit root of a 32-bit input.  4 EPROM bytes will have to be
programmed for every I1\$ line sent here.

--------------------

1/X, with unsigned I/O, output scaled by 65,536:

INVERT:  S=4  @  F\$="INVERTS"  @  RETURN
INVERTS: T=MAX(I,1)                     ! Rule out a 1/0 situation, which will have to be trapped anyway.
T=IP(4294967296/RES+.5)        ! Do the inversion and round to nearest number.  Don't count on DTH\$ as it can't convert
T=MIN(RES,4294967295)          ! all 8 hex digits at once.  Don't allow answer more than FFFF:FFFF (for 1/1) which must
I1\$=DTH\$(IP(RES/65536))[2]     ! be trapped anyway.  Output high cell first,
I1\$=I1\$&DTH\$(MOD(T,65536))[2]  ! then low cell.
RETURN

Looking up the 1st entry (1/0) should not be allowed, but result will show FFFF:FFFF.
Looking up the 2nd entry (1/1) should not be allowed, as the correct answer is 1:0000:0000, which cannot be shown in 32 bits.
Looking up 1/1 will show FFFF:FFFF also.
This rounds to the nearest answer.

--------------------

sin, input in binary angular measure (BAM), signed output scaled by \$8000 & rounded.  I had previously scaled the output by 28BE
like the input in radians; but this time I decided for the greater output resolution.

SINE: S=2  @  F\$="SINS"  @  RETURN     ! Set up info fo loop to use sine function subroutine below.
SINS: T=I/65536         ! How much of a circle is it?
T=RES*360         ! How many degrees does that translate to?
T=SIN(RES)        ! Get the sin of that many degrees.  Answer is in the range of -1 to 1, inclusive.
T=RES*32768       ! Scale the answer by \$8000, getting a range of -\$8000 to +\$8000, inclusive.
T = RES + 65536   ! HP-71 DTH\$ function doesn't convert a neg number to hex so add \$10000, getting \$8000 to \$18000, inclusive.
T=MIN(RES,98303)  ! We'll AND-out the leading 1 below.  Limit range to \$8000 to \$17FFF.  HP-71 DTH\$() does rounding.
I1\$=DTH\$(RES)     ! Output string.
RETURN

Answers in SINS are rounded to the nearest value that can be represented in a cell of the table.

If you scale the output by \$8000 to get maximum resolution, then you can't have +1 without it turning into a -1, so you would
have to take MIN(RES,\$7FFF); and \$7FFF/\$8000 = .99997d = SIN(89.55°), which hardly seems acceptable.
If you scale the output by \$4000 instead, you can genuinely get to sin(90°) instead of maxing out at sin(89.55); but the
resolution is such that the next step down is sin(89.37°), so the extra resolution over the entire circle afforded by scaling by
\$8000 might be good.  On the other hand, taking the asin(1) would require taking care of this problem somewhere in the program by
doing a MIN( , ) or dividing or whatever, all taking time; and then you have the problem of shifting or dividing without rounding
if you bring the scale factor down after taking a sin.  For example, \$7FFF shifted over 8 bits is \$7F, which makes for a large
error.  However, if you wanted to add \$80 to this and feed it to an 8-bit D/A, you can't give it \$100 anyway.
conclusion: stay with the \$8000 scale factor.

I'm not fond of the idea of scaling the output of this by something between \$8000 and \$4000.  If you're using A/D's and D/A's,
their maximum resolution in bits will be a power of 2, and leaving SIN's output scaled by a power of 2 will make some operations
easier because you can use SHIFT instead of division or multiplication, saving time.

--------------------

ASIN, signed input scaled by \$8000, so we can go ±1 (actually -1 to +.99997) and get -90 to +89.555°, output in PiRads:

ASINE:  S=2  @  F\$="ASINES" @ RETURN
ASINES: IF I > 32767 THEN T=I-65536 ELSE T=I    ! Make the second half of the table be for the negative-number inputs.
T=RES/32768      ! Remove \$8000 scale factor to get sin into range of ±1.  Pos #s get put in table 1st (at lower addr)
T=ASIN(RES)      ! Get the arcsin of that fraction.  Output is in degrees, in the range of -90° to +89.55°.
T=RES/360                 ! Get the angle as a portion of a circle, in the range of -.25 to almost +.25.
T=RES*65536               ! Turn it into PiRads.  Range will be -16384 to +16383.
IF RES<0 THEN T=RES+65536 ! Have negative numbers represented by being over \$7FFF.
I1\$=DTH\$(RES)             ! Output hex string.
RETURN

Answers in ASINES are rounded to the nearest value that can be represented in a cell of the table.
Making output in 2*PiRads (range -\$8000 to +\$7FFF instead of -\$4000 to +\$3FFF) would not improve the output resolution any,
since the input is the limiting factor.  We still would not be able to get any closer to +90° output.

+1 input (for an output of +90°) must be trapped before look-up since it doesn't work here.

--------------------

ATAN45:  S=2  @  F\$="ATAN45S" @ RETURN      ! Set FOR - NEXT loop to end at 32769-1.
ATAN45S: T=I/32768              ! What portion of the tan of 45° is it?  Range is 0-1.
T=ATAN(RES)            ! Get the arctangent of that fraction.  Output is in degrees, in the range of 0 to 45.
T=RES/360              ! Get the angle as a portion of a circle.
T=RES*65536            ! Turn it into PiRads.  Range will be 0 to 8192.
I1\$=DTH\$(RES)          ! Output a 4-hex-digit string.
RETURN

Answers in atan45S are rounded to the nearest value that can be represented in a cell of the table.

Table size will be 32769 cells, one more than you might think.  The extra cell can be the 1st cell of the log2 table, since you
can't have the log of 0.  This means the log2 table should immediately follow the atan45 table in EPROM, so that the final value
kept in cell 0 of the log2 table will be what atan45 put there.  (The routine using the log2 table must trap a 0 input.)
Unsigned input is scaled by \$8000.  \$7FFF gives 44.9991°, and \$8000 gives 45°.

---------------
change atan45 to be a full 128K table, using the 2nd 64K for -45° to 0°.   * * No. I decided against it. * *
atan45:  (  * * * * *   not converted from older method either

T=I
IF RES > 32768 THEN T=RES-65536     ! Make the second half of the table, starting with \$8001, for the negative-number inputs.
T=I/32768           ! What part of the tan of 45°?  Range -.999969 to +1.  Lookup program treats \$8000 as a scaled +1, not -1.
T=ATAN(RES)         ! Get the arctangent of that fraction.  Output is in degrees, in the range of -44.9965° to +45°.
T=RES/360           ! Get the angle as a portion of a circle.
T=RES*65536         ! Turn it into PiRads.  Range will be 0 to 8191 (not 8192, since input=1 will be trapped before starting).
OUTPUT A; DTH\$(RES) ! Output a 4-hex-digit string.  Follow each completed line with CR & LF.

Signed input is scaled by \$8000; but \$8000 is treated as the highest of the positive numbers, not the lowest of the negative
numbers.  This is because the table is primarily intended to be used by a          * * * * *

--------------------

log2, unsigned input not scaled, unsigned output scaled by 4096, maximum output actually the base-2 log of 65,524.91

LOG2:  S=2  @  F\$="LOG2S" @ RETURN        ! Don't allow log(0) which = -inf and that cell is used by atan45.
LOG2S: T=LOG(I)*1.44269504089   ! Get the base-2 log of the number.
T=4096*RES               ! Scale the result by 4096.
T=MIN(RES,65535)         ! Don't let the output get high enough to round to \$1:0000
I1\$=DTH\$(RES)            ! Output hex string.
RETURN

Answers in LOG2 are rounded to the nearest value that can be represented in a cell of the table.

Since log(0) would produce an error and we need that first cell to be the last cell of the atan45 table which is \$8001 cells, this
log2 table must immediately follow the atan45 table in the EPROM.  The calling routine will have to trap negative and zero inputs.
In Concept's automated test equipment, I scaled the output by 2048 because I only allowed a 15-bit input, ie, all the positive
values possible from a 16-bit signed input, and aborted with an error message if the input was negative.

--------------------

alog2, unsigned input scaled by 4096, unsigned output not scaled, max output actually alog2(15.9999779861), which is more than
alog2(\$FFFF) scaled.

ALOG2:  S=2  @  F\$="ALOG2S" @ RETURN
ALOG2S: T=I/4096                ! Unscale the input.
T=RES/1.44269504089     ! Make the input number to be the base-2 log instead of the common log of where we want to go.
T=EXP(RES)              ! Take e^x.  Maximum input will not overflow, so we don't have to limit it.
I1\$=DTH\$(RES)           ! Output hex string.
RETURN

Answers in ALOG2 are rounded to the nearest value that can be represented in a cell of the table.

No problem with 0 input here, but you'll get the best resolution with high numbers of input and output. log2 and alog2 here
don't handle negative numbers.  Inverting, shifting, scaling, etc. will have to be done by the user's program.

--------------------

another LOG2 but leaves the integer part of the log to the user's calling routine.  Input is 0 to .9999847412 scaled by 65,536.

LOG2A:  S=2 @ F\$="LOG2AS" @ RETURN
LOG2AS: T=LOG(I/65536+1)*94548.4621998  ! Unscale input, add 1, get natural log, divide by ln(2), scale result by 65,536.
I1\$=DTH\$(RES)                   ! Output the hex string.
RETURN

(For a slight computing speed improvement, the 65,536 is divided by ln(2) and made into the constant 94548.4621998 to reduce the
number of operations.)  I see now that I could have used the 71's LOGP1 function (ie, ln(1+X) ), but it has enough digits that
the accuracy is nothing to worry about here.

--------------------

another ALOG2, but this one is the reverse of LOG2A.  It has the same input and output range and scale as LOG2A.

ALOG2A:  S=2 @ F\$="ALOG2AS" @ RETURN
ALOG2AS: T=EXPM1(I/94548.4621997)*65536
I1\$=DTH\$(RES)
RETURN

--------------------

another LOG2 but this one zooms in on the low one-sixteenth of LOG2A above, for better resolution at low numbers.  These two
take the place of the ln(1+X) function which is useful for accurate evaluation of ln(x) for x very close to 1.  Hyperbolic
functions, inverse hyperbolic functions, and certain financial calculations involve the expressions ln(1+x) and ex-1.

LOG2B:  S=2 @ F\$="LOG2BS" @ RETURN
LOG2BS: T=LOGP1(I/1048576)*756387.6976    ! The 1st number is 2^20, and the 2nd one is 2^19/ln(2)
I1\$=DTH\$(RES)
RETURN

--------------------

ALOG2B:  S=2 @ F\$="ALOG2BS" @ RETURN
ALOG2BS: T=EXPM1(I/1512775.3952)*1048576  ! \$FFFF input gives output of 46,423.7018472,
I1\$=DTH\$(RES)                    ! rounding to \$B558
RETURN

--------------------

ACOS, input scaled by \$8000, so we can go ±1 (actually -1 to +.99997) and get 180° backwards to .4476°, output in PiRads:
( * * * * * decided against doing this table, since you can get the same info from ASIN, following what's in AboutHexFiles.html)

ARCCOS:  S=2  @  F\$"ARCCOSS" @ RETURN
ARCCOSS:
IF I > 32767 THEN T=RES-65536 ! Make the second half of the table be for the negative-number inputs.
T=I/32768          ! Remove \$8000 scale factor to get sin in the range of ±1.  Pos #s get put in the table 1st (@ lower ADRs)
T=ACOS(RES)        ! Get the arccos of that fraction.  Output is in degrees, in the range of 180° down to .4476°.
T=RES/360          ! Get the angle as a portion of a circle.
T=RES*65536        ! Turn it into PiRads.  Range will be 32768 down to 81.49.
I1\$=DTH\$(RES)      ! Output hex string.
RETURN

Answers in ARCCOSS are rounded to the nearest value that can be represented in a cell of the table.

+1 input (for an output of 0°) must be trapped before look-up since it doesn't work here.

--------------------

(I have not done the LOG10 table.  I intend to just use the LOG2 with a scale factor.)

log10,  unsigned input not scaled, unsigned output scaled by 8192, and will reach a maximum of 4.8165.

LOGTEN:  S=2  @  F\$="LOGTENS" @ RETURN  ! Input 0 should be trapped, since you can't take the log of 0.
LOGTENS: T=LGT(MIN(I,1))                ! Get the base-10 log of the number.  Input of 0 will just give output of 0.
T=8192*RES                     ! Scale the result by 8192.  No need to limit output, since it won't reach FFFF.
I1\$=DTH\$(RES)                  ! Output hex string.
RETURN

Answers in LOGTENS are rounded to the nearest value that can be represented in a cell of the table.

If we scaled the output by such a number that FFFF is the maximum limit for both input and output, we would just be duplicating
the log2 table.  The idea here is that if you can live with the slightly more limited range of input, then you can get the
output where you want it faster by adding or subtracting instead of dividing or multiplying.

log10(\$FFFF)=4.81647330379  If we scale the output by 16,384, we have to limit the input to 44,355, not using the whole input
range.  If we scale the output by 8192, we can use the whole input range, but some of the output range is wasted, and we'd have
to limit the alog10 input range (assuming we keep the same scale factor for that).  Since log10 is used primarily for dB, and we
never need a resolution any better than .01dB, and better resolution at the input end becomes an issue at the lower numbers, I
think it makes more sense to scale the output by 8192.

--------------------

(I have not done the ALOG10 table.  I intend to just use the ALOG2 with the scale factor above.)

alog10, input scaled by 8192, output not scaled.  Inputs beyond 4.8165 (39,457d or \$9A21) will return FFFF, ie, alog10(48165).

ALOG10:  S=2  @  F\$="ALOG10S" @ RETURN
ALOG10S: T=I/8192
T=RES*2.30258509299
T=EXP(RES)
T=MIN(RES,65535)
I1\$=DTH\$(RES)
RETURN

Answers in alog10 are rounded to the nearest value that can be represented in a cell of the table.

(See notes at log10 above.)

--------------------

sqrt, with unsigned and unscaled I/O:  (table only 1 bank long, 64k 8-bit cells)

SQROOT1: S=1  @  F\$="SQRT1S"    @  RETURN
SQRT1S:  I1\$=DTH\$(IP(SQRT(I)))  @  RETURN

Only one EPROM byte per loop will be programmed here instead of two, since we want 64K 8-bit cells for this one.
This table could speed up SQRT by getting a closer first guess and thus reducing the number of iterations.
SQRT1S truncates; ie,, rounds down, but never up.  I don't know yet, but this might make the SQRT Forth word work better.

--------------------

sqrt, with unsigned and unscaled I/O:  (table only 1 bank long, 64k 8-bit cells)

SQROOT2: S=1  @  F\$="SQRT2S"  @  RETURN
SQRT2S:  I1\$=DTH\$(MIN(255,IP(SQRT(I)+.5)))  @  RETURN

Only one EPROM byte per loop will be programmed here instead of two, since we want 64K 8-bit cells for this one.
This table could speed up SQRT by getting a closer first guess and thus reducing the number of iterations.

--------------------

sqrt, with unsigned and unscaled I/O:  (table 2 banks long, 64k 16-bit cells)

SQROOT3: S=2  @  F\$="SQRT3S"  @  RETURN
SQRT3S:  I1\$=DTH\$(MIN(65535,IP(SQRT(I*65536)+.5)))  @  RETURN

Input in a sense is 32-bit (unsigned); but with only 64K cells, it assumes the 16 input bits are actually the high 16 bits of a
32-bit input whose low cell is 0000.  Output is rounded.

--------------------

MULT:  S=2  @  F\$="MULTS"                 @  RETURN   ! Set up info for loop to use multiplication function subroutine below.
MULTS: I1\$=DTH\$( IP(I/256) * MOD(I,256) ) @  RETURN   ! For making the 8x8 multiplication table with 65,536 16-bit answers.
! There is no rounding or truncating here, as all answers are exact.
--------------------

The following was only used for the 15-bit bit-reversing table which I'm not including in the EPROMs.  See further down for the
routine for the other bit-reversing tables.

BITREV:  S=2 @ F\$="BITREVS" @ DIM T1, T2, T3, T4  ! T1 is accum for new number.  T3 is used in place of I because it gets
INPUT "# of bits: "; T2                  ! destroyed.   T2 is the number of bits in the field to reverse.
RETURN
BITREVS: T1=0  @  T3=I
FOR T4=1 TO T2                           ! Wow, reversing bits in BASIC is sure slow on a hand-held from the 80's that
T1=T1*2  @  T3=T3/2                  ! was optimized for decimal floating-point performance on AAA battery power!
IF FP(RES) THEN T3=IP(T3) @ T1=T1+1
NEXT T4
I1\$=DTH\$(T1)
RETURN

--------------------

For bit-reversing on the tables of 14-bit and down, the original main routine was not practical due to the very different
starting-address requirements, so I modified it for those, adding the following to the same program.  For comments, see the
original main routine near the top, and the BITREVS routine immediately above.

BITREV2: STD
DIM F\$[8], I\$[52], I1\$[8], A, N, I, J, R, S, T, T1, B, T3, T4, T5
RESTORE IO @  R=DEVADDR("RS232") @ REMOTE @ OUTPUT R;"C0;SBE;R0;R1;SL0" @ LOCAL
PRINTER IS RS232
INPUT "# of bits: ";B
IF B>8 THEN S=2 ELSE S=1
INPUT "Start addr: ";T5                 ! Typical response would be something like HTD("E000") <ENTER>

"Set up PC & press CONT" @ PAUSE
N=2^B
GOSUB CELLDISP @ I\$=""
FOR I=0 TO N-1
DTH\$(I)[2]
IF LEN(I\$)=0 THEN I\$=DTH\$(T5+I*S)[2]&"00"

T1=0 @ T3=I
FOR T4=1 TO B
T1=T1*2 @ T3=T3/2
IF FP(RES) THEN T3=IP(T3) @ T1=T1+1
NEXT T4
I1\$=DTH\$(T1)

IF S=1 THEN I\$=I\$&I1\$[4] ELSE I\$=I\$&I1\$[4]&I1\$[2,3]

IF LEN(I\$)<38 AND MOD(I+1,8) THEN GOTO L3
GOSUB FINISHLN  @                   L3:
NEXT I
IF LEN(I\$) THEN GOSUB FINISHLN
BEEP @ WINDOW 1 @ "Finished 2^"&STR\$(B)&" table" @ STOP

I used this to form individual files for each number of bits, then edited them together, putting only a single type-02 Intel Hex
record ":02000002F0000C" (for bank F) at the beginning, and a single ":00000001FF" end-of-file record at the end.

--------------------

```

last updated Jun 21, 2012               contact: Garth Wilson, wilsonminesBdslextremeBcom (replacing the B's with @ and .)