10進表示
目次
BCD命令
2014.9.1 追記
わー、大作だぁ…。
BCD 命令を使った、と投稿されてきたプログラムです。
LD BC,8086 ; 10 (11)
LD DE,NTABLE ; 10 (11)
LD HL,DECIWK ; 10 (11)
DIV2LOOP:
SRL B ; 8 (10)
RR C ; 8 (10)
JR NC,SKIP ; 12 / 7 (13 / 8)
LD A,(DE) ; 7 (8)
ADD A,(HL) ; 7 (8)
DAA ; 4 (5)
LD (HL),A ; 7 (8)
INC E ; 4 (5)
INC L ; 4 (5)
LD A,(DE) ; 7 (8)
ADC A,(HL) ; 7 (8)
DAA ; 4 (5)
LD (HL),A ; 7 (8)
INC E ; 4 (5)
INC L ; 4 (5)
LD A,(DE) ; 7 (8)
ADC A,(HL) ; 7 (8)
LD (HL),A ; 7 (8)
INC E ; 4 (5)
DEC L ; 4 (5)
DEC L ; 4 (5)
JP DIV2LOOP ; 10 (11)
SKIP:
INC E ; 4 (5)
INC E ; 4 (5)
INC E ; 4 (5)
LD A,B ; 4 (5)
OR C ; 4 (5)
JP NZ,DIV2LOOP ; 12 / 7 (13 / 8)
PACK2ZONE:
LD DE,DECIMAL ; 10 (11)
INC L ; 4 (5)
INC L ; 4 (5)
LD B,30H ; 7 (8)
LD A,(HL) ; 7 (8)
OR A ; 4 (5)
JR NZ,ST5 ; 12 / 7 (13 / 8)
DEC L ; 4 (5)
RLD ; 18 (20)
JR NZ,ST4 ; 12 / 7 (13 / 8)
RLD ; 18 (20)
JR NZ,ST3 ; 12 / 7 (13 / 8)
DEC L ; 4 (5)
RLD ; 18 (20)
JR NZ,ST2 ; 12 / 7 (13 / 8)
RLD ; 18 (20)
OR B ; 4 (5)
JP ST1 ; 10 (11)
ST5:
OR B ; 4 (5)
LD (DE),A ; 7 (8)
INC E ; 4 (5)
DEC L ; 4 (5)
RLD ; 18 (20)
ST4:
OR B ; 4 (5)
LD (DE),A ; 7 (8)
INC E ; 4 (5)
RLD ; 18 (20)
ST3:
OR B ; 4 (5)
LD (DE),A ; 7 (8)
INC E ; 4 (5)
DEC L ; 4 (5)
RLD ; 18 (20)
ST2:
OR B ; 4 (5)
LD (DE),A ; 7 (8)
INC E ; 4 (5)
RLD ; 18 (20)
ST1:
LD (DE),A ; 7 (8)
INC E ; 4 (5)
XOR A ; 4 (5)
LD (DE),A ; 7 (8)
END:
NTABLE:
DB 01H,00H,00H
DB 02H,00H,00H
DB 04H,00H,00H
DB 08H,00H,00H
DB 16H,00H,00H
DB 32H,00H,00H
DB 64H,00H,00H
DB 28H,01H,00H
DB 56H,02H,00H
DB 12H,05H,00H
DB 24H,10H,00H
DB 48H,20H,00H
DB 96H,40H,00H
DB 92H,81H,00H
DB 84H,63H,01H
DB 68H,27H,03H
DECIWK:
DB 00H,00H,00H;
DECIMAL:
DB 00H,00H,00H,00H,00H,00H;
BCD 命令と言うのは、2進化10進、と呼ばれる方法で足し算・引き算を行う際に補助的に使用する命令です。
2進化10進については、詳しくは過去に書いた日記を見てね。
まぁ、16進数なのに、A~F を使わないで 10進数を表記したものだと思ってください。
で、BCD を使うことで 10進変換なんて出来たの!?
と思い詳細に読んでみると…あーなるほど。驚きの超絶技巧、と言うわけではないのですが、巧妙な高速化が行われています。
プログラムは大きく前半と後半に別れます。
まず、前半から解説。
NTABLE の中身は、16bit の「重み」を、あらかじめ BCD で表記したものです。
1 ~ 32768 までで、最大5桁ですが、1バイトの BCD では10進数2桁を表現できるため、3バイト区切りで並んでいます。(下位の桁から並んでいます)
まずは10進数にする 16bit データを、1bit づつビットの有無を判定し、ビットがある場合はその「重み」を、BCD で足し合わせます。
テーブルは3バイト区切りですから、足す際には3バイトづつの処理が必要です。
16bit 分を処理すると、BCD 化された数値が出来上がります。
この時点では、4bit で1桁を表現する BCD が、1バイトに2桁分入れられた「パックド BCD」の状態です。
ここから後半。
4bit づつ取り出して、上位 4bit に 03H を付けることで、数値を意味するアスキーコードに変換します。
つまり、目的とする「ゾーン BCD」の状態。
この際、RLD 命令が使用されています。
これも実は BCD を扱うことを考慮して作られた命令で、A レジスタの下位 4bit と (HL) のメモリ内容の間で、データがパックド BCD である前提で 10進数の桁ずらし(ローテート)を行います。
RLD では、Aレジスタの上位 4bit には影響を与えません。
でも、プログラムでは、最後の桁以外は常に上位 4bit に 3 を書き込んでいます。
これは、ループ展開して高速化している代償。大きな高速化のために、ちょっと損してる。
(この部分をうまく処理できると、もっと高速になるかも)
速度の遅い DIV ルーチンは使われていません。
割り算は遅いから逆数を掛ける人が出るかと思って、MUL ルーチンも存在することにしていたけど、それすら不要。
投稿プログラムでは、テーブルの参照アドレスを 16bit 処理している部分と、下位8bit だけで処理している部分がありました。
ここでは、「テーブルは 256バイトの境界をまたがない」ことにして、下位8bit 処理だけに統一しています。
前処理に 33 クロック。
前半処理は、加算が生じる場合 156 クロック。加算が無いと 66クロック。
元データとなる 8086 は、2進数で 0001 1111 1001 0110 。
一番左の 1 の左の桁を処理した際に終わるプログラムになっていますから、14 回処理を繰り返します。
処理される下位 14bit のうち、 1 は 9個、0 は 5個あるので、加算有りが 9回、無しが 5回です。
最後は 5クロック速くなるので、前半処理にかかる時間は
156*9 + 66*5 - 5 = 1729 クロック
後半処理は、前処理として左から「0ではない桁」を見つけ出し、あらかじめ展開されているループの途中にとびこむことで左詰めで文字列を作り出します。
えーと、前処理と本処理でいろいろややこしい関係になるので、ひたすら計算してみると、
| 5桁 | 243クロック |
| 4桁 | 233クロック |
| 3桁 | 223クロック |
| 2桁 | 213クロック |
| 1桁 | 206クロック |
これが必要なクロック数です。
1桁の時だけちょっと特別だけど、それ以降は1桁増えるとクロック数が 10 増えます。
例題では4桁なので、後半は 233クロック。
全部を足すと、
33 + 1729 + 233 = 1995
総計は、1995 クロックとなります。