[net.micro.cbm] c64 floating point

miller@uiucdcs.UUCP (02/28/84)

#N:uiucdcs:36100052:000:574
uiucdcs!miller    Feb 27 22:58:00 1984


     I have received numerous requests for reposts of my 4 part c64 floating
point series.  It seems almost everyone missed one or more parts.  I had been
mailing the missing parts to those people; however the amount of requests grew
so that I think a general reposting is in order.  Thus, the response to this
base note will include all four parts of the series, along with replies from
a few people who were kind enough to point out some problems with my articles.
It is long so if you are on a slow terminal I suggest you skip the response.

A. Ray Miller
Univ Illinois

miller@uiucdcs.UUCP (02/28/84)

#R:uiucdcs:36100052:uiucdcs:36100053:000:16902
uiucdcs!miller    Feb 27 23:00:00 1984


/***** uiucdcs:net.micro.cbm / miller /  1:01 am  Jan 28, 1984 */

     Well, here is the first installment of how to use the c64 floating point
routines from assembly language.  When using these routines, keep in mind that
they reside in the c64's ROM.  Hence, if you flip that out to expose the RAM at
those addresses, you will be unable to access these routines.
     This time, I'll go over the format of floating point numbers as used in
the machine.  Later, I'll describe which routines are available and how to
call them.  (By the way, since the VIC20's Basic interpretor is almost exactly
the same as on the c64, most of what I say here applies to that machine too.
Some of the addresses may have to be changed though.)
     C64 Basic floating point numbers are broken up into three contiguous
sections: the exponent (one byte) followed by the mantissa (four bytes)
followed by the sign of the number (one byte).  I'll go over each of the parts
in turn.
     The exponent is stored in "excess 128" which means it has a value 128
higher than the true exponent of the number.  That is, exponents can have a
value of -128 to 127 but are stored as 0 to 255.  (The purpose of "excess n" is
to avoid having to deal with negative exponents.  It makes comparisons for
size much easier.)  What the exponent tells you is how many bits the mantissa
must be shifted from its stored value to give you its true value.
     The mantissa is stored in a normalized format, i.e., the binary point is
assumed to be to the left of the value and the leftmost bit is always a 1.
Hence the 32 bit values stored here represent numbers between zero and one.
     If the high order bit of the sign byte is on then the number is negative.
If it is off then the number is positive.
     So to compute the true value of the number stored you can use the follow-
ing equation:
                  0.mantissa * 2 ** (exponent - 128)
(Multiplying by powers of two are equivalent to shifting, of course).  The
above equation will give you a generic binary value.  You then look at the sign
byte's bit to determine the sign of the number and you are done!
     Confused?  Let's look at some examples.  The number 13 is 1101 in binary,
or $D in hex.  Now 1101 is going to be normalized into a four byte section,
i.e., we will place the number in such that the lead bit is a 1.  Hence our
mantissa is:
11010000 00000000 00000000 00000000
  $D0      $00      $00      $00
But remember, the binary point is assumed to be to the left of the mantissa.
So the number stored above reads .1101 which is not right.  To get the real
value out we need to shift the binary point four places to the right.  This is
the purpose of the exponent.  It has the value of 4, plus 128, or 132.
Finally, the number is positive so the sign byte is 0.  Hence, our final
product is:
exponent -------------mantissa-------------- --sign--
10000100 11010000 00000000 00000000 00000000 00000000
  $84      $D0      $00      $00      $00      $00
  132      208        0        0        0        0
     Other examples would be:
One is represented as $81 $80 $00 $00 $00 $00
Two is represented as $82 $80 $00 $00 $00 $00
Three is represented as $82 $C0 $00 $00 $00 $00
One half is represented as $80 $80 $00 $00 $00 $00
One fourth is represented as $7F $80 $00 $00 $00 $00
And -67 is represented as $87 $86 $00 $00 $00 $80
     Next time, we'll look at the calling protocol for using the subroutines.

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / miller /  4:01 pm  Feb  2, 1984 */

     This is the second installment of how to use Basic's floating point sub-
routines from assembly language on the c64.  This time: protocol.
     There are two floating point "registers" on the machine, F1 and F2.  F1 is
located at $61-$66 (97-102) and F2 at $69-$6E (105-110).  These registers are
used for all of the floating point math routines.  Values are stored in each of
the six bytes in the format described last time.
     In addition, there is a sign comparison flag located at $6F (111).  If the
