Powrót do spisu treści

Rozdział 4

PROCEDURY ZMIENNOPRZECINKOWE

    W systemie operacyjnym komputerów Atari znajduje się wydzielony blok 2 KB pamięci ROM, w którym znajdują się procedury operacji matematycznych na liczbach rzeczywistych, zwanych także liczbami zmiennoprzecinkowymi (floating point). Są one niezbędne do wykonywania obliczeń na liczbach spoza zakresu obejmowanego przez dwubajtowe liczby całkowite, które stanowią podstawową reprezentację liczb w komputerach.

    Trzeba od razu zaznaczyć, że jest to najgorzej opracowana część systemu. Programy i języki wyższego poziomu korzystające z tych procedur są bardzo wolne, czego najlepszym przykładem jest wbudowany Atari Basic. Wiele z tych procedur można jednak wykorzystać w całości lub w części we własnych programach.

4.1. Format liczb zmiennoprzecinkowych

    Liczby rzeczywiste są zapisywane w Atari w specjalny sposób, pozwalający na umieszczenie w stosunkowo niewielkiej przestrzeni dużego zakresu wartości liczbowych. Arytmetyka zmiennorzecinkowa Atari może dzięki temu operować na liczbach z zakresu od 10-98 do 10+98. Dla przykładu zakres liczb dostępnych w Commodore 64 jest znacznie mniejszy: 10-38-10+38.

    Liczby zmiennoprzecinkowe (Floating Point - FP) zajmują zawsze sześć bajtów. W pierwszym bajcie zapisany jest znak liczby oraz jej wykładnik (eksponent). Znak liczby jest określony przez najstarszy, siódmy bit. Gdy jest on ustawiony, to liczba jest ujemna. Pozostałe siedem bitów jest właściwym wykładnikiem potęgi liczby 100 zwiększonym o $40 (64).

    Reszta, czyli pięć bajtów, zawiera mantysę liczby zapisaną w kodzie BCD. Kod ten pozwala na umieszczenie w jednym bajcie dwóch cyfr dziesiętnych, więc cała mantysa ma dziesięć cyfr znaczących. Mantysa jest zapisywana zawsze w ten sposób, aby jej pierwszy bajt był różny od zera. System zakłada, że za pierwszym bajtem znajduje się punkt dziesiętny (w zapisie anglosaskim stosowanym w komputerach jest to kropka, w polskim - przecinek).

    Zero jest w tej konwencji reprezentowane przez zerowy wykładnik i zerową mantysę (sześć bajtów równych zero). Przykłady konwersji liczb na zapis stosowany w Atari można znaleźć m. in. w książkach "Asembler 6502", "De Re Atari" i "PEEK-POKE 2".

4.2. Procedury operacji na liczbach FP

    Pakiet procedur FP znajduje się w pamięci ROM w obszarze $D800-$DFFF. Należy przy tym zwrócić uwagę, że ten sam obszar jest wykorzystywany przez nowe urządzenia. Procedury i programy obsługi nowych urządzeń nie mogą więc korzystać z procedur FP.

    Dla potrzeb operacji FP wykorzystywane są także obszary pamięci RAM od $D4 do $FF oraz od $057E do $05FF. W pierwszym z wymienionych obszarów znajdują się podstawowe rejestry liczb FP oraz rejestry pomocnicze operacji FP. Drugi obszar zajmuje bufor danych dla operacji FP.

    Liczby zmiennoprzecinkowe przechowywane są w czterech specjalnych sześciobajtowych rejestrach: FR0 (Floating point Register 0 - $D4-$D9), FR1 ($E0-$E5), FR2 ($E6-$EB) oraz FRE (FP Register Extra - $DA-$DF). Do wyprowadzania liczb FP w innych formatach służy bufor LBUFF (Line BUFfer), zaś bufor wejściowy jest wskazywany wektorem INBUFP (INput BUFfer Pointer). Liczby w formacie FP umieszczone poza rejestrami FR są wskazywane wektorami FLPTR (FLoating PoinTeR) i FPTR2 (Floating PoinTeR 2). Poza tym podczas obliczeń wykorzystywane są rejestry EEXP (E EXPonent), NSIGN (Number SIGN - znak liczby) i ESIGN (Exponent SIGN - znak wykładnika) oraz rejestry przejściowe ZTEMP1-3 (Zeropage TEMPorary register).

    Spośród procedur składających się na pakiet arytmetyki zmiennoprzecinkowej można wyodrębnić trzy najważniejsze grupy: procedury przekształceń z i na format FP, procedury obliczeń na liczbach FP oraz procedury przemieszczeń liczb FP.

