Skip to content

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:

  1. load a value into FACC. You can convert an integer, or move a MEM float number into FACC.
  2. do the same but for ARG, the second floating point register.
  3. call the required floating point calculation routine that will perform a calculation on FACC with ARG.
  4. repeat the previous 2 steps if required.
  5. 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 and FPWRT 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