high order bit is set, it means that F1 and F2 are of different signs.
     Subroutines may be executed by a JSR to the indicated  address.
Parameters are passed in and out of the routines through F1, F2, and sometimes
other memory locations.  When other locations must be specified, they are in
what we will call standard format (SF).  SF means that memory addresses are
passed through the A (LSB) and the Y (MSB) registers.
     Well, that's about all.  Next time, I'll give you the actual calling
addresses of the subroutines along with a description of what they do.  Stay
tuned...

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / miller /  1:00 pm  Feb  6, 1984 */

     This is the third in the series of how to use the Basic floating point
subroutines from machine language on the c64.  This time, we'll begin to look
at what subroutines are out there for us to use.  (Note: as before, most of
what I say applies to the vic20 too (or any future major revisions of the c64).
The only real changes are the addresses specified.)  Also, it has come to my
attention that info on the floating point routines is in the book "The VIC
Revealed".  I did not know this when I started this series.  At any rate, since
I'm sure there are others like me who do not have this book, I'll continue this
series.
     Here are the first 15 subroutines:

Integer to F1 $B391 (45969)
A two byte integer value in SF is converted to a floating point number.  The
results are stored into F1.

F1 to integer $B1AA (45482)
F1 is converted into a two byte integer which is stored into $64-65 (100-101).
F1 is trashed.

Memory to F1 $BBA2 (48034)
A 5 byte floating pt. no. anywhere in mem is loaded into F1.  The address is in
SF.  The sign flag of F1 is set if the high order bit of the mantissa is set;
otherwise it is reset.  The exponent is returned in the A register.

ASCII to F1 $B7B5 (47029)
An ASCII string is converted to floating pt format and stored in F1.  The
length of the string is loaded into A; the starting location into the "utility
string pointer" $22-23 (34-35).

F1 to ASCII $BDDD (48605)
F1 is converted into an ASCII string and stored starting at $0100 (256).  A $00
terminates the string.

Memory to F2 $BA8C (47756)
Like above except F2 is used and the sign comparison flag is set.  The exponent
of F1 is returned in A.

F1 to memory $BBD7 (48087)
F1 is stored into any 5 byte location (X holds the MSB; Y the LSB of that loca-
tion).  The high order bit of the mantissa is forced to the F1 sign flag.

F2 to F1 $BBFC (48124)
Copy F2 to F1.

F1 to F2 $BC0F (48143)
Copy F1 to F2.

Logical AND of F1 and F2 $AFE9 (45033)
The results are placed into F1.

Logical OR of F1 and F2 $AFE6 (45030)
The results are placed into F1.

F1 = F1 - F2 $B853 (47187)
Subtraction.

F1 = F1 + F2 $B86A (47210)
Addition.  You must set the sign comparison flag first.  Do this by EORing lo-
cations $66 and $6E and store the results in $6F.  Also, load A with the value
found in $61.  Both of the functions can be done by the memory to F2 routine.

F1 = F1 * F2 $BA30 (47664)
Multiplication.  Same notes apply as in addition.  However, if you call $BA28
(47656) instead, the memory to F2 will be executed first and then this one
automatically.

F1 = LOG (F1) $B9EA (47954)
Natural logarithm (base e).

     Next time I'll complete this series with the final 13 subroutines.

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / decwrl!daemon /  4:29 am  Feb  7, 1984 */
From: vogon::goodenough  (Jeff Goodenough, IPG Basingstoke)
I was very interested to read Ray Miller's articles (2 to date) on using
the C64's BASIC ROM Floating Point routines.  However, I must take issue on
a very fundamental point : although a 6-byte floating point format as he
describes is used internally, all variables and constants (which is what
the user works with) are stored in a 5-byte format. The first byte (at the
lowest address) is the exponent, followed by four bytes of mantissa. The
sign is held in bit 7 of the first mantissa byte (the 2nd byte of the 5-byte
number).

Maybe Ray is getting round to this, but I felt I should point this out to
avoid (generate?) confusion.

Another point of interest is that a zero exponent represents absolute zero
- as far as I can see, the mantissa contents are ignored in this case. This
is borne out by a funny quirk of C64 basic:  if you say X=0, this is stored
as $00 00 00 00 00, whereas if you say X=0.0, this is stored as
$00 20 00 00 00!  (or maybe the 20 should be 32 - I can't remember whether
I was printing in decimal or hex when I discovered this.)