4.2.1. Procedury przekształceń liczb FP

    Ponieważ komputer normalnie nie operuje na liczbach zmiennoprzecinkowych, muszą one być wprowadzane w innej postaci i zamieniane na format FP. Odwrotną operację należy wykonać w celu wyprowadzenia otrzymanych wyników. W komputerze liczby mogą być przedstawiane w postaci dwubajtowych liczb całkowitych albo w postaci ciągu znaków kodu ASCII. Pakiet procedur zmiennoprzecinkowych zawiera dwie pary procedur do przekształceń liczb z tych formatów na sześciobajtowy format FP i odwrotnie.

    Pierwsza para procedur umożliwia konwersję z i na dwubajtowe liczby całkowite. Zarówno liczba wejściowa, jak i wyjściowa umieszczane są w rejestrze FR0, przy czym liczba całkowita zajmuje w nim tylko dwa pierwsze bajty.

    Procedura IFP wykonuje przekształcenie liczby całkowitej umieszczonej w dwóch pierwszych bajtach FR0 na liczbę FP, którą również umieszcza w FR0. W tym celu najpierw przenosi liczbę całkowitą do rejestru ZTEMP2 w odwróconej kolejności bajtów (starszy, młodszy) i zeruje rejestr FR0 przez wywołanie procedury ZFR0.

    Następnie w trybie dziesiętnym procesora wykonuje szesnaście razy mnożenie liczby całkowitej przez 2 i dodawanie z użyciem bitu przeniesienia (Carry) trzech pierwszych bajtów mantysy liczby zmiennoprzecinkowej. W ten sposób dokonana zostaje konwersja cyfr. Ponieważ maksymalną wartością liczby całkowitej jest 65535, to zakłada się wykładnik potęgi sto równy 2 (po zwiększeniu $42).
            0100 ;Integer to FP conversion
            0110 ;
            0120 FR0 =   $D4
            0130 NFR0 =  $DC00
            0140 ZFR0 =  $DA44
            0150 ZTEMP2 = $F7
            0160 ;
            0170     *=  $D9AA
            0180 ;
            0190     LDA FR0
            0200     STA ZTEMP2+1
            0210     LDA FR0+1
            0220     STA ZTEMP2
            0230     JSR ZFR0
            0240     SED
            0250     LDY #$10
            0260 NX1 ASL ZTEMP2+1
            0270     ROL ZTEMP2
            0280     LDX #$03
            0290 NX2 LDA FR0,X
            0300     ADC FR0,X
            0310     STA FR0,X
            0320     DEX
            0330     BNE NX2
            0340     DEY
            0350     BNE NX1
            0360     CLD
            0370     LDA #$42
            0380     STA FR0
            0390     JMP NFR0
    Otrzymujemy więc liczbę postaci NN.NNNN*1002, gdzie N jest czterobitową cyfrą w kodzie BCD. Jeżeli liczba całkowita jest mniejsza niż 10000, to dwie pierwsze cyfry liczby FP są równe 0. Sytuacja taka jest nieprawidłowa, więc na końcu wywoływana jest procedura NFR0, która doprowadza liczbę zawartą w rejestrze FR0 do wymaganej postaci.
            0100 ;Floating Point to Integer
            0110 ;
            0120 FR0 =   $D2
            0130 IDEX = $DCB9
            0140 ROLZ2 = $DA5A
            0150 ZTEMP1 = $F5
            0160 ZTEMP2 = $F7
            0170 ZTEMP3 = $F9
            0180 ;
            0190     *=  $D9D2
            0200 ;
            0210     LDA #$00
            0220     STA ZTEMP2
            0230     STA ZTEMP2+1
            0240     LDA FR0
            0250     BMI EXIT
            0260     CMP #$43
            0270     BCS EXIT
            0280     SEC
            0290     SBC #$40
            0300     BCC MIN
            0310     ADC #$00
            0320     ASL A
            0330     STA ZTEMP1
            0340 NXT JSR ROLZ2
            0350     BCS EXIT
            0360     LDA ZTEMP2
            0370     STA ZTEMP3
            0380     LDA ZTEMP2+1
            0390     STA ZTEMP3+1
            0400     JSR ROLZ2
            0410     BCS EXIT
            0420     JSR ROLZ2
            0430     BCS EXIT
            0440     CLC
            0450     LDA ZTEMP2+1
            0460     ADC ZTEMP3+1
            0470     STA ZTEMP2+1
            0480     LDA ZTEMP2
            0490     ADC ZTEMP3
            0500     STA ZTEMP2
            0510     BCS EXIT
            0520     JSR IDEX
            0530     CLC
            0540     ADC ZTEMP2+1
            0550     STA ZTEMP2+1
            0560     LDA ZTEMP2
            0570     ADC #$00
            0580     BCS EXIT
            0590     STA ZTEMP2
            0600     DEC ZTEMP1
            0610     BNE NXT
            0620 MIN JSR IDEX
            0630     CMP #$05
            0640     BCC BPS
            0650     CLC
            0660     LDA ZTEMP2+1
            0670     ADC #$01
            0680     STA ZTEMP2+1
            0690     LDA ZTEMP2
            0700     ADC #$00
            0710     STA ZTEMP2
            0720 BPS LDA ZTEMP2+1
            0730     STA FR0
            0740     LDA ZTEMP2
            0750     STA FR0+1
            0760     CLC
            0770     RTS
            0780 EXIT SEC
            0790     RTS
    Dokonująca odwrotnej zamiany procedura FPI rozpoczyna się od wyzerowania rejestru ZTEMP2 i sprawdzenia znaku liczby FP. Jeżeli liczba jest ujemna i nie może być przedstawiona jako całkowita, to ustawiany jest bit Carry i procedura się kończy. To samo następuje w przypadku wykładnika większego od 2, co oznacza liczbę większą od 65535 (a dokładniej: większą od 1000000).

    Następnie jest obliczany wykładnik przez odjęcie $40 od pierwszego bajtu liczby FP. Wynik ujemny (wykładnik mniejszy od zera, czyli liczba mniejsza od 100) powoduje przeskoczenie do końcowej fazy procedury.

    Teraz przy pomocy procedur IDEX i ROLZ2 są kolejno obliczane bajty liczby całkowitej. W przypadku wystąpienia błędu w dowolnej fazie tego przeliczania następuje opuszczenie procedury FPI z ustawionym bitem Carry. Jeżeli przebieg konwersji był poprawny, to liczba całkowita jest przenoszona z rejestru ZTEMP2 do dwóch pierwszych bajtów FR0 i po skasowaniu bitu Carry procedura się kończy.
            0100 ;ROtate Left ZTEMP2
            0110 ;
            0120 ZTEMP2 = $F7
            0130 ;
            0140     *=  $DA5A
            0150 ;
            0160     CLC
            0170     ROL ZTEMP2+1
            0180     ROL ZTEMP2
            0190     RTS
    Procedura ROLZ2 wykonuje jedynie przesunięcie w lewo obu bajtów liczby całkowitej przechowywanej w rejestrze ZTEMP2.
            0100 ;Integer Digit EXtract
            0110 ;
            0120 FRX =   $EC
            0130 ROLFR0 = $DBEB
            0140 ;
            0150     *=  $DCB9
            0160 ;
            0170     JSR ROLFR0
            0180     LDA FRX
            0190     AND #$0F
            0200     RTS
    Pomocnicza procedura IDEX wywołuje najpierw procedurę ROLFR0, a następnie wydziela cyfrę (bity 0-3) z uzyskanego bajtu umieszczonego w dodatkowym rejestrze FRX.
            0100 ;ROtate Left FP Registers
            0110 ;
            0120 FR0 =   $D4
            0130 FR2 =   $E6
            0140 FRX =   $EC
            0150 ;
            0160     *=  DBE7
            0170 ;
            0180 ROLFR2 LDX #FR2+1
            0190     BNE CONT
            0200 ROLFR0 LDA #FR0+1
            0210 CONT LDY #$04
            0220 NXT CLC
            0230     ROL $04,X
            0240     ROL $03,X
            0250     ROL $02,X
            0260     ROL $01,X
            0270     ROL $00,X
            0280     ROL FRX
            0290     DEY
            0300     BNE NXT
            0310     RTS
    Procedury ROLFR0 i ROLFR2 dokonują czterokrotnego przesunięcia w lewo mantysy liczby FP zawartej odpowiednio w rejestrze FR0 lub FR2. Przesunięciu podlega także zawartość rejestru FRX, dzięki czemu po zakończeniu procedury aktualnie najbardziej znacząca cyfra mantysy znajduje się w tym rejestrze (w bitach 0-3).

    Do zamiany ciągu znaków ASCII na sześciobajtową liczbę FP służy procedura AFP. Przed jej rozpoczęciem adres ciągu znaków do zamiany musi być umieszczony w rejestrze INBUFP (INput BUFfer Pointer), a po dokonaniu konwersji otrzymana liczba zmiennoprzecinkowa znajduje się w rejestrze FR0.
0100 ;ASCII ro FP conversion   0390     JSR ZFR0
0110 ;                         0400     BEQ BPS
0120 AF1 =   $DA48             0410 NXT LDA #$FF
0130 ASCSS =  $DBBB            0420     STA FCHRFLG
0140 CIX =   $F2               0430 BPS JSR INBCN
0150 DIGRT = $F1               0440     BCS NDT
0160 EEXP =  $ED               0450     PHA
0170 ESIGN = $EF               0460     LDX FR0+1
0180 FCHRFLG = $F0             0470     BNE OVF
0190 FR0   = $D4               0480     JSR ROLFR0
0200 FRX   = $EC               0490     PLA
0210 INBCN = $DB94             0500     ORA FR0+5
0220 INBSS = $DBA1             0510     STA FR0+5
0230 INCIX = $DB9D             0520     LDX DIGRT
0240 NFR0 =  $DC00             0530     BMI NXT
0250 NSIGN = $EE               0540     INX
0260 ROLFR0 = $DBEB            0550     STX DIGRT
0270 ZFR0 =  $DA44             0560     BNE NXT
0280 ;                         0570 OVF PLA
0290     *=  $D800             0580     LDX DIGRT
0300 ;                         0590     BPL NRM
0310     JSR INBSS             0600     INC EEXP
0320     JSR ASCSS             0610 NRM JMP NXT
0330     BCS EX1               0620 EX1  RTS
0340     LDX #EEXP             0630 NDT CMP #'.
0350     LDY #$04              0640     BEQ DPT
0360     JSR AF1               0650     CMP #'E
0370     LDX #$FF              0660     BEQ ESG
0380     STX DIGRT             0670     LDX FCHRFLG

