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,de6. 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]