I have also done a fair amount of research on these routines but I'll leave
Ray to carry on with his excellent series.  If I have any other points to
raise I'll do so!

Jeff
(from rainswept England).

	UUCP:		... decvax!decwrl!rhea!vogon!goodenough
			...allegra!decwrl!rhea!vogon!goodenough
			... ucbvax!decwrl!rhea!vogon!goodenough

	ARPA:		decwrl!rhea!vogon!goodenough@Berkeley
			decwrl!rhea!vogon!goodenough@SU-Shasta
/* ---------- */
/***** uiucdcs:net.micro.cbm / miller / 12:08 am  Feb  9, 1984 */
I stand corrected.
Jeff is absolutely right on his comments about internal/external representation
of the c64 floating point numbers.  My description of the "memory to F1" and
"F1 to memory" subroutines mentioned the 5 byte/high mantissa sign format (see
part 3 of the series) but this certainly was unclear.

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / miller / 12:17 am  Feb 12, 1984 */

     This is the 4th and final note in the series of how to use the c64's Basic
floating point subroutines from assembly language.  The final 13 subroutines
are:

F1 = F2 / F1 $BB12 (47890)
Division.  The notes on addition apply here too.  But if you call $BB0F
instead, the loading of F2 from memory will be done automatically before the
division.

F1 = F2 ^ F1 $BF7B (49013)
Exponentiation.  The notes on addition apply here too.  The alternate entry
point here is $BF78 which will first load F1 from memory (but does not set the
sign compare flag properly).  When using the alternate entry points, keep in
mind you must use SF as with all of the "Memory to F" routines.

F1 = F1 / 10 $BAFE (47870)
Divide by 10.

Compare F1 and memory $BC5B (48219)
F1 is compared with some floating point number in a location specified in SF.
If the numbers are equal, A is set to 0; otherwise it is set to $FF.

F1 = ABS(F1) $BC58 (48216)
Absolute value.

F1 = INT(F1) $BCCC (48332)
Returns the integer portion of the number.

F1 = SGN(F1) $BC39 (48185)
F1 is replaced by: -1 if it was less than zero, 0 if it was zero, or 1 if it
was greater than zero.

F1 = SQR(F1) $BF71 (49009)
Square root.

F1 = EXP(F1) $BFED (49133)
e to the power F1.

F1 = COS(F1) $E264 (57956)
Cosine in radians.

F1 = SIN(F1) $E26B (57963)
Sine in radians.

F1 = TAN(F1) $E2B7 (58039)
Tangent in radians.

F1 = ATN(F1) $E30D (58125)
Arctangent in radians.

     Next week: the solution to the "c64 delete back past the 80th column at
the bottom of the screen bug" (CDBPT8CATBOTSB).  I consider that to be one of
the most important notes I've written.  Don't touch that dial...

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / decwrl!daemon /  7:05 pm  Feb 10, 1984 */
From: vogon::goodenough  (Jeff Goodenough, IPG)
In reply to sytek!blk (aka B<), Ray Miller probably did just what I did -
wrote a disassembler and disassembled the ROM.  From then on it's just a
detective game - following leads like the pointer table in low ROM
(near A000) which is in order of token values, and picking out likely
looking bits of code that play with the FAC.  Then you try it out and see
if it works.  As we English say, "the proof of the pudding is in the eating".

Or maybe you *do* have inside info, Ray?  Also, any response on my earlier
comment regarding variable storage?  Another point: could you check that
floating subtract?  My reading of it is that it is an *inverse* subtract
(like divide), so that F1 := F2-F1, not F1-F2.  But I call FSUB at $B850
(instead of B853) which loads the SF variable into F2 first.  Maybe it
moves things around as well, but I don't have my ROM listing with me to
check.

Jeff Goodenough
DEC, Basingstoke, (olde) Hampshire, UK.

	UUCP:		... decvax!decwrl!rhea!vogon!goodenough
			...allegra!decwrl!rhea!vogon!goodenough
			... ucbvax!decwrl!rhea!vogon!goodenough

	ARPA:		decwrl!rhea!vogon!goodenough@Berkeley
			decwrl!rhea!vogon!goodenough@SU-Shasta
