Na tisk 16 bitoveho cisla se da pouzit i rom cast pro kalkulacku. Pokud se nepletu tak stejne si to v basicu uklada jako real.
ZX Spectrum floating-point format: EEEE EEEE SMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM exp, sign + m, m, m, m ZX Spectrum integer format: 0000 0000 SSSS SSSS LLLL LLLL HHHH HHHH 0000 0000 0, 8x dup S, lo, hi, 0
Takze staci integer spravne nacpat na vrchol kalkulacky a zavolat rutinu pro tisk.
V M4 Forthu kdyz to chci tisknou pres rom to pak vypada takto (v rutine je navic na konci nejaka prace, aby se cislo odstranilo z TOS (HL) a nacetlo se do nej NOS (DE)).
dworkin@dw-A15:~/Programovani/ZX/Forth/M4$ ../check_word.sh 'PUSH(1234) DOTZXROM' push DE ; 1:11 1234 ex DE, HL ; 1:4 1234 ld HL, 1234 ; 3:10 1234 call ZXPRT_S16 ; 3:17 .zxrom ( x -- ) ;------------------------------------------------------------------------------ ; Input: HL ; Output: Print signed decimal number in HL ; Pollutes: AF, BC, HL <- DE, DE <- (SP) ZXPRT_S16: ; zxprt_s16 ( x -- ) push DE ; 1:11 zxprt_s16 ld A, H ; 1:4 zxprt_s16 add A, A ; 1:4 zxprt_s16 sbc A, A ; 1:4 zxprt_s16 sign ld E, A ; 1:4 zxprt_s16 2. byte sign xor A ; 1:4 zxprt_s16 1. byte = 0 ld D, L ; 1:4 zxprt_s16 3. byte lo ld C, H ; 1:4 zxprt_s16 4. byte hi ld B, A ; 1:4 zxprt_s16 5. byte = 0 ld IY, 0x5C3A ; 4:14 zxprt_s16 Re-initialise IY to ERR-NR. call 0x2AB6 ; 3:17 zxprt_s16 call ZX ROM STK store routine call 0x2DE3 ; 3:17 zxprt_s16 call ZX ROM print a floating-point number routine pop HL ; 1:10 zxprt_s16 pop BC ; 1:10 zxprt_s16 load ret pop DE ; 1:10 zxprt_s16 push BC ; 1:11 zxprt_s16 save ret ret ; 1:10 zxprt_u16 ; seconds: 0 ;[32:184]
Stale je to kratsi nez psat vlastni rutinu.
Pokud by to byl unsigned integer tak je to jeste kratsi
dworkin@dw-A15:~/Programovani/ZX/Forth/M4$ ../check_word.sh 'PUSH(1234) UDOTZXROM' push DE ; 1:11 1234 ex DE, HL ; 1:4 1234 ld HL, 1234 ; 3:10 1234 call ZXPRT_U16 ; 3:17 u.zxrom ( u -- ) ;------------------------------------------------------------------------------ ; Input: HL ; Output: Print unsigned decimal number in HL ; Pollutes: AF, BC, HL <- DE, DE <- (SP) ZXPRT_U16: ; zxprt_u16 ( u -- ) push DE ; 1:11 zxprt_u16 ld B, H ; 1:4 zxprt_u16 ld C, L ; 1:4 zxprt_u16 call 0x2D2B ; 3:17 zxprt_u16 call ZX ROM stack BC routine call 0x2DE3 ; 3:17 zxprt_u16 call ZX ROM print a floating-point number routine pop HL ; 1:10 zxprt_u16 pop BC ; 1:10 zxprt_u16 load ret pop DE ; 1:10 zxprt_u16 push BC ; 1:11 zxprt_u16 save ret ret ; 1:10 zxprt_u16 ; seconds: 0 ;[22:146]
I po těch letech, kdy jsem na osmibitech dělal, mě vždycky znovu překvapí, jak jsou ty programy a podprogramy krátké. Třeba ten tisk čísla vypadá, že je v ROM refaktorován tak, že jeho části se používají i jinde.
Jasně, ROM ZX Spectra (ale ani ROM Atárka atd.) toho neumí z dnešního pohledu moc, ale tehdy to na mnoho věcí stačílo (BASIC byl takovej shell) a mělo to doslova pár kilobajtů.
Před třiceti lety jsem si dal práci s nalezením všech vzájemných odkazů mezi rutinami v ZX Spectru.
Tak například podprogram OUT-NUM-1 #1A1B je z jiných míst ROM volán celkem třikrát - z adres #1354, #135F, #2F88. Třeba se to některým infoarcheologům bude hodit: https://vitsoft.info/zxref.htm.
A nejlepší na tom bylo, že člověk přesně věděl jak dlouho každá instrukce trvá, takže se dalo dopředu spočítat časování kritických úloh, jako třeba zápis a čtení dat z magnetofonu, vysílání a příjem morseovky nebo ovládání sériového portu.
Kdyz se tady mluvi o zajimavych algoritmech pro Z80, tak taky prispeju se svou troskou do mlyna. Tohle je muj algoritmus pro deleni. Deli se HL / C, vysledek je v HL, zbytek je v A. To jsem jednou na kolejich slysel, jak se dva kolegove bavi o rutine na deleni. Ze je to komplikovane. Poslal jsem je nekam, rikal jsem, ze se to musi vejit do 10 bajtu. Neverili. Tak jsem sedl ke compu a napsal jsem tohle:
; unsigned division hl/c -> hl, hl%c -> a div_hl_c: ld b,0x10 l1: add hl,hl rla jr c, l2 cp c jr c, l3 l2: sub c inc l l3: djnz l1 ret
No dobre, neni to 10 bajtu, je to 14, ale je to mene, nez 10 hexadecimalne :-).
Snad je to dobre, vytahl jsem to ze 20 let stareho mailu, kde jsem to nekomu posilal s poznamkou, ze je to 15 let, co jsem tento algoritmus naposledy pouzil :-).
Tato rutina je jednoduse pouzitelna i pro prevod cisel do ascii. Staci naplnit C=10 a volat tuto rutinu opakovane, dokud v HL neni nula. Po kazdem zavolani staci pricist 0x30 k A a zobrazit. Cislo ovsem vyleze opacne - napred jednotky, pak desitky a tak dale. S tim je potreba pocitat. Samozrejme to funguje jenom pro unsigned 16 bitova cisla.
Me to stale prijde slozite, je to jako hrat sachy a myslet si ze pozice je snadna a prijit o kralovnu.
Vyzkousej si dat do A treba 0xFF nebo 0x01 a delit 30336/127. Vidis to na prvni pohled? Ja teda ne.
I kdyz ten algoritmus muze byt jednoduchy (pujdes po tehle vyslapane cesticce v minovme poli) stale pochopit proc jdes tudy muze byt slozite (i snadne zaroven).
A pravdepodobne pujde spis o rychlost a to se pak teprve zacne komplikovat... Protoze pro kratky kod staci pouhe odecitani, a kdyz vidis jak je to kratke tak si reknes: jeee 8 bajtu super, nez zjistit ze se to jen odecita a pak jen: argh.. to nechci.
Div_HL_DE: ;Inputs: ; HL / DE ;Outputs: ; A is the quotient (HL/DE) ; HL is the remainder xor a dec a _loop: inc a sbc hl,de jr nc,_loop add hl,de
Mozna spis 10 bajtu, kdyz to zmenime na
Div_HL_DE: ;Inputs: ; HL / DE ;Outputs: ; A is the quotient (HL/DE) ; HL is the remainder xor a ld bc,-1 _loop: inc bc sbc hl,de jr nc,_loop add hl,de
6. 4. 2023, 19:24 editováno autorem komentáře
Ano, cokoliv co neni proste odecitani je prakticky lepsi. Ta metoda co ma smycku opakujici se 16x ma konstantni rychlost a je jen o par bajtu delsi nez pouhe odecitani. Odecitani bude rychlejsi jen pro nejmensi vysledky.
Vlastne jsem se nezminil, ze existuji varianty kdy delenec je kladny. To dokaze usnadnit/zkratit/zrychlit kod, kdyz mas jistotu ze nejvyssi bit bude nula. Da se to pouzit kdyz delite znamenkove a pred volanim deleni si to upravite na absolutni hodnoty.
Odectani ale pouzivam u vypisu cisla v desitkove soustave. Je to kratke, a pro kazdy rad se to provede ne vic jak 10x+1. Opakovane deleni pro kazdy rad by bylo zavisle na te metode deleni (slozitejsi smycka 16x) a navic i delsi.
dworkin@dw-A15:~/Programovani/ZX/Forth/Pasmo_test$ ../check_word.sh 'UDOT' call PRT_U16 ; 3:17 u. ( u -- ) ;------------------------------------------------------------------------------ ; Input: HL ; Output: Print unsigned decimal number in HL ; Pollutes: AF, BC, HL <- DE, DE <- (SP) PRT_U16: ; prt_u16 xor A ; 1:4 prt_u16 HL=103 & A=0 => 103, HL = 103 & A='0' => 00103 ld BC, -10000 ; 3:10 prt_u16 call BIN16_DEC ; 3:17 prt_u16 ld BC, -1000 ; 3:10 prt_u16 call BIN16_DEC ; 3:17 prt_u16 ld BC, -100 ; 3:10 prt_u16 call BIN16_DEC ; 3:17 prt_u16 ld C, -10 ; 2:7 prt_u16 call BIN16_DEC ; 3:17 prt_u16 ld A, L ; 1:4 prt_u16 pop HL ; 1:10 prt_u16 load ret ex (SP),HL ; 1:19 prt_u16 ex DE, HL ; 1:4 prt_u16 jr BIN16_DEC_CHAR ; 2:12 prt_u16 ;------------------------------------------------------------------------------ ; Input: A = 0 or A = '0' = 0x30 = 48, HL, IX, BC, DE ; Output: if ((HL/(-BC) > 0) || (A >= '0')) print number -HL/BC ; Pollutes: AF, HL inc A ; 1:4 bin16_dec BIN16_DEC: ; bin16_dec add HL, BC ; 1:11 bin16_dec jr c, $-2 ; 2:7/12 bin16_dec sbc HL, BC ; 2:15 bin16_dec or A ; 1:4 bin16_dec ret z ; 1:5/11 bin16_dec does not print leading zeros BIN16_DEC_CHAR: ; bin16_dec or '0' ; 2:7 bin16_dec 1..9 --> '1'..'9', unchanged '0'..'9' rst 0x10 ; 1:11 bin16_dec putchar(reg A) with ZX 48K ROM ld A, '0' ; 2:7 bin16_dec reset A to '0' ret ; 1:10 bin16_dec ; seconds: 0 ;[47:256]
Jinak u toho deleni vidim dve zakladni cesty. Podle toho na jakou stranu budes posouvat registrovy par. Jedna dela kratkou a rychlu smycku, ale musis to udelat pokazde 16x (mluvim o 16 bitove deleni). Druhy zpusob se musi inicializovat a ma slozitejsi kod, a je dokonce pomalejsi pro deleni nahodnych cisel, ale dokaze ukoncit vypocet drive, takze pro deleni kde vysledek nema tolik bitu je to rychlejsi.
Pokud pises prekladac a nemas tuseni na co se to bude pouzivat (nemas zadny vzorek co tam poleze) tak si muzes fakt vybrat... .) Tak napises vic verzi a, ktera se vlozi do runtime zavisi na parametru.
Pokud ti jde o rychlost tak oba zpusoby se navic daji napsat o to sloziteji.
dworkin@dw-A15:~/Programovani/ZX/Forth/Pasmo_test$ ../check_word.sh 'UDIV' call UDIVIDE ; 3:17 u/ pop DE ; 1:10 u/ ;============================================================================== ; Divide 16-bit unsigned values (with 16-bit result) ; In: DE / HL ; Out: HL = DE / HL, DE = DE % HL UDIVIDE: ;[51:cca 900] # default version can be changed with "define({TYPDIV},{name})", name=old_fast,old,fast,small,synthesis ; /3 --> cca 1551, /5 --> cca 1466, 7/ --> cca 1414, /15 --> cca 1290, /17 --> cca 1262, /31 --> cca 1172, /51 --> cca 1098, /63 --> cca 1058, /85 --> cca 1014, /255 --> cca 834 ld A, H ; 1:4 or L ; 1:4 HL = DE / HL ret z ; 1:5/11 HL = DE / 0? ld BC, 0x0000 ; 3:10 if 0 UDIVIDE_LE: inc B ; 1:4 B++ add HL, HL ; 1:11 jr c, UDIVIDE_GT ; 2:7/12 ld A, H ; 1:4 sub D ; 1:4 jp c, UDIVIDE_LE ; 3:10 jp nz, UDIVIDE_GT ; 3:10 ld A, E ; 1:4 sub L ; 1:4 else ld A, D ; 1:4 UDIVIDE_LE: inc B ; 1:4 B++ add HL, HL ; 1:11 jr c, UDIVIDE_GT ; 2:7/12 cp H ; 1:4 endif jp nc, UDIVIDE_LE ; 3:10 or A ; 1:4 HL > DE UDIVIDE_GT: ; ex DE, HL ; 1:4 HL = HL / DE ld A, C ; 1:4 CA = 0x0000 = result UDIVIDE_LOOP: rr D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back ccf ; 1:4 inverts carry flag adc A, A ; 1:4 rl C ; 2:8 djnz UDIVIDE_LOOP ; 2:8/13 B-- ex DE, HL ; 1:4 ld L, A ; 1:4 ld H, C ; 1:4 ret ; 1:10 ; seconds: 0 ;[55:255]
dworkin@dw-A15:~/Programovani/ZX/Forth/Pasmo_test$ ../check_word.sh 'define({TYPDIV},small) UDIV' call UDIVIDE ; 3:17 u/ pop DE ; 1:10 u/ ;============================================================================== ; Divide 16-bit unsigned values (with 16-bit result) ; In: DE / HL ; Out: HL = DE / HL, DE = DE % HL UDIVIDE: ld A, H ; 1:4 small version or L ; 1:4 HL = DE / HL ret z ; 1:5/11 HL = DE / 0 ld BC, 0x0000 ; 3:10 ld A, D ; 1:4 UDIVIDE_LE: inc B ; 1:4 B++ add HL, HL ; 1:11 jr c, UDIVIDE_GT ; 2:7/12 cp H ; 1:4 jp nc, UDIVIDE_LE ; 3:10 or A ; 1:4 HL > DE UDIVIDE_GT: ; ex DE, HL ; 1:4 HL = HL / DE ld A, C ; 1:4 CA = 0x0000 = result UDIVIDE_LOOP: rr D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back ccf ; 1:4 inverts carry flag adc A, A ; 1:4 rl C ; 2:8 djnz UDIVIDE_LOOP ; 2:8/13 B-- ex DE, HL ; 1:4 ld L, A ; 1:4 ld H, C ; 1:4 ret ; 1:10 ; seconds: 0 ;[41:197]
A ted to prijde, snadnost deleni
dworkin@dw-A15:~/Programovani/ZX/Forth/Pasmo_test$ ../check_word.sh 'define({TYPDIV},fast) UDIV' call UDIVIDE ; 3:17 u/ pop DE ; 1:10 u/ ;============================================================================== ; Divide 16-bit unsigned values (with 16-bit result) ; In: DE / HL ; Out: HL = DE / HL, DE = DE % HL UDIVIDE: ld A, H ; 1:4 fast version or L ; 1:4 HL = DE / HL ret z ; 1:5/11 HL = DE / 0 ld BC, 0x00FF ; 3:10 if 1 ld A, D ; 1:4 UDIVIDE_LE: inc B ; 1:4 B++ add HL, HL ; 1:11 jr c, UDIVIDE_GT ; 2:7/12 cp H ; 1:4 jp nc, UDIVIDE_LE ; 3:10 or A ; 1:4 HL > DE else UDIVIDE_LE: inc B ; 1:4 B++ add HL, HL ; 1:11 jr c, UDIVIDE_GT ; 2:7/12 ld A, H ; 1:4 sub D ; 1:4 jp c, UDIVIDE_LE ; 3:10 jp nz, UDIVIDE_GT ; 3:10 ld A, E ; 1:4 sub L ; 1:4 jp nc, UDIVIDE_LE ; 3:10 or A ; 1:4 HL > DE endif UDIVIDE_GT: ; ex DE, HL ; 1:4 HL = HL / DE ld A, C ; 1:4 CA = 0xFFFF = inverted result ;1 rr D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;2 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;3 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;4 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;5 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;6 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;7 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 ;8 srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 dec B ; 1:4 B-- jr z, UDIVIDE_END; 2:7/12 UDIVIDE_LOOP: srl D ; 2:8 rr E ; 2:8 DE >> 1 sbc HL, DE ; 2:15 jr nc, $+3 ; 2:7/12 add HL, DE ; 1:11 back adc A, A ; 1:4 rl C ; 2:8 djnz UDIVIDE_LOOP ; 2:8/13 B-- ex DE, HL ; 1:4 cpl ; 1:4 ld L, A ; 1:4 ld A, C ; 1:4 cpl ; 1:4 ld H, A ; 1:4 ret ; 1:10 UDIVIDE_END: ex DE, HL ; 1:4 cpl ; 1:4 ld L, A ; 1:4 ld H, B ; 1:4 ret ; 1:10 ; seconds: 0 ;[170:815]
Nebudu ukazovat radsi vsechny verze.
Zajimava cast je kdyz vite cim budete delit. Delite konstantou. A nebo vite cim budete nasobit.
U nasobeni je to relativne snadne. Existuje jedna metoda ktera je v cca 98% pripadu nejlepsi. Ale u deleni je to magie pro kazdou konstantu, kde zkousite nekolik cest (zakladni metoda je ze to proste nasobite 2^(8 nebo 16 nebo ...) / constanta) a muste si vse overovat, ze do toho nasypete vsechny mozne vstupy, abyste meli jistotu.
dworkin@dw-A15:~/Programovani/ZX/Forth/Pasmo_test$ ../check_word.sh '_10UDIV' ;[36:209] 10u/ Variant HL/10 = HL*(2*65536/2*65536)/10 = HL*(2*65536/10)/(2*65536) = HL*13107/(2*65536) = HL*51*257/(2*65536) ; 10u/ = HL*b_0011_0011*(1+1/256) >> (1+8) ld B, H ; 1:4 10u/ ld C, L ; 1:4 10u/ 1 1x = base xor A ; 1:4 10u/ add HL, HL ; 1:11 10u/ 1 adc A, A ; 1:4 10u/ *2 AHL = 2x add HL, BC ; 1:11 10u/ adc A, 0x00 ; 2:7 10u/ +1 AHL = 3x add HL, HL ; 1:11 10u/ 0 adc A, A ; 1:4 10u/ *2 AHL = 6x add HL, HL ; 1:11 10u/ 0 adc A, A ; 1:4 10u/ *2 AHL = 12x add HL, HL ; 1:11 10u/ 1 adc A, A ; 1:4 10u/ *2 AHL = 24x add HL, BC ; 1:11 10u/ adc A, 0x00 ; 2:7 10u/ +1 AHL = 25x add HL, HL ; 1:11 10u/ 1 adc A, A ; 1:4 10u/ *2 AHL = 50x add HL, BC ; 1:11 10u/ ld BC, 0x0033 ; 3:10 10u/ rounding down constant adc A, B ; 1:4 10u/ +1 AHL = 51x add HL, BC ; 1:11 10u/ adc A, B ; 1:4 10u/ +0 AHL = 51x with rounding down constant ld B, A ; 1:4 10u/ (AHL * 257) >> 16 = (AHL0 + 0AHL) >> 16 = AH.L0 + A.HL = A0 + H.L + A.H ld C, H ; 1:4 10u/ _BC = "A.H" 51x/256 = 0.19921875x add HL, BC ; 1:11 10u/ _HL = "H.L" + "A.H" = 51.19921875x adc A, 0x00 ; 2:7 10u/ AHL = 51.19921875x rra ; 1:4 10u/ rr H ; 2:8 10u/ ld L, H ; 1:4 10u/ ld H, A ; 1:4 10u/ HL = HL/10 = (HL*51*257)>>17 = (HL*51.19921875)>>8 = HL*0.099998474 ; seconds: 0 ;[36:209]