0680     BNE PRV               1100     STA EEXP
0690     CMP #'+               1110 ES0 PLA
0700     BEQ NXT               1120     CLC
0710     CMP #'-               1130     ADC EEXP
0720     BEQ NEG               1140     STA EEXP
0730 NEG STA NSIGN             1150     BNE PRV
0740     BEQ NXT               1160 NDG CMP #'+
0750 DPT LDX DIGRT             1170     BEQ PLS
0760     BPL PRV               1180     CMP #'-
0770     INX                   1190     BNE CNT
0780     STX DIGRT             1200     STA ESIGN
0790     BEQ NXT               1210 PLS JSR INBCN
0800 ESG LDA CIX               1220     BCC NX
0810     STA FRX               1230 CNT LDA FRX
0820     JSR INBCN             1240     STA CIX
0830     BCS NDG               1250 PRV DEC CIX
0840 NX  TAX                   1260     LDA EEXP
0850     LDA EEXP              1270     LDX DIGRT
0860     PHA                   1280     BMI NSC
0870     STX EEXP              1290     BEQ NSC
0880     JSR INBCN             1300     SEC
0890     BCS EVE               1310     SBC DIGRT
0900     PHA                   1320 NSC PHA
0910     LDA EEXP              1330     ROL A
0920     ASL A                 1340     PLA
0930     STA EEXP              1350     ROR A
0940     ASL A                 1360     STA EEXP
0950     ASL A                 1370     BCC POS
0960     ADC EEXP              1380     JSR ROLFR0
0970     STA EEXP              1390 POS LDA EEXP
0980     PLA                   1400     CLC
0990     CLC                   1410     ADC #$44
1000     ADC EEXP              1420     STA FR0
1010     STA EEXP              1430     JSR NFR0
1020     LDY CIX               1440     BCS EX2
1030     JSR INCIX             1450     LDX NSIGN
1040 EVE LDA ESIGN             1460     BEQ EXC
1050     BEQ ES0               1470     LDA FR0
1060     LDA EEXP              1480     ORA #$80
1070     EOR #$FF              1490     STA FR0
1080     CLC                   1500 EXC CLC
1090     ADC #$01              1510 EX2 RTS
    Na początku procedury AFP wyszukiwany jest (przez procedurę INBSS) pierwszy znak ciągu ASCII do konwersji. Następnie procedura ASCSS sprawdza poprawność tego znaku. Jeżeli znak jest nieprawidłowy, to procedura jest opuszczana z ustawionym bitem Carry.

    Gdy ciąg może być poddany przekształceniu, to zerowane są rejestry EEXP, NSIGN, ESIGN, FCHRFLG (First CHaRacter FLaG) oraz FR0, a rejestr DIGRT (DIGits to Right of decimal) otrzymuje wartość $FF.

    Teraz cyfry z ciągu ASCII są kolejno odczytywane i po zamianie na postać BCD, umieszczane w rejestrze FR0. Jeżeli odczytany znak nie jest cyfrą, to sprawdzane są inne dozwolone możliwości: punkt dziesiętny (.), znak wykładnika (E), znak plus (+) i znak minus (-).

    Odczytanie punktu dziesiętnego powoduje ustalenie wartości wykładnika liczby FP. Po rozpoznaniu "E" kolejno odczytywane są cyfry wykładnika. W ten sposób odczytywanie kolejnych znaków ciągu ASCII jest kontynuowane, aż do rozpoznania niedozwolonego znaku, co kończy procedurę AFP. Jeżeli w chwili zakończenia procedury liczba FP nie ma żadnej wartości (nie został prawidłowo odczytany żaden znak ciągu ASCII), to wskazywany jest błąd przez ustawienie bitu Carry.

    Podczas przebiegu procedury AFP wywoływane jest kilka procedur pomocniczych, które mogą być zastosowane także w programach użytkownika. Niektóre z nich są także wywoływane podczas obliczeń na liczbach FP.
            0100 ;INput Buffer Search for Space
            0110 ;
            0120 CIX =   $F2
            0130 INBUFP = $F3
            0140 ;
            0150     *=  $DBA1
            0160 ;
            0170     LDY CIX
            0180     LDA #$20
            0190 NXT CMP (INBUFP),Y
            0200     BNE EXIT
            0210     INY
            0220     BNE NXT
            0230 EXIT STY CIX
            0240     RTS
    Zadaniem procedury INBSS jest wstępne rozpoznanie kolejnego znaku ciągu ASCII. Jeżeli jest to znak spacji, odczytywany jest następny znak. Gdy znak jest różny od spacji, to jego położenie w buforze jest zapisywane w rejestrze CIX (Current IndeX) i procedura się kończy.
            0100 ;ASCII String Search
            0110 ;
            0120 CIX =   $F2
            0130 INBCN = $DB94
            0140 ;
            0150     *=  $DBBB
            0160 ;
            0170     LDA CIX
            0180     PHA
            0190     JSR INBCN
            0200     BCC EXC
            0210     CMP #'.
            0220     BEQ DPT
            0230     CMP #'+
            0240     BEQ SGN
            0250     CMP #'-
            0260     BEQ SGN
            0270 EXS PLA
            0280     SEC
            0290     RTS
            0300 SGN JSR INBCN
            0310     BCC EXC
            0320     CMP #'.
            0330     BNE EXC
            0340 DPT JSR INBCN
            0350     BCC EXC
            0360     BCS EXS
            0370 EXC PLA
            0380     STA CIX
            0390     CLC
            0400     RTS
    Procedura ASCSS sprawdza poprawność odczytanego znaku. Gdy znak jest nieprawidłowy, to przed opuszczeniem procedury ustawiany jest bit Carry. Poprawnymi znakami są cyfry oraz punkt dziesiętny lub znaki "+" i "-", jeśli następuje po nich cyfra. W celu odczytania znaku ASCII i jego zamiany na bajt BCD wywoływana jest procedura INBCN.
            0100 ;INput Buffer CoNvert
            0110 ;
            0120 ADBT =  $DBAF
            0130 CIX =   $F2
            0140 INBUFP = $F3
            0150 ;
            0160     *=  $DB94
            0170 ;
            0180     JSR ADBT
            0190     LDY CIX
            0200     BCC INCIX
            0210     LDA (INBUFP),Y
            0220 INCIX INY
            0230     STY CIX
            0240     RTS
    Procedura INBCN najpierw wywołuje procedurę ADBT, która odczytuje cyfre z ciągu znaków ASCII, a następnie zwiększa o jeden zawartość rejestru CIX.
            0100 ;ASCII Digit to ByTe
            0110 ;
            0120 CIX =   $F2
            0130 INBUFP = $F3
            0140 ;
            0150     *=  $DBAF
            0160 ;
            0170     LDY CIX
            0180     LDA (INBUFP),Y
            0190     SEC
            0200     SBC #$30
            0210     BCC EXIT
            0220     CMP #$0A
            0230     RTS
            0240 ;
            0250     *=  $DBD0
            0260 ;
            0270 EXIT SEC
            0280     RTS
    Właściwej zamiany cyfry w kodzie ASCII na cztery bity kodu BCD dokonuje procedura ADBT. Odczytuje ona najpierw z bufora wejściowego wskazywanego wektorem INBUFP kolejny znak wyznaczony indeksem CIX i odejmuje od niego $30 (kod ASCII cyfry 0). Jeżeli uzyskany wynik jest mniejszy od zera lub większy od 9 (a więc nie jest to cyfra), to przed opuszczeniem procedury ustawiany jest bit Carry.

    Należy zwrócić uwagę, że sekwencja rozkazów SEC, RTS (oznaczona etykietą EXIT) znajduje się w procedurze ASCSS. Zaoszczędzono w ten sposób dwa bajty w pamięci.

    Ostatnią z procedur przekształcających liczby FP jest procedura FASC, której zadaniem jest konwersja liczby FP na ciąg znaków ASCII. Liczba do konwersji musi być umieszczona w rejestrze FR0, a wynikowy ciąg zapisywany jest w buforze LBUFF.
0100 ;FP to ASCII conversion   0470 DIB JSR DECIBP
0110 ;                         0480     JMP SGN
0120 BTAD =  $DC9D             0490 ZERO LDA #'0+$80
0130 CIX =   $F2               0500     STA LBUFF
0140 DECIBP = $DCC1            0510     RTS
0150 EEXP =  $ED               0520 CPL LDA #$01
0160 FR0 =   $D4               0530     JSR STALB
0170 INBUFP = $F3              0540     JSR LBSR
0180 LBPR2 = $057F             0550     INX
0190 LBSR =  $DCA4             0560     STX CIX
0200 LBUFF = $0580             0570     LDA FR0
0210 STALB = $DC70             0580     ASL A
0220 STBV =  $DA51             0590     SEC
0230 STLB =  $DC9F             0600     SBC #$80
0240 ;                         0610     LDX LBUFF
0250     *=  $D8E6             0620     CPX #$30
0260 ;                         0630     BEQ EXP
0270     JSR STBV              0640     LDX LBUFF+1
0280     LDA #$30              0650     LDY LBUFF+2
0290     STA LBPR2             0660     STX LBUFF+2
0300     LDA FR0               0670     STY LBUFF+1
0310     BEQ ZERO              0680     LDX CIX
0320     AND #$7F              0690     CPX #$02
0330     CMP #$3F              0700     BNE NIN
0340     BCC CPL               0710     INC CIX
0350     CMP #$45              0720 NIN CLC
0360     BCS CPL               0730     ADC #$01
0370     SEC                   0740 EXP STA EEXP
0380     SBC #$3F              0750     LDA #'E
0390     JSR STALB             0760     LDY CIX
0400     JSR LBSR              0770     JSR STLB
0410     ORA #$80              0780     STY CIX
0420     STA LBUFF,X           0790     LDA EEXP
0430     LDA LBUFF             0800     BPL PLS
0440     CMP #'.               0810     LDA #$00
0450     BEQ DIB               0820     SEC
0460     JMP ADB               0830     SBC EEXP