/* ---------- */
/***** uiucdcs:net.micro.cbm / miller / 12:50 am  Feb 15, 1984 */

     By golly Jeff, you're right!  It is F1 = F2 - F1 on the c64 floating point
routines (and not F1 - F2).  My mistake there.  Actually, subtraction is very
short, so I have reproduced it below for those that are interested:

; subtraction
;
    ORG  $B853
    LDA  102    ;get sign of F1
    EOR# $FF    ;complement F1 sign
    STA  102    ;replace so F1=-1*F1
    EOR  110    ;compare F1, F2 signs
    STA  111    ;set sign compare flag for addition
    LDA  97     ;setup for add too
    JMP  $B86A  ;goto addition subroutine

     I wrote a note last week on the variable storage question so I won't
reproduce that here again.
     Thanks for pointing that out, Jeff.  Sorry for the mixup...

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / genrad!al /  6:13 pm  Feb  9, 1984 */
I am not positive but I think there is a minor error in the reported addresses
for two of the routines mentioned by Ray Miller.  The F1 to memory routine is
at $BBD4 not $BBD7 and the F1 to F2 routine is at $BC0C, not $BCOF.  The rest
of the routines look correct.
/* ---------- */
/***** uiucdcs:net.micro.cbm / miller / 11:10 pm  Feb 14, 1984 */

     Sorry, but I if I've done this correctly, I have to differ with you on the
addresses for the c64 floating point routines.  You claimed that two of the
addresses I gave in part 3 of my series were incorrect, i.e., "F1 to memory"
and "F1 to F2".  I have copied one of the routines below so that you can verify
it works.  The comments are of course mine, but the rest comes straight from my
disassembler.

; F1 to F2 subroutine
;
    ORG  $BC0F  ;where I started disassembling
    LDX# 6      ;six bytes to copy
    LDA  96,X   ;F1 (97-102)
    STA  104,X  ;F2 (105-110)
    DEX
    BNE  *-5    ;done?
    STX  112
    RTS

     The other routine begins:

; F1 to memory
;
    ORG  $BBD7
    STX  34     ;save MSB memory location
    STY  35     ;save LSB
     .
     .
     .

     If you jump to the locations you suggested ($BC0C and $BBD4) rather than
the ones I gave in my four part series, then in both cases you will first
execute a JSR $BC1B which is an internal round off F1 subroutine.  After the
RTS you will wind up right back where I suggested you branch to in the first
place.

A. Ray Miller
Univ Illinois
/* ---------- */
/***** uiucdcs:net.micro.cbm / genrad!al /  9:37 pm  Feb 16, 1984 */
In regards to the accuracy of addresses $BBD4 vs $BBD7 for moving F1 to memory
and $BC0C vs $BC0F for moving F1 to F2, I still think I'm right that the lower
addresses are correct.  First of all, the extra byte at $70 is very important
and should not be ignored.  BASIC obtains extra accuracy by using one more bit
of mantissa for arithmetic operations on the main floating point accumulator
(F1) and then rounding off before sending F1 to any place else (the storage
format in memory is different and so needs conversion and the secondary FPA
(F2) doesn't have room to store the extra bit).  But in addition, $BBD0 is the
entry point for the routine that stores the FPA into the memory occupied by
the current variable (as in a LET statement) and it loads X and Y with the
correct address and then falls through to $BBD4.  In fact, I found no routine
within BASIC that calls $BBD7.  By analogy, I assume that $BC0C is actually
the correct entry point for most F1 to F2 operations though I agree with you
that the only difference between your entry points and mine is that call to
$BC1B.
/* ---------- */
/***** uiucdcs:net.micro.cbm / decwrl!lipman /  6:06 pm  Feb 24, 1984 */
Message-Id: <8402241106.AA14043@decwrl.ARPA>
Date: Friday, 24 Feb 1984 03:04:35-PST
From: vogon::goodenough  (speling courtesy of clapped out VT100)
To: net.micro.cbm
Subject: FP rounding and ASCII to screen codes

Looking at the code again, I think I have to agree with genrad!al (sorry,
you didn't post your name!), although for FSTO I have been using $BBD7 like
Ray, and not $BBD4.  It looks as though for the sake of accuracy I should
change to the lower address.  As Al (?) says, where this is called internally,
$BBD4 is used.  I haven't used F1 to F2 directly, as I regard that as
'internal' - I work with memory variables, using (A,Y) addressing.

Jeff.
/* ---------- */