Dnes sa budeme zaoberat dalsou matematickou operaciou, ktora
nie je priamo zahrnuta v instrukcnom subore Z80. Je to delenie.
Delenie cisla M (delenec) cislom N (delitel) nie je vlastne nic
ine, ako zistovanie kolkokrat treba k nule pricitat cislo N aby
vzniklo cislo M, alebo kolkokrat treba od cisla M odcitat N aby
sme dospeli k nule. Vsimnime si tuto druhu myslienku a skusme ju
realizovat.
Nasobenie dvoch cisel v minulej lekci nam davalo 16-bitovy
vysledok. Preto skusme tu nasu rutinku na delenie upravit tak,
aby pracovala so 16-bitovym delencom. Z toho nam vyplyva, ze na
odcitanie budeme musiet pouzit instrukcie ktora odcitavaju
16-bitove cisla. Tieto instrukcie odcitavaju cislo od registra
HL, preto je tento register vyhodny na uchovavanie hodnoty
delenca. Od registra HL sa daju odcitavat registre BC,DE,HL a
SP. Registre SP a HL nepripadaju do uvahy, lebo SP je ukazovatel
zasobnika a je potrebny pri zasobnikovych operaciach a keby sme
od HL odcitali HL, potom by nam vysla nula a stratili by sme
delenec.
Zvolme si teda, ze delitel bude v registri DE. Register BC
pouzijeme ako pocitadlo odcitani - aby sme vedeli, ze kolkokrat
sa nam podarilo odcitat delitela od delenca.
Samotne odcitanie budeme robit instrukciou SBC HL,DE. Lenze
tato instrukcia okrem odcitania DE od HL este odcita od HL aj
hodnotu priznaku CARRY. Preto musime tento priznak pred
odcitanim vynulovat. Ako uz vieme, CARRY nuluju vsetky logicke
instrukcie. Vyberme spomedzi nich taku, ktora ma co najmenej
vedlajsich efektov - takou je napr. OR A alebo AND A.
No a mozme sa venovat nasej rutinke. Na zaciatku vynulujeme
pocitadlo odcitani - register BC. Instrukcia SBC HL,DE nam
nastavi CARRY prave vtedy, ak to s tymi odcitaniami
"presvihneme" - prekrocime nulu a dostaneme sme sa do zapornych
hodnot. Ked CARRY nie je nastavene, znamena to ze este sme
neprekrocili nulu a mozeme sa znovu pokusit o dalsie odcitanie.
Na konci este musime spravit istu korekciu - poslednym odcitanim
sme sa dostali do oblasti zapornych hodnot, preto toto posledne
odcitanie uz nemozeme ratat do vysledku (napravime to
instrukciou DEC BC) a do delenca musime vratit hodnotu, ktora
bola pred tymto posledny odcitanim. Najlepsie to urobime tak, ze
tu istu hodnotu, co sme "navyse" odcitali zase znovu pricitame,
a tym vlastne dostavame okrem celociselnej casti podielu v
registri BC aj v zvysok po deleni v registri HL.
Zhrnutie: Nasa rutinka (nazvime ju "lomeno") ma na vstupe
delenec v registri HL, delitel v DE a po jej vykonani je v BC
celociselna cast vysledku a v HL zvysok po deleni.
Nase "lomeno" bude teda vyzerat takto:
lomeno | ld | bc,#00 | inicializacia pocitadla na nulu |
lom1 | or | a | vynulovanie CARRY [pre istotu] |
sbc | hl,de | odcitanie delitela od delenca | |
inc | bc | po kazdom odcitani zvysime poc. | |
jr | nc,lom1 | opakujeme pokym to nie je zaporne | |
add | hl,de | oprava prekrocenia nuly | |
dec | bc | oprava hodnoty vysledku | |
ret | konec |
A teraz sa venujme optimalizacii vzhladom na dlzku a cas
vykonavania rutinky. Ked sa nad tou rutinkou zamyslime, zistime
ze vysledky sa vobec nezmenia, ak opravu hodnoty vysledku
spravime na zaciatku rutinky (hned po instrukcii LD BC,0). Lenze
postupnost instrukii LD BC,0 a DEC BC mozeme nahradit jednou
instrukciou LD BC,-1 (co je presne to iste ako LD BC,65535).
Este si vsimnime, ze ked po odcitani skaceme na navestie "lom1"
tak tam skaceme prave vtedy, ked CARRY je nulove. Cize namiesto
na instrukciu OR A by sme mohli skakat az na SBC HL,DE. Tymto sa
nam vykonavanie rutinky trochu zrychli. To OR A je tam potrebne,
lebo ak ideme vykonat prve odcitane, nevieme ako je CARRY
nastavene.
Pokial nepotrebujeme poznat zvysok po deleni, mozeme opravu
prekrocenia nuly (robenu istukciou ADD HL,DE) kludne vynechat,
lebo na samotny podiel v registri BC nema ziadny vplyv.
No a po tychto troch upravach sa nam nasa rutinka zmensila do
takejto podoby:
lomeno | ld | bc,#ffff | inicializacia pocitadla na -1 |
or | a | zaciatocne vynulovanie CARRY | |
lom1 | sbc | hl,de | odcitanie delitela od delenca |
inc | bc | po odcitani zvysime pocitadlo | |
jr | nc,lom1 | opakujeme pokym to nie je zaporne | |
ret | koniec |
Jedna otazka na zamyslenie, ktorej odpoved necham na vas: Co by
sa stalo, keby sme zavolali tuto rutinku s nulovym delitelom ?
Tentoraz som si pre vas prichystal domacu ulohu, aku ste este
nemali. Doteraz ste alebo riesili daky problem alebo ste mali
navrhnut nejaku rutinku. Avsak tentoraz mate ulohu presne
obratenu - predkladam vam uz hotovu rutinku a vasou ulohou je
vysvetlit, ako pracuje a ake konkretne cinnosti v nej vykonavaju
jednotlive instrukcie. Prezradim vam len tolko, ze tato rutinka
na vstupe ocakava nejake cislo v registri HL a na vystupe je v
registri A druha odmocnina tohto cisla.
sqr | ld | de,1 |
xor | a | |
dec | a | |
loop | sbc | hl,de |
inc | de | |
inc | de | |
inc | a | |
jr | nc,loop | |
ret |
Vas Busy.