0840     STA EEXP              1030     JSR BTAD
0850     LDA #'-               1040 ADB LDA LBUFF
0860     BNE STR               1050     CMP #$30
0870 PLS LDA #'+               1060     BNE SGN
0880 STR JSR STLB              1070     CLC
0890     LDX #$00              1080     LDA INBUFP
0900     LDA EEXP              1090     ADC #$01
0910 NXT SEC                   1100     STA INBUFP
0920     SBC #$0A              1110     LDA INBUFP+1
0930     BCC FIN               1120     ADC #$00
0940     INX                   1130     STA INBUFP+1
0950     BNE NXT               1140 SGN LDA FR0
0960 FIN CLC                   1150     BPL EXIT
0970     ADC #$0A              1160     JSR DECIBP
0980     PHA                   1170     LDY #$00
0990     TXA                   1180     LDA #'-
1000     JSR BTAD              1190     STA (INBUFP),Y
1010     PLA                   1200 EXIT RTS
1020     ORA #$80
    Na początku poprzez wywołanie procedury STBV ustalany jest adres bufora, w którym zostanie zapisany wynikowy ciąg znaków ASCII i do poprzedzającego bufor rejestru LBPR2 (Line Buffer PRefix 2) wpisywane jest zero.

    Następnie odczytywany jest bajt wykładnika liczby FP. Gdy jest on równy 0, to do bufora wpisywany jest znak zera zwiększony o $80 (w inverse video) i procedura się kończy. W przeciwnym razie jest sprawdzane, czy wykładnik mieści się w zakresie od 0 do 4.

    Jeżeli tak, to liczba FP jest bezpośrednio zamieniana na ciąg ASCII. Potem ustalane jest jeszcze tylko polożenie punktu dziesiętnego i znak liczby.

    Gdy wykładnik jest mniejszy od zera (liczba mniejsza od 0) lub większy od 4 (liczba ma więcej niż osiem cyfr przed punktem dziesiętnym), to ciąg wynikowy będzie zawierał liczbę w notacji naukowej (tzn. mantysa i wykładnik potęgi 10). W takim przypadku najpierw odtwarzane są cyfry znaczące, a następnie wartość wykładnika potęgi 100 jest przeliczana na wartość wykładnika potęgi 10.

    Na końcu (niezależnie od wielkości przekształcanej liczby) otrzymany ciąg jest przeszukiwany w celu odnalezienia pierwszej cyfry różnej od zera i na tą cyfrę ustawiany jest wektor bufora. Teraz sprawdzany jest znak liczby i gdy liczba jest ujemna, to wektor bufora jest zmniejszany o jeden i przed pierwszą cyfrą wpisywany jest znak "-".

    Poniżej przedstawione są pomocnicze procedury wykorzystywane przez FASC i inne procedury FP.
            0100 ;STore Buffer Vector
            0110 ;
            0120 INBUFP = $F3
            0130 LBUFF = $0580
            0140 ;
            0150     *=  $DA51
            0160 ;
            0170     LDA # >LBUFF
            0180     STA INBUFP+1
            0190     LDA # <LBUFF
            0200     STA INBUFP
            0210     RTS
    Jedynym zadaniem procedury STBV jest wpisanie do wektora INBUFP (INput BUFfer Pointer) adresu bufora wyjściowego LBUFF (Line BUFFer). Zapewnia to umieszczenie wynikowego ciągu ASCII w buforze LBUFF.
            0100 ;DECrement Input Buffer Pointer
            0110 ;
            0120 INBUFP = $F3
            0130 ;
            0140     *=  $DCC1
            0150 ;
            0160     SEC
            0170     LDA INBUFP
            0180     SBC #$01
            0190     STA INBUFP
            0200     LDA INBUFP+1
            0210     SBC #$00
            0220     STA INBUFP+1
            0230     RTS
    Procedura DECIBP zmniejsza o jeden wektor INBUFP. Wektor ten jest aktualizowany po wpisaniu do bufora każdego znaku i wskazuje zawsze miejsce wpisania następnego znaku. Po wywołaniu DECIBP wektor wskazuje więc na ostatni znak ciągu wynikowego.
            0100 ;STore ASCII to LBUFF
            0110 ;
            0120 BTAD =  $DC9D
            0130 FR0 =   $D4
            0140 STLB =  $DC9F
            0150 ZTEMP2 = $F7
            0160 ;
            0170     *=  $DC70
            0180 ;
            0190     STA ZTEMP2
            0200     LDX #$00
            0210     LDY #$00
            0220 NXT JSR DP
            0230     SEC
            0240     SBC #$01
            0250     STA ZTEMP2
            0260     LDA FR0+1,X
            0270     LSR A
            0280     LSR A
            0290     LSR A
            0300     LSR A
            0310     JSR BTAD
            0320     LDA FR0+1,X
            0330     AND #$0F
            0340     JSR BTAD
            0350     INX
            0360     CPX #$05
            0370     BCC NXT
            0380 DP  LDA ZTEMP2
            0390     BNE EXIT
            0400     LDA #'.
            0410     JSR STLB
            0420 EXIT RTS
    Przed wywołaniem procedury STALB w akumulatorze zostaje umieszczona liczba określająca ilość cyfr przed punktem dziesiętnym podzielona przez dwa. Po wywołaniu liczba ta zostaje przepisana do rejestru ZTEMP2. Teraz kolejno odczytywane są bajty mantysy z rejestru FR0. Z każdego z nich wydzielane są najpierw cztery starsze, a potem cztery młodsze bity. Zawarte w nich cyfry kodu BCD są zamieniane przez procedurę BTAD na znaki ASCII i umieszczane w buforze wyjściowym.

    Przed odczytaniem każdego bajtu liczby FP zawartość rejestru ZTEMP2 jest zmniejszana o 1 i gdy osiągnie wartość 0, to do bufora wpisywany jest punkt dziesiętny (kropka - ".").
            0100 ;ByTe to ASCII Digit
            0110 ;
            0120 LBUFF = $0580
            0130 ;
            0140     *=  $DC9D
            0150 ;
            0160     ORA #$30
            0170 STLB STA LBUFF,Y
            0180     INY
            0190     RTS
    Wspomniana już wcześniej procedura BTAD dodaje $30 do wartości znajdującej się w akumulatorze cyfry, przez co uzyskujemy kod ASCII tej cyfry. Następnie wpisuje zawartość akumulatora do buforu LBUFF w miejsce określone przez zawartość rejestru Y. Ta operacja jest też wykorzystywana oddzielnie przez wywołanie procedury od etykiety STLB ($DC9F) zamiast od początku.
            0100 ;Line Buffer SeaRch
            0110 ;
            0120 LBUFF = $0580
            0130 ;
            0140     *=  $DCA4
            0150 ;
            0160     LDX #$0A
            0170 NXT LDA LBUFF,X
            0180     CMP #'.
            0190     BEQ FIN
            0200     CMP #'0
            0210     BNE EXIT
            0220     DEX
            0230     BNE NXT
            0240 FIN DEX
            0250     LDA LBUFF,X
            0260 EXIT RTS
    Procedura LBSR przeszukuje wstecz od dziesiątego znaku bufor wyjściowy i zwraca w akumulatorze pierwszy napotkany znak różny od punktu dziesiętnego i od zera. Rejestr X zawiera wtedy indeks tego znaku od początku bufora.

    Po przekształceniu liczby na format FP może się zdarzyć, że uzyskany wynik nie odpowiada w pełni wymaganemu formatowi. Sześciobajtowa liczba zmiennoprzecinkowa musi mieć pierwszy bajt mantysy różny od zera. Niektóre z procedur konwersji nie spełniają tego wymagania i dlatego wywołują procedurę NFR0, która poprawia format liczby FP.
            0100 ;Normalize FR0
            0110 ;
            0120 FR0 =   $D4
            0130 FRE =   $DA
            0140 ZFR0 =  $DA44
            0150 ;
            0160     *=  $DC00
            0170 ;
            0180     LDX #$00
            0190     STX FRE
            0200 NFR0A LDX #$04
            0210     LDA FR0
            0220     BEQ EXIT
            0230 NX1 LDA FR0+1
            0240     BNE BPS
            0250     LDY #$00
            0260 NX2 LDA FR0+2,Y
            0270     STA FR0+1,Y
            0280     INY
            0290     CPX #$05
            0300     BCC NX2
            0310     DEC FR0
            0320     DEX
            0330     BNE NX1
            0340     LDA FR0+1
            0350     BNE BPS
            0360     STA FR0
            0370     CLC
            0380     RTS
            0390 BPS LDA FR0
            0400     AND #$7F
            0410     CMP #$71
            0420     BCC NEX
            0430     RTS
            0440 NEX CMP #$0F
            0450     BCS EXIT
            0460     JSR ZFR0
            0470 EXIT CLC
            0480     RTS
    Na początku sprawdzany jest wykładnik liczby FP i gdy jest on równy zero, to cała liczba jest równa zero i następuje opuszczenie procedury. Jeżeli liczba jest różna od zera to rozpoczyna się pętla normalizowania formatu mantysy.

    Gdy pierwszy bajt mantysy jest równy zero, to cała mantysa jest przesuwana o jeden bajt, a wykładnik zmniejsza się o 1. Operacja ta powtarzana jest czterokrotnie, chyba że w kolejnym przejściu zostanie stwierdzona niezerowa wartość pierwszego bajtu mantysy. Jeśli po tym mantysa nadal jest równa zero, to cała liczba otrzymuje przez wyzerowanie wykładnika wartość zero i procedura się kończy.

    Gdy wartość pierwszego bajtu mantysy jest różna od zera, następuje sprawdzenie wartości wykładnika. Wykładnik większy od 49 oznacza przekroczenie dopuszczalnego zakresu wartości i procedura kończy się z ustawionym bitem Carry. Wykładnik mniejszy od -49 także oznacza przekroczenie dozwolonego zakresu wartości, lecz nie wywołuje błędu, ale powoduje wyzerowanie całego rejestru FR0. Zakończenie procedury ze skasowanym bitem Carry oznacza, że liczba FP jest równa zero lub mieści się w dopuszczalnym zakresie.

    Ostatnią z procedur pomocniczych jest ZFR0, która służy do zerowania całego rejestru FR0.
            0100 ;set Zero to FR0
            0110 ;
            0120 FR0 =   $D4
            0130 ;
            0140     *=  $DA44
            0150 ;
            0160     LDX #FR0
            0170     LDY #$06
            0180 AF1 LDA #$00
            0190 NXT STA $00,X
            0200     INX
            0210     DEY
            0220     BNE NXT
            0230     RTS
    Jej dodatkowym zastosowaniem jest zerowanie dowolnego obszaru zerowej strony pamięci od adresu podanego w rejestrze X i o długości określonej przez rejestr Y. Wymaga to uprzedniego umieszczenia odpowiednich wartości w rejestrach X i Y oraz wywołanie procedury od etykiety AF1 ($DA48). Niektóre źródła podają wartość $DA46 jako adres AF1, lecz takie wywołanie nie występuje w żadnym miejscu pakietu procedur zmiennoprzecinkowych.

