6: Math Library
The Commander X16 contains a floating point Math library with a precision of 40 bits, which corresponds to 9 decimal digits. It is a stand-alone derivative of the library contained in Microsoft BASIC. Except for the different base address, it is compatible with the C128 and C65 libraries.
The full documentation of these functions can be found in the book C128 Developers Package for Commodore 6502 Development. The Math Library documentation starts in Chapter 13. (PDF page 257)
The following functions are available from machine language code after setting the ROM bank to 4, which is the default.
Format Conversions
Address | Symbol | Description |
---|---|---|
$FE00 | AYINT |
convert floating point to integer (signed word) |
$FE03 | GIVAYF |
convert integer (signed word) to floating point |
$FE06 | FOUT |
convert floating point to ASCII string |
$FE09 | VAL_1 |
convert ASCII string in .X:.Y length in .A, to floating point in FACC. Caveat! Read below! |
$FE0C | GETADR |
convert floating point to an address (unsigned word) |
$FE0F | FLOATC |
convert address (unsigned word) to floating point |
Important caveat ragarding the VAL_1
routine in its current implementation:
Unlike the other routines in the math library, this routine calls into the VAL implementation
that is inside BASIC, and so it requires much of the BASIC zeropage to be intact to function correctly.
The reason is that that routine ultimately relies on some internal BASIC routines that use a lot of BASIC zero page space.
Ideally in the future, the VAL_1
routine gets a new implementation that doesn't rely on the code in BASIC, thereby removing this restriction.
X16 Additions
The following calls are new to the X16 and were not part of the C128 math library API:
Address | Symbol | Description |
---|---|---|
$FE87 | FLOAT |
FACC = (s8).A convert signed byte to float |
$FE8A | FLOATS |
FACC = (s16)facho+1:facho |
$FE8D | QINT |
facho:facho+1:facho+2:facho+3 = u32(FACC) |
$FE93 | FOUTC |
Convert FACC to ASCIIZ string at fbuffr - 1 + .Y |
Movement
PACK
indicates a conversion from a normalized floating-point number in FACC to its packed format in memory; UNPACK
indicates a conversion from the packed format in memory to the normalized format in the destination.
Address | Symbol | Description |
---|---|---|
$FE5A | CONUPK |
ARG = UNPACK(RAM MEM) |
$FE5D | ROMUPK |
ARG = UNPACK(ROM MEM) (use CONUPK ) |
$FE60 | MOVFRM |
FACC = UNPACK(RAM MEM) (use MOVFM ) |
$FE63 | MOVFM |
FACC = UNPACK(ROM MEM) |
$FE66 | MOVMF |
MEM = PACK(ROUND(FACC)) |
$FE69 | MOVFA |
FACC = ARG |
$FE6C | MOVAF |
FACC = ROUND(FACC); ARG = FACC |
X16 Additions
The following calls are new to the X16 and were not part of the C128 math library API:
Address | Symbol | Description |
---|---|---|
$FE81 | MOVEF |
ARG = FACC |
Math Functions
Address | Symbol | Description |
---|---|---|
$FE12 | FSUB |
FACC = MEM - FACC |
$FE15 | FSUBT |
FACC = ARG - FACC |
$FE18 | FADD |
FACC = MEM + FACC |
$FE1B | FADDT |
FACC = ARG + FACC |
$FE1E | FMULT |
FACC = MEM * FACC |
$FE21 | FMULTT |
FACC = ARG * FACC |
$FE24 | FDIV |
FACC = MEM / FACC |
$FE27 | FDIVT |
FACC = ARG / FACC |
$FE2A | LOG |
FACC = natural log of FACC |
$FE2D | INT |
FACC = INT() truncate of FACC |
$FE30 | SQR |
FACC = square root of FACC |
$FE33 | NEGOP |
negate FACC (switch sign) |
$FE36 | FPWR |
FACC = raise ARG to the MEM power |
$FE39 | FPWRT |
FACC = raise ARG to the FACC power |
$FE3C | EXP |
FACC = EXP of FACC |
$FE3F | COS |
FACC = COS of FACC |
$FE42 | SIN |
FACC = SIN of FACC |
$FE45 | TAN |
FACC = TAN of FACC |
$FE48 | ATN |
FACC = ATN of FACC |
$FE4B | ROUND |
FACC = round FACC |
$FE4E | ABS |
FACC = absolute value of FACC |
$FE51 | SIGN |
.A = test sign of FACC |
$FE54 | FCOMP |
.A = compare FACC with MEM |
$FE57 | RND_0 |
FACC = random floating point number |
X16 Additions to math functions
The following calls are new to the X16 and were not part of the C128 math library API:
Address | Symbol | Description |
---|---|---|
$FE6F | FADDH |
FACC += .5 |
$FE72 | ZEROFC |
FACC = 0 |
$FE75 | NORMAL |
Normalize FACC |
$FE78 | NEGFAC |
FACC = -FACC (just use NEGOP) |
$FE7B | MUL10 |
FACC *= 10 |
$FE7E | DIV10 |
FACC /= 10 |
$FE84 | SGN |
FACC = sgn(FACC) |
$FE90 | FINLOG |
FACC += (s8).A add signed byte to float |
$FE96 | POLYX |
Polynomial Evaluation 1 (SIN/COS/ATN/LOG) |
$FE99 | POLY |
Polynomial Evaluation 2 (EXP) |
How to use the routines
Concepts:
- FACC (sometimes abbreviated to FAC): the floating point accumulator. You can compare this to the 6502 CPU's .A register, which is the accumulator for most integer operations performed by the CPU. FACC is the primary floating point register. Calculations are done on the value in this register, usually combined with ARG. After the operation, usually the original value in FACC has been replaced by the result of the calculation.
- ARG: the second floating point register, used in most calculation functions. Often the value in this register will be lost after a calculation.
- MEM: means a floating point value stored in system memory somewhere. The format is 40 bits (5 bytes) Microsoft binary format. To be able to work with given values in calculations, they need to be stored in memory somewhere in this format. To do this you'll likely need to use a separate program to pre-convert floating point numbers to this format, unless you are using a compiler that directly supports it.
Note that FACC and ARG are just a bunch of zero page locations. This means you can poke around in them. But that's not good practice because their locations aren't guaranteed/public, and the format is slightly different than how the 5-byte floats are normally stored into memory. Just use one of the Movement routines to copy values into or out of FACC and ARG.
To perform a floating point calculation, follow the following pattern:
- load a value into FACC. You can convert an integer, or move a MEM float number into FACC.
- do the same but for ARG, the second floating point register.
- call the required floating point calculation routine that will perform a calculation on FACC with ARG.
- repeat the previous 2 steps if required.
- the result is in FACC, move it into MEM somewhere or convert it to another type or string.
An example program that calculates and prints the distance an object has fallen over a certain period using the formula $d = \dfrac{1}{2} g {t}^{2}$
; calculate how far an object has fallen: d = 1/2 * g * t^2.
; we set g = 9.81 m/sec^2, time = 5 sec -> d = 122.625 m.
CHROUT = $ffd2
FOUT = $fe06
FMULTT = $fe21
FDIV = $fe24
CONUPK = $fe5a
MOVFM = $fe63
lda #4
sta $01 ; rom bank 4 (BASIC) contains the fp routines.
lda #<flt_two
ldy #>flt_two
jsr MOVFM
lda #<flt_g
ldy #>flt_g
jsr FDIV ; FACC= g/2
lda #<flt_time
ldy #>flt_time
jsr CONUPK ; ARG = time
jsr FMULTT ; FACC = g/2 * time
lda #<flt_time
ldy #>flt_time
jsr CONUPK ; again ARG = time
jsr FMULTT ; FACC = g/2 * time * time
jsr FOUT ; to string
; print string in AY
sta $02
sty $03
ldy #0
loop:
lda ($02),y
beq done
jsr CHROUT
iny
bne loop
done:
rts
flt_g: .byte $84, $1c, $f5, $c2, $8f ; float 9.81
flt_time: .byte $83, $20, $00, $00, $00 ; float 5.0
flt_two: .byte $82, $00, $00, $00, $00 ; float 2.0
Notes
RND_0
: For .Z=1, the X16 behaves differently than the C128 and C65 versions. The X16 version takes entropy from .A/.X/.Y instead of from a CIA timer. So in order to get a "real" random number, you would use code like this:
LDA #$00
PHP
JSR entropy_get ; KERNAL call to get entropy into .A/.X/.Y
PLP ; restore .Z=1
JSR RND_0
- The calls
FADDT
,FMULTT
,FDIVT
andFPWRT
were broken on the C128/C65. They are fixed on the X16. - For more information on the additional calls, refer to Mapping the Commodore 64 by Sheldon Leemon, ISBN 0-942386-23-X, but note these errata:
FMULT
adds mem to FACC, not ARG to FACC