x86命令には「BCD補正命令」なる一族あり、その中でもパックドBCD数を扱うDAA命令は御先祖の8ビット機(8080/8085)でも存在した伝統の命令です。しかしx86ではアンパックドBCD数を扱うための頭文字にAを頂く命令どもが追加されております。強力?でも今となっては盲腸みたいなもん?どうなん?
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認には以下を使用させていただいております。
BCD(Binary Coded Decimal)
通常2進数であれば、4ビット幅を一杯につかって10進数の0から15までの数字を表すことが可能です。しかしこれを10進数の0から9を表現する表記のみを有効とすれば、2進4ビット=10進1桁として表現することができます。BCDでありますな。これが効いてくるのは、ピタリ10進で計算したい用途です。なにせ2進と10進は分かり合えない部分あり、0.1は10進じゃピタリ賞ですが、2進では正確に表すこと不可能。よって0.1%の利率の複利計算などすると、何もテクを使わなければ2進と10進の結果は一致しませぬ。
遥かな古代、電卓というものが登場したときにもこの2進、10進の問題があり、基本電卓というものは10進で計算すべしという縛りがかかったみたいです。思い起こせば86系はその先祖である8ビット機、そしてそのまた先祖である4ビット機にルーツを持っており、まさに4ビット機は「電卓」のために作られたものでありました。よって86系は累代、BCD演算というものに貴重なファーストオペコード空間の一部を割り当ててきたわけであります。今となってはどれどほどの意味があるのかは問いませぬが。
さてBCDにも以下のお作法あり。
-
- パックド、4ビット幅を10進1桁に割り当てる。8ビット(バイト)に10進2桁を記憶させる
- アンパックド、1バイト(8ビット)の下4ビットに10進1桁を記憶させる。上の4ビットは0詰めで残しておく。
今回練習してみますのは、2のアンパックドBCD数を扱うのに「便利」なものどもです。御先祖の8ビット機にはなく、8086に至って登場した命令群です。
AAA一族
さて、今回練習する命令は以下の4命令です。
-
- AAA 、ASCII ADJUST FOR ADDITION
- AAD、ASCII ADJUST FOR DIVISION
- AAM、ASCII ADJUST FOR MULTIPLY
- AAS、ASCII ADJUST FOR SUBTRACTION
加減乗除、ひととおりそろってます。基本全ての命令はALレジスタにアンパックドの10進数が入っている前提で、ALレジスタとの通常の(2進の)バイト演算命令を行ったときに結果がアンパックドな10進数になるようにサポートする命令です。また、AHレジスタには10進の上位桁(割り算の場合のみ余り)が入ることになってます。
ただしその使い方はちょっと凸凹ありです。
-
- AAA、2進加算命令を実行した後でアンパックド10進になるように補正
- AAD、2進除算命令の結果がアンパックド10進で得られるように除算前に先回りで「補正」しておく
- AAM、2進乗算命令を実行した後でアンパックド10進になるように補正
- AAS、2進減算命令を実行した後でアンパックド10進になるように補正
AADだけ順序が違うのね。また、AAAとAASは1バイト命令ですが、AADとAAMは2バイト命令です。そしてAADとAAMの2バイト目は即値の「10」だと思います。10進補正するときの10という数値が命令の中に含まれておると(8086世代の場合、当然そこを書き替えると異なる動作をするので演算命令として使えないこともない。。。)
今回動かしてみるアセンブリ言語ソース
NASMアセンブラ用のソース、FreeDOS上でCOM形式のオブジェクトにして実行するつもりのものが以下に。
org 100h section .text start: mov sp, stacktop test: mov ax, 0105h mov dl, 08h add al, dl aaa mov ax, 0101h add al, dl aaa nop mov ax, 0402h aad div dl nop mov ax, 0009h mul dl aam nop mov ax, 0203h sub al, dl aas mov ax, 0209h sub al, dl aas fin: mov ax, 0x4c00 int 0x21 section .data align=16 workw: dw 5678h section .bss align=16 resb 2048 stacktop:
動作確認
上記のソース (aaa.asm)から、以下のコマンドライン一発で実行可能なオブジェクトが得られます。
nasm aaa.asm -fbin -o aaa.com
動作確認は、FreeDOS上の debug コマンド(御本家 マイクロソフト社のdebugと「上位互換」のデバッガ)を使って行っております。
AX=0105のところに赤線ありますが、これはAH、ALの2つを合わせて10進2桁「15」だ、ということであります。ここにDLレジスタの「8」を加えるわけです。だたし2進加算命令 ADD AL, DLではALレジスタの内容が0x0D(つまり十進で13)になるだけでBCD表現からはハズレてます。そこでAAA命令を発するとあら不思議、AH=02、AL=03あわせて「23」ということで10進加算されております。
ここで注目したいのはフラグのACとCYです。とくに8086のACフラグ(auxiliary carry)は条件分岐に使えるわけでもなく、何のためのフラグ?と思われているかもしれませんが、BCD補正では、ほれこのように大活躍。
上記は、AAAの効果あり、のケースでしたが、計算によってはAAAなど不要の場合もあります。こんな感じ。
上記は10進で 1 + 8 = 9 と2進でみてもOKな範囲なので何も起こりませぬ。
最初AXに0402が入ってますが、これは10進解釈で「42」です。これを事前のAADをすると、AXの内容が002Aになってます。0x2A=42なので、AAD命令により10進2桁が2進数になったことになります。ここで DIV DLしてます。
42 ÷ 8 = 5 あまり2
ということで最終的にはAHにあまりの2、ALに商の5が入っておると。BCDで割り算できたことになりますな。
ALにDLをかけると 9 x 8 = 72 となります。2進数では0x48です。この2進の結果に対してAAM命令を適用すると、AHに7、ALに2とアンパックド10進に変換されとります。
最後は引き算です。赤線引いてないすけど、足し算と以下同文です。最初は補正が必要な場合。
計算はできるけど、説明がメンドイ。それに今となっては使い途もビミョ~。