4.2.2. Procedury przemieszczeń liczb FP

    Druga ważna grupa procedur zmiennoprzecinkowych to procedury przemieszczeń liczb w formacie FP. Mamy w tej grupie procedury zapisu, odczytu i przenoszenia liczb FP. Można je łatwo wykorzystać we własnych programach, niekoniecznie w całości.
            0100 ;FP number LoaD to FR0
            0110 ;using X,Y Registers
            0120 ;
            0130 FLPTR = $FC
            0140 FR0 =   $D4
            0150 ;
            0160     *=  $DD89
            0170 ;
            0180     STX FLPTR
            0190     STY FLPTR+1
            0200 FLD0P LDY #$05
            0210 NXT LDA (FLPTR),Y
            0220     STA FR0,Y
            0230     DEY
            0240     BPL NXT
            0250     RTS
    Pierwsza procedura odczytuje sześciobajtową liczbę FP i umieszcza ją w rejestrze FR0. Liczba jest pobierana z miejsca, którego starszy bajt zawarty jest w rejestrze Y, a młodszy w rejestrze X. Wywołanie tej procedury od etykiety FLD0P (FP number LoaD into FR0 using Pointer - $DD8D) spowoduje przeniesienie do FR0 liczby, której adres jest wskazany wektorem FLPTR (FLoating PoinTeR).
            0100 ;FP number LoaD to FR1
            0110 ;using X,Y Registers
            0120 ;
            0130 FLPTR = $FC
            0140 FR1 =   $E0
            0150 ;
            0160     *=  $DD98
            0170 ;
            0180     STX FLPTR
            0190     STY FLPTR+1
            0200 FLD1P LDY #$05
            0210 NXT LDA (FLPTR),Y
            0220     STA FR1,Y
            0230     DEY
            0240     BPL NXT
            0250     RTS
    Działanie procedury FLD1R jest analogiczne, jedynie liczba FP jest umieszczana w rejestrze FR1. Również tutaj istnieje drugie wejście do procedury oznaczone etykietą FLD1P (FP number LoaD into FR1 using Pointer - $DD9C).
            0100 ;FP number STore from FR0
            0110 ;using X,Y Registers
            0120 ;
            0130 FLPTR = $FC
            0140 FR0 =   $D4
            0150 ;
            0160     *=  $DDA7
            0170 ;
            0180     STX FLPTR
            0190     STY FLPTR+1
            0200 FST0P LDY #$05
            0210 NXT LDA FR0,Y
            0220     STA (FLPTR),Y
            0230     DEY
            0240     BPL NXT
            0250     RTS
    Funkcję odwrotną w stosunku do FLD0R pełni procedura FST0R, która zapisuje we wskazanym miejscu pamięci zawartość rejestru FR0. Przy wywołaniu od etykiety FST0R jako wektor wykorzystywana jest zawartość rejestrów X i Y, a przy wywołaniu od FST0P (FP number STore using Pointer - $DDAB) zawartość rejestru FLPTR.

    Kolejne trzy procedury służą do przemieszczania liczb FP pomiędzy poszczególnymi rejestrami FR.
            0100 ;FP number MOVe
            0110 ;from FR0 to FR1
            0120 ;
            0130 FR0 =   $D4
            0140 FR1 =   $E0
            0150 ;
            0160     *=  $DDB6
            0170 ;
            0180     LDX #$05
            0190 NXT LDA FR0,X
            0200     STA FR1,X
            0210     DEX
            0220     BPL NXT
            0230     RTS
    Procedura FPMOV01 przepisuje zawartość rejestru FR0 do rejestru FR1.
            0100 ;FP number MOVe
            0110 ;from FR0 to FRE
            0120 ;
            0130 FR0 =   $D4
            0140 FRE =   $DA
            0150 ;
            0160     *=  $DD34
            0170 ;
            0180     LDY #$05
            0190 NXT LDA FR0,Y
            0200     STA FRE,Y
            0210     DEY
            0220     BPL NXT
            0230     RTS
    Procedura FPMOV0E przepisuje liczbę w formacie FP z rejestru FR0 do rejestru FRE.
            0100 ;FP number MOVe
            0110 ;from FR1 to FR2
            0120 ;
            0130 FR1 =   $E0
            0140 FR2 =   $E6
            0150 ;
            0160     *=  $DD28
            0170 ;
            0180     LDY #$05
            0190 NXT LDA FR1,Y
            0200     STA FR2,Y
            0210     DEY
            0220     BPL NXT
            0230     RTS
    Procedura FPMOV12 przepisuje sześć bajtów z rejestru FR1 do rejestru FR2.

