MulDivPo początkowym zainteresowaniu różnego rodzaju grami komputerowymi przychodzi czas na napisanie pierwszego programu. Początkujący programiści zaczynają zazwyczaj od poznania języka BASIC. Pomimo swych wielu wad posiada on niewątpliwą zaletę, jaką jest stała obecność w pamięci komputera, w związku z czym nie trzeba go wczytywać z dysku lub, co gorsza, z kasety. Język ten, bądź co bądź przestarzały i wyrabiający nie najlepsze nawyki, nie sprawia większych problemów w realizowaniu najprostszych pomysłów. Po pewnym jednak czasie jego możliwości stają się niewystarczające. Wielu programistów zdenerwowanych powolnością BASIC-a sięga wtedy po wygodniejsze i szybsze narzędzia, jakimi są np. TURBO BASIC XL lub Action!. Są jednak i tacy, którzy postanawiają zgłębić tajniki sztuki, jaką jest programowanie komputerów, sięgając po język niższego rzędu - Assembler. Powszechnie wiadomo, że jest to język bardzo szybki. Niestety, jego zwinność okupiona została niewielką liczbą operacji dostępnych dla użytkownika. Należą do nich między innymi proste operacje logiczne, porównania, skoki, a także działania arytmetyczne na liczbach binarnych i w tzw. kodzie BCD. Projektanci 6502 zapomnieli niestety o bardzo wygodnych rozkazach mnożenia i dzielenia, w które są wyposażone inne, tak popularne procesory, jak np. INTEL 8086. Kiedy więc programista, piszący dawniej w BASIC-u, zechce przeprowadzić tak prostą z jego punktu widzenia operację, jaką jest MNOŻENIE.Tok postępowania jest prosty: aby otrzymać wynik mnożenia liczb A przez B, należy liczbę A dodać do siebie B razy. Poniższy program doskonale to obrazuje: __mul ldy __b beq __don ;skok gdy B=0 clc __lop adc __a ;dodanie A bcc __dec inx ;zwiększenie MSB wyniku clc __dec dey ;zmniejszenie B o 1 bne __lop ;skok gdy B<>0 __don rtsPrzed skokiem do podprogramu należy do rejestrów .A i .X w kolejności LSB, MSB wpisać liczbę, do której ma zostać dodany wynik mnożenia (jest to bardzo wygodne przy wielokrotnym sumowaniu iloczynów różnych liczb), a w komórkach __A i __B umieścić wartości czynników mnożenia. Dla potrzeb programu należy zdefiniować jeszcze dwie komórki pomocnicze: __a dta b(0) __b dta b(0)Rezultat operacji uzyskamy w rejestrach .A i .Y procesora w kolejności LSB, MSB. Widzimy tutaj, że czas trwania procedury zależy od liczby wpisanej do __B. Im jest ona większa, tym dłużej będzie przebiegać cała operacja. Tak więc osobom, którym zależy przede wszystkim na czasie, w związku z czym powyższa procedura może dla ich potrzeb okazać się niewystarczająca, proponuję dokładną analizę kolejnej metody, opierającej się również na wielokrotnym dodawaniu. Jak wiadomo z trzeciej klasy szkoły podstawowej, każdy iloczyn dwóch liczb można rozpisać jako iloczyn jednego z czynników mnożenia i sumy dowolnych liczb, dających w wyniku drugi czynnik owego działania, np.: 2*20=2*(10+5+3+2) lub 2*20=(1+1)*20. Zgodnie z powyższym możemy dowolne mnożenie rozpisać jako iloczyn pierwszego z czynników mnożenia i sumę kolejnych, nie powtarzających się potęg liczby dwa, dających w wyniku drugi czynnik działania, np.: 3*28=3*(16+8+4)=3*(24+23+22)= 3*(1*24+1*23+1*22+0*21+0*20). Wynik mnożenia uzyskamy, sumując poszczególne iloczyny pierwszego czynnika mnożenia przez odpowiednie potęgi liczby dwa. Niejeden Czytelnik w tym momencie mógłby się zdziwić i powiedzieć: "Przecież procesor 6502 nie posiada na swojej liście rozkazów mnożenia, co czyni niemożliwym wykonanie także i potęgowania!". Niewątpliwie miałby on rację, lecz procesor 6502 posiada polecenia ASL i ROL, które przesuwają poszczególne bity w lewo, powodując proste mnożenie przez dwa. Poprzez wielokrotną rotację dowolnej liczby w lewo otrzymamy więc iloczyn tejże liczby i poszczególnych potęg liczby "dwa", np.: 3*2 = 3*21 - przesunięcie jednokrotne 3*2*2 = 3*22 - przesunięcie dwukrotne 3*2*2*2=3*23 - przesunięcie trzykrotne. Program obliczający iloczyn dwóch liczb i opierający się na powyższych założeniach wygląda tak: __mul eor #%1lllllll ;modyfikacja sta __bta ;pierwszego czynnika stx __btb ;przygotowanie ldx #0 ;drugiego czynnika stx __btb+2 ldx #8 ;długość słowa __lop lsr __bta ;sprawdzenie aktualnej bcs __nxt ;potęgi dwójki lda __btb ;dodanie __BTB adc __rsl ;pomnożonego przez 2 sta __rsl ;2 do potęgi 8-.X do lda __btb+1 ;wyniku adc __rsl+1 sta __rsl+1 __nxt asl __btb ;pomnożenie __BTB rol __btb+1 ;przez kolejną potęgę ;liczby 2 dex bne __lop rtsDo prawidłowego działania procedury należy jeszcze zarezerwować komórki, gdzie będą umieszczone wartości czynników mnożenia i wynik. __bta org *+1 __btb org *+2 __rsl org *+2Moment zastanowienia mogłaby wywołać rezerwacja dwóch bajtów dla __BTB. Jest to celowe, gdyż np. liczba 128 pomnożona już przez pierwszą potęgę liczby 2 daje w wyniku 256, a więc liczbę, nie mieszczącą się w ośmiu bitach. Przed użyciem procedury należy umieścić liczbę, która będzie dodana do iloczynu w komórkach __RSL oraz __RSL+1 w kolejności LSB i MSB, a w .A i .X wpisać czynniki mnożenia. Powyższy podprogram powoduje pomnożenie przez siebie dwóch liczb ośmiobitowych, wpisując szesnastobitowy wynik do komórek __RSL i __RSL+1 w wiadomej kolejności. Warto zauważyć, ze w przeciwieństwie do poprzedniego, czas wykonywania tego podprogramu jest dla różnych danych mniej więcej taki sam. Niemniej wygodnym, co spędzającym sen z powiek utrapieniem jest DZIELENIE.__div ldy #0 ;przygotowanie .Y __lop lda __a ;odejmowanie dzielnika sbc __b ;od dzielnej tax lda __a+1 sbc #0 bcc __don ;skok gdy __A<__B stx __a ;zapis reszty sta __a+1 iny ;zwiększenie wyniku o 1 bne __lop ldy #$ff ;dzielenie przez liczbę don__ rts ;dającą wynik>255Przed uruchomieniem procedury należy umieścić dzielną i dzielnik we wcześniej zarezerwowanych komórkach. __a org *+2 __b org *+1Po powrocie z podprogramu .Y zawiera ośmiobitowy wynik (w przypadku dzielenia przez zero umieszczana jest tam wartość 255), a komórka __A resztę z dzielenia __A przez __B. Zawartość dzielnika nie jest kasowana. W tym przypadku czas działania procedury zależy od wielkości dzielnej i wartości dzielnika (im mniejszy dzielnik i większa dzielna, tym dłuższy czas). Niżej przedstawiona procedura dzielenia jest dokładną odwrotnością, opisywanego przeze mnie, drugiego ze sposobów mnożenia liczb ośmiobitowych. Polega ona na odejmowaniu od dzielnej iloczynów dzielnika i poszczególnych potęg liczby dwa w kolejności od najstarszej do najmłodszej. Tok postępowania: 1. Jeśli dzielna jest większa lub równa iloczynowi dzielnika i 2N oznacza to, że dzielnik mieści się w dzielnej 2N razy, np.: 128=8*24 - dzielnik (8) mieści się w dzielnej (128) 24 razy. 2. Jeśli relacja ta jest prawdziwa, dodajemy do wyniku 2N i odejmujemy od dzielnej iloczyn tegoż właśnie 2N i dzielnika. 3. Zmniejszamy numer potęgi (N) liczby 2, przez którą mnożymy dzielnik i jeśli N=0, to powtarzamy wszystkie operacje. Po zakończeniu działania reszta z dzielenia znajduje się w dzielnej. Oto przykładowy przebieg ośmiobitowego dzielenia liczby 120 przez 11: wynik: 00001010 bin = 10 dec, reszta: 10 dec. Opis metody odbiega nieco od treści procedury (zamiast nieporęcznych operacji dodawania 2N do wyniku, użyłem rotacji bitów), jednakże jej istota nie została przez to zmieniona. __div sta __bta ;przygotowanie stx __bta+1 ;danych ldx #0 stx __btb sty __btb+1 ;dzielnik*2^8 ldx #8 ;długość słowa __lop lsr __btb ;dzielnik*2(.X-1) ror __btb+1 lda __bta ;dzielna-dzielnik sbc __btb ;a zarazem porównanie tay lda __bta+1 sbc __btb+1 bcc __nxt ;skok gdy __BTA<__BTB sty __bta ;dzielna=dzielna+ sta __bta+1 ; -dzielnik*2(.X-1) __nxt rol __rsl ;"wciągnięcie" potęgi dex ;.X-1 do wyniku bne __lop rtsProcedura ta, jak i poprzednie, także wymaga rezerwacji pamięci dla swych danych. __bta org *+2 __btb org *+2 __rsl org *+1Wykorzystanie procedury sprowadza się do umieszczenia dzielnej w .A i .X w formacie LSB i MSB, oraz dzielnika w .Y i wywołania __div. Po powrocie z podprogramu w komórce __RSL znajduje się wynik a w __BTA reszta z dzielenia. Analogicznie, jak w przypadku drugiej procedury mnożącej dwa bajty, czas obliczeń powyższej procedury nie zależy od wartości danych. Uwaga! Zawartości komórek od __BTA do __RSL włącznie ulegają zmianie w trakcie działania podprogramu. Wszystkie opublikowane procedury można w prosty sposób uzdatnić do obliczeń o dowolnej precyzji. W jaki sposób? Ten problem pozostawiam Czytelnikowi. (jk)
P.S. Procedury zostały napisane pod QA w konwencji programów firmowych. Użyte w tekście skróty LSB i MSB oznaczają rzecz jasna "least significant byte" - mniej znaczący bajt i "most significant byte" - bardziej znaczący bajt. |