ぐだぐだ低レベルプログラミング(231)x86(16bit)、BCD整数のロード、セーブ

Joseph Halfmoon

前回はpartialでexactな「剰余算」でした。今回もメンドクセー命令がつづきます。packed な BCD 整数のロード、ストア命令っす。BCD=Binary-coded decimalデス。8087FPUには「フツーの」整数とは別にBCD数を扱うための命令あり。計算できる桁数は10進18桁です。なんで18桁?

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
8087のpacked Decimal

8087系FPUは、メモリ上にフツーの(2の補数)整数を置いておいてそれを取り扱えます。それに加えてパックされたBCD整数のサポートもあります。そのフォーマットが以下に。packedDecimal

80ビット幅(内部テンポラリ実数型と同じ)を使い、上記のようにMSBに符号ビットを置き、LSB側の72ビットに10進各桁4ビットx18桁を格納するフォーマットです。ビット78から72は使用してません。

いわゆるパックドデシマル(4ビットのニブルに十進数1桁づつを格納し、上下のニブルで1バイトに合計2桁を格納)です。十進なので0~9のみ格納し、0xAから0xFまでは使いませぬ。

この18桁のBCD整数は以下の命令によりFPUのレジスタ・スタックとやりとりされます

    • FBLD、パックドBCD整数をメモリからレジスタへロード
    • FBSTP、レジスタの内容をパックドBCD整数形式でメモリにストア

レジスタ・スタック上での演算は、整数も浮動小数もないので、過去回でやってきた命令どもは何でもアリです(当然、エラーになるやつはエラーになる。)

さて、18桁という仕様について、どこかで「COBOLの標準が18桁だったから18桁になった」という記述を読んだ記憶があります。元より忘却力の老人、その真偽を知らず。そこで例によってGoogleの生成AI、Gemini 2.5 Flash様にその経緯をお聞きしてみましたぞ。geminiBCD360

 

上記のように、Gemini様は、今に至るコンピュータの歴史の中で燦然と輝くIBM System/360 を「推し」てます。たしかに「360」のお名前を見るだけで、お惚け老人などひれ伏してしまうのですが、土下座しながらもホントか?と疑問。なぜなら、

    • IBM System/360は1964年発表、製品が出回ったのは翌年くらいかららしい
    • COBOLは1959年開発、翌1960年から規格書が出回ったらしい
からであります。時系列的にいうとCOBOL規格書が18桁のパックドBCD数のサポートを要求していた(なにせ、そのご要求の後ろには米国の政府調達はこれに従えという「強制力」が鎮座していたので、当時のコンピュータメーカ各社はこのご要求を満たすべく奔走せざるを得なかったと想像)からでないの、と。お惚け老人の勝手な意見デス。
まあIBM様もSystem/360以前から、「いろいろ」BCD形式はサポートされていたみたい。その集大成としてSystem/360ではCOBOL準拠にしたのではないかしらん、と想像。まあ、昔の話なのでホントのところはよく分からんです。
しかし、メインフレーム素人老人の朧げな記憶では、System/360はレジスタ幅32ビット(浮動小数では64ビット)であったような。どうやって十進18桁(パックドBCD形式を2進変換してしまうとするとだいたい60ビット)の計算ができたの?とさななる疑問デス。これに対するGemini様のご回答は(今度こそは)もっともな感じでした。

geminiBCD360_2

そうでした、System/360はマイクロコードマシンだったみたいです。CISC中のCISCね。それに比べたら「ロード・ストア」っぽいところのある8087など腰が引けてるCISCだと。別にレジスタ使わずともいいじゃん。

今回実験のプログラム

今回は以下のような処理を行ってみます。

    1. 2つのBCD数をFBLD命令でメモリからレジスタスタックにロードする
    2. ロードした数2個をレジスタスタック上の加算する(通常のFADD命令)
    3. 加算結果をFBSTP命令でレジスタスタックからメモリにストアする

そしてメモリ上で計算結果が確認できればOKっと。

なお以下は「強力なx86用アセンブラNASM」用のソースです(MSのMASMともインテルASM86とも微妙に異なるけど、まあ分かるっしょ。)

%use fp

segment code
..start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, stacktop
test:
    mov si, src1
    mov di, src2
    mov bx, dst1
    fbld tword [si]
    fbld tword [di]
    fadd
    fbstp tword [bx]
    nop
fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
    resb    1024 * 63
src1: dt  12_345_678_901_245_678p
src2: dt -00_000_000_001_245_678p
dst1: dt 0p

segment stack   class=STACK align=16
    resb    2048
stacktop:
    dw 1024 dup (0)
stackend:
アセンブルして実行

MS-DOS互換で機能強化されているフリーなFreeDOS上、以下のステップで上記のアセンブラソース fbld.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)

なお、FreeDOS付属の「debugx」デバッガは、8087のレジスタ内容を浮動小数で見せてくれます

nasm -f obj -l fbld.lst fbld.asm
wlink name fbld.exe format dos file fbld.obj
debugx fbld.exe

まずはプログラムの逆アセンブルリストunasmFBLD

まずは、実行前のメモリの様子。MEMbefore

SIが指しているのが赤枠、DIが指しているのが緑枠です。後でストアされる領域が黄枠。なお、8086/8087はリトル・エンディアンです。先頭バイトの0x78が、アセンブラの以下のところの末尾の78ね。

src1: dt 12_345_678_901_245_678p

さて、実行っと。ADDbeforeAfterEC

レジスタスタック上の緑枠のマイナスの値と赤枠の値を加算したら、黄枠の結果が得られることが分かるよね。

そしてFBSTP命令でスタックトップの値をメモリにストアしたところが以下にMEMafterEC

歴史的経緯は別にして、18桁のパックドBCD数の計算は今も健在。大丈夫か?

ぐだぐだ低レベルプログラミング(230)x86(16bit)、FPREM、剰余は正確で面倒 へ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です