4.2.3. Procedury obliczeń zmiennoprzecinkowych

    Najważniejszą częścią pakietu arytmetyki zmienno- przecinkowej są procedury wykonujące obliczenia na liczbach FP. Pakiet zawiera procedury czterech działań podstawowych oraz potęgowania i logarytmowania.

    Procedury dodawania (FADD) i odejmowania (FSUB) wymagają, aby argumenty działania znajdowały się w rejestrach FR0 i FR1. Różnią się one tylko początkiem - procedura FSUB najpierw zmienia znak liczby zawartej w FR1 - potem obie są identyczne.
            0100 ADJ0 =  $DC3A
            0110 ADJ1 =  $DC3E
            0120 FR0 =   $D4
            0130 FR1 =   $E0
            0140 NFR0 =  $DC00
            0150 ZTEMP2 = $F7
            0160 ;
            0170 ;Floating point SUBtraction
            0180 ;
            0190     *=  $DA60
            0200 ;
            0210 FSUB LDA FR1
            0220     EOR #$80
            0230     STA FR1
            0240 ;
            0250 ;Floating point ADDition
            0260 ;
            0270 FADD LDA FR1
            0280     AND #$7F
            0290     STA ZTEMP2
            0300     LDA FR0
            0310     AND #$7F
            0320     SEC
            0330     SBC ZTEMP2
            0340     BPL PLUS
            0350     LDX #$05
            0360 NX1 LDA FR0,X
            0370     LDY FR1,X
            0380     STA FR1,X
            0390     TYA
            0400     STA FR0,X
            0410     DEX
            0420     BPL NX1
            0430     BMI FADD
            0440 PLUS BEQ BP1
            0450     CMP #$05
            0460     BCS BP2
            0470     JSR ADJ1
            0480 BP1 SED
            0490     LDA FR0
            0500     EOR FR1
            0510     BMI BP3
            0520     LDX #$04
            0530     CLC
            0540 NX2 LDA FR0+1,X
            0550     ADC FR1+1,X
            0560     STA FR0+1,X
            0570     DEX
            0580     BPL NX2
            0590     CLD
            0600     BCS RR
            0610 BP2 JMP NFR0
            0620 ADJ LDA #$01
            0630     JSR ADJ0
            0640     LDA #$01
            0650     STA FR0+1
            0660     JMP NFR0
            0670 BP3 LDX #$04
            0680     SEC
            0690 NX3 LDA FR0+1,X
            0700     SBC FR1+1,X
            0710     STA FR0+1,X
            0720     DEX
            0730     BPL NX3
            0740     BCC BP4
            0750     CLD
            0760     JMP NFR0
            0770 BP4 LDA FR0
            0780     EOR #$80
            0790     STA FR0
            0800     SEC
            0810     LDX #$04
            0820 NX4 LDA #$00
            0830     SBC FR0+1,X
            0840     STA FR0+1,X
            0850     DEX
            0860     BPL NX4
            0870     CLD
            0880     JMP NFR0
    Ponieważ pierwszy argument (zawarty w FR0) nie może być mniejszy od drugiego (FR1), to najpierw porównywane są ich wykładniki. Gdy powyższe wymaganie jest niespełnione, liczby są zamieniane miejscami.

    Gdy różnica między wykładnikami jest większa od 4, to wykonanie działania nie zmieni wartości cyfr znaczących mantysy większego argumentu. W takim przypadku następuje bezpośredni skok do procedury NFR0.

    Różnica między wykładnikami mniejsza od 5 powoduje wywołanie procedury ADJ1, która wyrównuje wartości wykładników i odpowiednio przesuwa bajty mantysy liczby zawartej w FR1.

    Jeśli liczby mają równe wykładniki i jednakowy znak, to kolejne bajty ich mantys są dodawane. Wystąpienie przepełnienia podczas dodawania sygnalizuje konieczność przesunięcia mantysy i zwiększenia wartości wykładnika.

    Gdy liczby różnią się znakiem, to kolejne bajty ich mantys są odejmowane. Brak przepełnienia (bit Carry skasowany) po tej operacji sygnalizuje zmianę znaku wyniku. W takim razie wszystkie bajty mantysy muszą być jeszcze zamienione na ich uzupełnienie.

    W każdym przypadku procedury FADD i FSUB nie kończą się rozkazem RTS, lecz bezpośrednim skokiem do procedury NFR0, która doprowadza wynik do prawidłowego formatu.

    Wykonanie dodawania lub odejmowania wymaga, aby wykładniki argumentów były jednakowe. Wyrównanie wykładników przeprowadzane jest przez procedury ADJ0 i ADJ1. Przed wywołaniem tych procedur w akumulatorze musi być umieszczona różnica między wykładnikami.
            0100 ;ADJust FR0 or FR1
            0110 ;
            0120 FR0 =   $D4
            0130 FR1 =   $E0
            0140 ZTEMP2 = $F7
            0150 ZTEMP3 = $F9
            0160 ;
            0170     *=  $DC3A
            0180 ;
            0190 ADJ0 LDX #FR0
            0200     BNE BPS
            0210 ADJ1 LDX #FR1
            0220 BPS STX ZTEMP3
            0230     STA ZTEMP2
            0240     STA ZTEMP2+1
            0250 NX1 LDY #$04
            0260 NX2 LDA $04,X
            0270     STA $05,X
            0280     DEX
            0290     DEY
            0300     BNE NX2
            0310     LDA #$00
            0320     STA $05,X
            0330     LDX ZTEMP3
            0340     DEC ZTEMP2
            0350     BNE NX1
            0360     LDA $00,X
            0370     CLC
            0380     ADC ZTEMP2+1
            0390     STA $00,X
            0400     RTS
    Na początku zawartość akumulatora jest zapisywana do obu bajtów rejestru ZTEMP2. Pierwszy bajt jest wykorzystywany jako licznik pętli, a drugi posłuży do uaktualnienia wartości wykładnika. Teraz wymaganą liczbę razy bajty mantysy są przesuwane w prawo i po dodaniu do wykładnika zawartości drugiego bajtu ZTEMP2 procedura się kończy.

    Druga para procedur obliczeniowych (FMUL i FDIV) ma wspólny jedynie koniec. Jednak ze względu na położenie w pamięci i podobieństwo funkcji zostaną one opisane razem.
            0100 ADD01 = $DD01
            0110 ADD02 = $DD05
            0120 ADDE1 = $DD09
            0130 ADDE2 = $DD0F
            0140 EEXP =  $ED
            0150 EVSGN = $DCE0
            0160 FR0 =   $D4
            0170 FR1 =   $E0
            0180 FR2 =   $E6
            0190 FRE =   $DA
            0200 NFR0A = $DC04
            0210 SGNEV = $DCCF
            0220 SHR0 =  $DC62
            0230 ZFR0 =  $DA44
            0240 ZTEMP1 = $F5
            0250 ;
            0260 ;Floating point MULtiplication
            0270 ;
            0280     *=  $DADB
            0290 ;
            0300 FMUL LDA FR0
            0310     BEQ EXC
            0320     LDA FR1
            0330     BEQ EXZ
            0340     JSR SGNEV
            0350     SEC
            0360     SBC #$40
            0370     SEC
            0380     ADC FR1
            0390     BMI EXS
            0400     JSR EVSGN
            0410 NX1 LDA FRE+5
            0420     AND #$0F
            0430     STA ZTEMP1+1
            0440 LP1 DEC ZTEMP1+1
            0450     BMI EN1
            0460     JSR ADD01
            0470     JMP LP1
            0480 EN1 LDA FRE+5
            0490     LSR A
            0500     LSR A
            0510     LSR A
            0520     LSR A
            0530     STA ZTEMP1+1
            0540 LP2 DEC ZTEMP1+1
            0550     BMI EN2
            0560     JSR ADD02
            0570     JMP LP2
            0580 EN2 JSR SHR0
            0590     DEC ZTEMP1
            0600     BNE NX1
            0610 EXIT LDA EEXP
            0620     STA FR0
            0630     JMP NFR0A
            0640 EXZ JSR ZFR0
            0650 EXC CLC
            0660     RTS
            0670 EXS SEC
            0680     RTS
            0690 ;
            0700 ;Floating point DIVision
            0710 ;
            0720 FDIV LDA FR1
            0730     BEQ EXS
            0740     LDA FR0
            0750     BEQ EXC
            0760     JSR SGNEV
            0770     SEC
            0780     SBC FR1
            0790     CLC
            0800     ADC #$40
            0810     BMI EXS
            0820     JSR EVSGN
            0830     INC ZTEMP1
            0840     JMP BPS
            0850 LP3 LDX #$00
            0860 NX2 LDA FR0+1,X
            0870     STA FR0,X
            0880     INX
            0890     CPX #$0C
            0900     BNE NX2
            0910 BPS LDY #$05
            0920     SEC
            0930     SED
            0940 NX3 LDA FRE,Y
            0950     SBC FR2,Y
            0960     STA FRE,Y
            0970     DEY
            0980     BPL NX3
            0990     CLD
            1000     BCC LP4
            1010     INC FR0+5
            1020     BNE BPS
            1030 LP4 JSR ADDE2
            1040     ASL FR0+5
            1050     ASL FR0+5
            1060     ASL FR0+5
            1070     ASL FR0+5
            1080 NX4 LDY #$05
            1090     SEC
            1100     SED
            1110 NX5 LDA FRE,Y
            1120     SBC FR1,Y
            1130     STA FRE,Y
            1140     DEY
            1150     BPL NX5
            1160     CLD
            1170     BCC LP5
            1180     INC FR0+5
            1190     BNE NX4
            1200 LP5 JSR ADDE1
            1210     DEC ZTEMP1
            1220     BNE LP3
            1230     JSR SHR0
            1240     JMP EXIT
    Po wywołaniu obu procedur najpierw sprawdzane są wykładniki argumentów. Jeżeli wykładnik pierwszej liczby jest równy zeru, to procedura się kończy. Gdy zerem jest wykładnik drugiej liczby, to w przypadku mnożenia pierwsza liczba jest zerowana i również następuje opuszczenie procedury, a przy dzieleniu przed końcem procedury bit Carry jest ustawiany w celu zasygnalizowania błędu.

    Jeśli oba wykładniki są niezerowe, to wywoływana jest procedura SGNEV, która oblicza znak wyniku i zapisuje go w rejestrze NSIGN (Number SIGN). Znaki obu argumentów są następnie kasowane.
            0100 ;SiGN EValuation
            0110 ;
            0120 FR0 =   $D4
            0130 FR1 =   $E0
            0140 NSIGN = $EE
            0150 ;
            0160     *=  $DCCF
            0170 ;
            0180     LDA FR0
            0190     EOR FR1
            0200     AND #$80
            0210     STA NSIGN
            0220     ASL FR1
            0230     LSR FR1
            0240     LDA FR0
            0250     AND #$7F
            0260     RTS
    Po zakończeniu SGNEV obliczany jest wykładnik wyniku. Jeżeli przekracza on dopuszczalny zakres, to procedura jest przerywana z ustawionym bitem Carry. W przeciwnym razie wywoływana jest procedura MVARG.
            0100 ;MoVe ARGuments
            0110 ;
            0120 EEXP =  $ED
            0130 FMOV0E = $DD34
            0140 FMOV12 = $DD28
            0150 FR0 =   $D4
            0160 FR1 =   $E0
            0170 FR2 =   $E6
            0180 FRX =   $EC
            0190 NSIGN = $EE
            0200 ROLFR2 = $DBE7
            0210 ZFR0 =  $DA44
            0220 ZTEMP1 = $F5
            0230 ;
            0240     *=  $DCE0
            0250 ;
            0260     ORA NSIGN
            0270     STA EEXP
            0280     LDA #$00
            0290     STA FR0
            0300     STA FR1
            0310     JSR FMOV12
            0320     JSR ROLFR2
            0330     LDA FRX
            0340     AND #$0F
            0350     STA FR2
            0360     LDA #$05
            0370     STA ZTEMP1
            0380     JSR FMOV0E
            0390     JSR ZFR0
            0400     RTS
    Najpierw dodaje ona znak do obliczonego bajtu wykładnika i całość zapisuje w rejestrze EEXP. Następnie zeruje wykładniki obu argumentów, a argumenty przepisuje z FR0 do FRE i z FR1 do FR2. Zawartość FR2 jest przy tym przesuwana w lewo o cztery bity tak, że pierwsza cyfra mantysy znajduje się teraz w bajcie wykładnika. Przed końcem MVARG zerowany jest jeszcze rejestr FR0.

    Dalszy przebieg obu procedur (FMUL i FDIV) jest także bardzo podobny. Argumenty działania są dodawane lub odejmowane w pętli, aż do osiągnięcia prawidłowego wyniku. Zastosowane jest tu wielokrotne dodawanie zamiast mnożenia i wielokrotne odejmowanie zamiast dzielenia. Do wykonania tych operacji służy procedura NADD, która wywoływana jest zależnie od potrzeby od jednej z czterech etykiet.
            0100 ;Numbers ADDition
            0110 ;
            0120 FR0 =   $D4
            0130 FR1 =   $E0
            0140 FR2 =   $E6
            0150 FRE =   $DA
            0160 ZTEMP2 = $F7
            0170 ;
            0180     *=  $DD01
            0190 ;
            0200 ADD01 LDX #FR0+5
            0210     BNE AD1
            0220 ADD02 LDX #FR0+5
            0230     BNE AD2
            0240 ADDE1 LDX #FRE+5
            0250 AD1 LDY #FR1+5
            0260     BNE CONT
            0270 ADDE2 LDX #FRE+5
            0280 AD2 LDY #FR2+5
            0290 CONT LDA #$05
            0300     STA ZTEMP2
            0310     CLC
            0320     SED
            0330 NXT LDA $00,X
            0340     ADC $00,Y
            0350     STA $00,X
            0360     DEX
            0370     DEY
            0380     DEC ZTEMP2
            0390     BPL NXT
            0400     CLD
            0410     RTS
    Dodaje ona w trybie dziesiętnym procesora zawartości rejestrów liczb FP parami (zależnie od miejsca wywołania). Po wywołaniu od ADD01 dodaje zawartość FR1 do FR0, od ADD02 ($DD05) FR2 do FR0, od ADDE1 ($DD09) FR1 do FRE, a od ADDE2 ($DD0F) FR2 do FRE.
            0100 ;SHift Right FR0
            0110 ;
            0120 FR0 =   $D4
            0130 ;
            0140     *=  $DC62
            0150 ;
            0160     LDX #$0A
            0170 NXT LDA FR0,X
            0180     STA FR0+1,X
            0190     DEX
            0200     BPL NXT
            0210     LDA #$00
            0220     STA FR0
            0230     RTS
    Drugą procedurą pomocniczą jest SHR0, która na końcu każdej pętli obliczeń przesuwa w prawo zawartość rejestru FR0 i do bajtu wykładnika wpisuje zero.

    Po zakończeniu obliczania mantysy wcześniej obliczony bajt wykładnika jest przepisywany z rejestru EEXP. Teraz w celu uzyskania poprawnego formatu wyniku wykonywany jest bezpośredni skok do procedury NFR0.

    Kolejna procedura służy do przeliczeń wielomianowych i jest używana przy obliczeniach logarytmicznych i wykładniczych. Korzysta ona z tabeli współczynników, której adres jest umieszczany w rejestrach X i Y przed wywołaniem PLYARG. Liczba przejść pętli obliczeniowej jest podana w akumulatorze.
            0100 ;PoLYnomial EVaLuation
            0110 ;
            0120 ESIGN = $EF
            0130 FADD =  $DA66
            0140 FLD0R = $DD89
            0150 FLD1R = $DD98
            0160 FMOV01 = $DDB6
            0170 FMUL =  $DADB
            0180 FPTR2 = $FE
            0190 FST0R = $DDA7
            0200 PLYARG = $05E0
            0210 ;
            0220     *=  $DD40
            0230 ;
            0240     STX FPTR2
            0250     STY FPTR2+1
            0260     STA ESIGN
            0270     LDX # <PLYARG
            0280     LDY # >PLYARG
            0290     JSR FST0R
            0300     JSR FMOV01
            0310     LDX FPTR2
            0320     LDY FPTR2+1
            0330     JSR FLD0R
            0340     DEC ESIGN
            0350     BEQ EXIT
            0360 LOOP JSR FMUL
            0370     BCS EXIT
            0380     CLC
            0390     LDA FPTR2
            0400     ADC #$06
            0410     STA FPTR2
            0420     BCC BPS
            0430     LDA FPTR2+1
            0440     ADC #$00
            0450     STA FPTR2+1
            0460 BPS LDX FPTR2
            0470     LDY FPTR2+1
            0480     JSR FLD1R
            0490     JSR FADD
            0500     BCS EXIT
            0510     DEC ESIGN
            0520     BEQ EXIT
            0530     LDX # <PLYARG
            0540     LDY # >PLYARG
            0550     JSR FLD1R
            0560     BMI LOOP
            0570 EXIT RTS
    Procedura PLYEVL wykonuje obliczenie wg wzoru:
             ((...((A1*x+A2)*x+A3)...)*x+An)
