yasuokaの日記: Z80における定数10の除算は、商と余りのどちらを先に求めるべきか 5
ここ数日の日記(これとこれとこれ)で、Z80で100未満の数値を10で除算処理するというのに挑戦してみた。Bレジスタに0~99の整数が入っている時に、10で除算した商をHレジスタに、余りをAレジスタに返す、という問題なのだが、ここまでに私(安岡孝一)が検討した結果を、とりあえず、まとめておこうと思う。まずは、商を51/512で求めて、その後に余りを求める手法。
78 LD A,B
0F RRCA
0F RRCA
0F RRCA
0F RRCA
E6 0F AND 0FH
3C INC A
80 ADD B
4F LD C,A
81 ADD C
81 ADD C
17 RLA
17 RLA
17 RLA
17 RLA
E6 0F AND 0FH
67 LD H,A
87 ADD A
87 ADD A
84 ADD H
2F CPL
07 RLCA
88 ADC B
25バイト98ステート、といったところだろうか。私個人としては、まずまずの速さだと思う。一方、余りをDAAで先に求めて、その後に商を求める方法。
78 LD A,B
0F RRCA
0F RRCA
0F RRCA
27 DAA
87 ADD A
27 DAA
4F LD C,A
78 LD A,B
E6 07 AND 07H
91 SUB C
27 DAA
E6 0F AND 0FH
4F LD C,A
78 LD A,B
91 SUB C
67 LD H,A
0F RRCA
94 SUB H
94 SUB H
E6 0F AND 0FH
67 LD H,A
79 LD A,C
26バイト101ステート、といったところだろうか。計算間違いでなければ、商を先に求める方が、ほんの少し早い気がする。ただ、この程度の差だと、ほんのひとチューニングで簡単にひっくり返る。何か、いいアイデア、ないかなぁ。
前半 (スコア:1)
>商を51/512で求めて
7行目 INC A の分、3/32 が暗黙のうちに加算されていますね?
# そして後半を読み解く元気はない…
51/512≒1/10 (スコア:2)
後半の前半 (スコア:1)
0~99の数値Aの10での余りは、Aを8での商と余りの
二つにわけて、各々の10での余りを加算すればいい
A =(A/8)*8 + (A%8)
A%10=((A/8)*8)%10 + (A%8)%10
(この加算で10を越えたらもう一度余りを…)
と
DAAでは(ざっくり)0~15の連続数が0~9、16~21に
分断される、これを使って割る10をうまいこと振り分けて・・・?
を
組み合わせて 余りを求めているのではないか? までわかった、つもり。
で、
(A/8)を足してはDAA、足してはDAAで8回足し、
最後に(A%8)を足してDAAすれば、
Aに二進化10進で商と余りがペアで入る、
(56→0x56になる)ような気がする、
けど極端に短くはなりませんでした。
(商と余りの分割もクロック喰うし)
というところで勘弁してください。
p.s.
INC A ... は二つ前の日記で触れてありましたね。
DAAで余りを先に求めるバージョン (スコア:2)
Re:後半の前半 (スコア:1)
p.s.
足してはDAA足してはDAA…は一つ前の日記に書いてありましたね。
(他の考察も)
-*-*-
DAA は鬼門なんですよ…。
以下、私が今をさること25年ぐらい前に書いた
Z80シミュレータの DAA の部分です。(たぶん間違っている)
int ans, half_ans, dst;
u_char data;
case 0x27:
#ifdef DEBUG
fprintf(stdout, "DAA ");
#endif /* DEBUG */
data = 0x00;
if(((reg.sing.A & 0xf) > 9) || (reg.flag.H==1)) {
data = 0x06;
}
if((reg.sing.A > 0x90) || (reg.flag.C==1)) {
data |= 0x60;
}
if(reg.flag.N==0) {
half_ans = (reg.sing.A & 0x0f) + (data & 0x0f);
reg.sing.A = ans = reg.sing.A + data;
} else {
half_ans = (reg.sing.A & 0x0f) - (data & 0x0f);
reg.sing.A = ans = reg.sing.A - data;
}
reg.flag.H = half_ans >> 4;
reg.flag.C |= ans >> 8; /* is it true ??? */
reg.flag.S = reg.sing.A >> 7;
reg.flag.Z = (reg.sing.A==0);
reg.flag.P = parity(reg.sing.A);
return(D_OK);
本や解説書には「二進化10進の補正をします」ぐらいしか書いてなくて、
想像と実機でちょろっと確認したぐらいで、書いたものです。
加算を一度しかやっていないので、0x89 + 0x11 → DAA が
0x00 にならないからたぶん間違っているんじゃないかなぁ…。