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").
|