gdzie x jest zawartością rejestru FR0 w chwili wywołania procedury (przechowywana w rejestrze PLYARG), a A1,2...n są kolejnymi współczynnikami z tabeli. Obliczenie jest wykonywane aż do wystąpienia przepełnienia lub do wyzerowania licznika przepisanego na początku procedury z akumulatora do rejestru ESIGN.

    Procedura potęgowania posiada dwa punkty początkowe. Po wywołaniu od etykiety EXP ($DDC0) podnosi liczbę e do potęgi zawartej w FR0, zaś po wywołaniu od EXP10 ($DDCC) bazą potęgowania jest liczba 10.
            0100 ;EXPonentation
            0110 ;
            0120 DIGRT = $F1
            0130 FCHRFLG = $F0
            0140 FDIV =  $DB28
            0150 FLD0R = $DD89
            0160 FLD1R = $DD98
            0170 FMOV01 = $DDB6
            0180 FMUL =  $DADB
            0190 FPI =   $D9D2
            0200 FR0 =   $D4
            0210 FR1 =   $E0
            0220 FRE =   $E6
            0230 FST0R = $DDA7
            0240 FSUB =  $DA60
            0250 IFP =   $D9AA
            0260 PLYEVL = $DD40
            0270 ;
            0280     *=  $DDC0
            0290 ;
            0300 EXP LDX #$89
            0310     LDY #$DE
            0320     JSR FLD1R
            0330     JSR FMUL
            0340     BCS EX2
            0350 EXP10 LDA #$00
            0360     STA DIGRT
            0370     LDA FR0
            0380     STA FCHRFLG
            0390     AND #$7F
            0400     STA FR0
            0410     SEC
            0420     SBC #$40
            0430     BMI EVL
            0440     CMP #$04
            0450     BPL EX2
            0460     LDX #FRE
            0470     LDY #$05
            0480     JSR FST0R
            0490     JSR FPI
            0500     LDA FR0
            0510     STA DIGRT
            0520     LDA FR0+1
            0530     BNE EX2
            0540     JSR IFP
            0550     JSR FMOV01
            0560     LDX #FRE
            0570     LDY #$05
            0580     JSR FLD0R
            0590     JSR FSUB
            0600 EVL LDA #$0A
            0610     LDX #$4D
            0620     LDY #$DE
            0630     JSR PLYEVL
            0640     JSR FMOV01
            0650     JSR FMUL
            0660     LDA DIGRT
            0670     BEQ FIN
            0680     CLC
            0690     ROR A
            0700     STA FR1
            0710     LDA #$01
            0720     BCC BPS
            0730     LDA #$10
            0740 BPS STA FR1+1
            0750     LDX #04
            0760     LDA #$00
            0770 NXT STA FR1+2,X
            0780     DEX
            0790     BPL NXT
            0800     LDA FR1
            0810     CLC
            0820     ADC #$40
            0830     BCS EX2
            0840     BMI EX2
            0850     STA FR1
            0860     JSR FMUL
            0870 FIN LDA FCHRFLG
            0880     BPL EX1
            0890     JSR FMOV01
            0900     LDX #$8F
            0910     LDY #$DE
            0920     JSR FLD0R
            0930     JSR FDIV
            0940 EX1 RTS
            0950 EX2 SEC
            0960     RTS
    Przy potęgowaniu liczby e wartość potęgi jest najpierw mnożona przez współczynnik z tabeli. Po tej operacji oba warianty potęgowania przebiegają identycznie.

    Teraz sprawdzany jest znak współczynnika liczby zawartej w FR0. Dodatni wykładnik wymaga jeszcze sprawdzenia zakresu. Gdy jest większy od 3, tzn. liczba jest większa od 1000000, to wartość wyniku potęgowania przekroczy zakres dopuszczalny dla liczb FP. Następnie argument jest zamieniany na dwubajtową liczbę całkowitą i jeśli starszy bajt jest niezerowy, to również przekroczony zostanie dozwolony zakres wartości wyniku. W obu tych przypadkach ustawiany jest bit Carry i procedura się kończy.

    Jeżeli wartość argumentu jest poprawna, to rozpoczyna się obliczanie wyniku. Są przy tym wykorzystywane procedury FMUL, FDIV i PLYEVL, lecz samo obliczenie jest dość skomplikowane i jego opis zostanie pominięty. Czytelnicy zainteresowani tym zagadnieniem mogą skorzystać z zamieszczonego programu źródłowego procedury.

    Także procedura logarytmowania rozpoczyna się w dwóch różnych miejscach, zależnie od podstawy logarytmu. Od etykiety LOG ($DECD) obliczany jest logarytm naturalny, a od LOG10 ($DED1) logarytm dziesiętny.
            0100 ;LOGarithm
            0110 ;
            0120 DIGRT = $F1
            0130 FADD =  $DA66
            0140 FCHRFLG = $F0
            0150 FDIV = $DB28
            0160 FLD1R = $DD98
            0170 FMOV01 = $DDB6
            0180 FMUL =  $DADB
            0190 FR0 =   $D4
            0200 FR1 =   $E0
            0210 FR2 =   $E6
            0220 FST0R = $DDA7
            0230 IFP =   $D9AA
            0240 PLYEVL = $DD40
            0250 RSQT =  $DE95
            0260 ;
            0270     *=  $DECD
            0280 ;
            0290 LOG LDA #$01
            0300     BNE CONT
            0310 LOG10 LDA #$00
            0320 CONT STA FCHRFLG
            0330     LDA FR0
            0340     BEQ EXS
            0350     BMI EXS
            0360     JMP PT1
            0370 EXS SEC
            0380     RTS
            0390 PT2 SBC #$40
            0400     ASL A
            0410     STA DIGRT
            0420     LDA FR0+1
            0430     AND #$F0
            0440     BNE DIG
            0450     LDA #$01
            0460     BNE BPS
            0470 DIG INC DIGRT
            0480     LDA #$10
            0490 BPS STA FR1+1
            0500     LDX #$04
            0510     LDA #$00
            0520 NXT STA FR1+2,X
            0530     DEX
            0540     BPL NXT
            0550     JSR FDIV
            0560     LDX #$66
            0570     LDY #$DF
            0580     JSR RSQT
            0590     LDX #FR2
            0600     LDY #$05
            0610     JSR FST0R
            0620     JSR FMOV01
            0630     JSR FMUL
            0640     LDA #$0A
            0650     LDX #$72
            0660     LDY #$DF
            0670     JSR PLYEVL
            0680     LDX #FR2
            0690     LDY #$05
            0700     JSR FLD1R
            0710     JSR FMUL
            0720     LDX #$6C
            0730     LDY #$DF
            0740     JSR FLD1R
            0750     JSR FADD
            0760     JSR FMOV01
            0770     LDA #$00
            0780     STA FR0+1
            0790     LDA DIGRT
            0800     STA FR0
            0810     BPL CNV
            0820     EOR #$FF
            0830     CLC
            0840     ADC #$01
            0850     STA FR0
            0860 CNV JSR IFP
            0870     BIT DIGRT
            0880     BPL ADD
            0890     LDA #$80
            0900     ORA FR0
            0910     STA FR0
            0920 ADD JSR FADD
            0930     LDA FCHRFLG
            0940     BEQ EXC
            0950     LDX #$89
            0960     LDY #$DE
            0970     JSR FLD1R
            0980     JSR FDIV
            0990 EXC CLC
            1000     RTS
            1010 ;
            1020     *=  $DFF6
            1030 ;
            1040 PT1 LDA FR0
            1050     STA FR1
            1060     SEC
            1070     JMP PT2
    Na początku sprawdzany jest wykładnik argumentu i gdy jest ujemny lub równy zero, to procedura kończy się z sygnalizacją błędu (ustawiony bit Carry). W przeciwnym razie wykładnik jest przepisywany z FR0 do FR1 i rozpoczyna się właściwa część obliczeniowa procedury.

    Opis całej procedury logarytmowania również zostanie pominięty, należy jednak zwrócić uwagę na jej podział na trzy części. Zostało to spowodowane wprowadzeniem poprawek do pierwotnej wersji pakietu procedur zmiennoprzecinkowych przy zachowaniu adresów początkowych procedur.

    Podczas operacji logarytmowania jest wywoływana jeszcze jedna, wcześniej nie omawiana procedura - RSQT.
            0100 ;ReSult QuoTient
            0110 ;
            0120 FADD =  $DA66
            0130 FDIV =  $DB28
            0140 FLD0R = $DD89
            0150 FLD1R = $DD98
            0160 FPTR2 = $FE
            0170 FR1 =   $E0
            0180 FR2 =   $E6
            0190 FST0R = $DDA7
            0200 FSUB =  $DA60
            0210 ;
            0220     *=  $DE95
            0230 ;
            0240     STX FPTR2
            0250     STY FPTR2+1
            0260     LDX #FR1
            0270     LDY #$05
            0280     JSR FST0R
            0290     LDX FPTR2
            0300     LDY FPTR2+1
            0310     JSR FLD1R
            0320     JSR FADD
            0330     LDX #FR2
            0340     LDY #$05
            0350     JSR FST0R
            0360     LDX #FR1
            0370     LDY #$05
            0380     JSR FLD0R
            0390     LDX FPTR2
            0400     LDY FPTR2+1
            0410     JSR FLD1R
            0420     JSR FSUB
            0430     LDX #FR2
            0440     LDY #$05
            0450     JSR FLD1R
            0460     JSR FDIV
            0470     RTS
    Wykonuje ona obliczenie ilorazu różnicowego dwóch argumentów. Adres pierwszego argumentu musi być przed wywołaniem procedury umieszczony w rejestrach X i Y, a drugi argument znajduje się w rejestrze FR1. Wynik obliczony według wzoru
                          x1-x2
                         -------
                          x1+x2
jest umieszczany w rejestrze FR0.

    Jak wynika z opisu procedur, pakiet arytmetyki FP zawiera jeszcze tabele współczynników potęgowania i logarytmowania. Są one umieszczone w obszarach od $DE4D do $DE94 oraz od $DF66 do $DFF5.

    Poza wyżej wymienionymi jeszcze cztery procedury arytmetyki FP wbudowane są do interpretera Atari Basic. Uniemożliwia to korzystanie z nich przy odłączonym interpreterze. Są to:
      $BDA7   SIN - FP SINus routine
      $BDB1   COS - FP COSinus routine
      $BE77   ATAN - FP ArcTANgent routine
      $BEE5   SQR - FP SQuare Root routine
Procedury należące do interpretera Atari Basic są opisane razem z tym interpreterem (zob. "Mapa pamięci ATARI XL/XE. Procedury interpretera Basica").
Zientara Wojciech: Mapa pamięci Atari XL/XE. Podstawowe procedury systemu operacyjnego, SOETO, Warszawa, 1988.