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

Joseph Halfmoon

前回はメモリオペランドを使った整数演算でした。今回は「剰余算」です。フツーのプログラミング言語の剰余演算子は整数に対するものが多いですが、8087系FPUの剰余の命令FPREMは浮動小数演算です。そのうえ、partial で exact だと。とってもメンドクセー雰囲気が漂ってくるんだが、どうなんだ?

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

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

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
FPREM

剰余算というと、「5÷2の商は2、余り1」なので「1」ね、と整数計算するのがフツーです。しかし、8087FPUのもつFPREM命令では、「5.1 ÷ 2.1の商は2、余り0.9」という塩梅で割る数も割られる数も、そして結果の剰余も皆、浮動小数デス(実際には2進で0.1とか0.9とか表現できないので例えっす。)そのうえこの命令 partial remainder の略だそうな。partialって何?

この命令、「1回で結果が出ることもある」けれど、与えられた入力によってはループを回さないとならないので parial らしいです。 つまり、1回のFPREM命令実行後、フラグをチェックして完結してなかったらループを回さないとならないのだそうな。なんで、メンドイよ~。
その心は、この命令は exact にしたかったがためらしいです。不用意に割り算を実施して例外などを起こさないように、精度を守るのだと。そのため引き算(スケーリングあり)を繰り返して余りを求めるという「ある意味プリミティブ」な荒業を繰り出してます。しかし、もしかするととても長い時間がかかるその期間黙って計算から戻ってこなくなると割り込みを取りこぼす可能性があります。なんていっても8087の命令は、8086の命令ストリームの中に間借りしてます。適切に命令に切れ目を入れて、割り込みを受け付けないとマズイです。それでループだと。これならテキトーに中断できますわな。
なんでこういう変?な命令が欲しかったのか? その理由の一つが8087の三角関数命令にあったみたいです。8087の場合、一般角はおろか、2π以下の角度に対しても何時でも計算できるわけでなく、0~π/4とか入力に許される縛りがとてもきつかったです。範囲外はエラーね。そこでデカイ角度でも計算できる範囲に収めるためにFPREMという便利な命令を備えるにいたったという経緯があったらしいです。
しかし、本連載の過去回を読まれた方は知ってます。8087の角度の縛り、80387に至った時点で解けてます。つまり、FPREM命令の目的の一つであった、一般角から小さい角度に変換するという仕事は不要。悲し~。
なお、80387ではFPREM1という命令が追加されてます。「似たような」命令なのですが、こちらの命令はその値域がIEEEの規定に合致(±割る数の2分の1の範囲)してます。一方、FPREMの方は符号次第で±割る数の範囲にバラけます。これまた違いがビミョー。
今回実験のプログラム

今回は以下の2つの数をFPREM命令で処理してみます。

    • だいたい 6.28 くらいの割る数。泣く子も黙る 2π ね。
    • だいたい63.355くらいの割られる数。10*2π + π/6ね。10周まわって6分のπっと。

まあ、スタックトップに置かれた割られる数(一般角)から360°以下のフツーの角度を求める感じ。

なお以下は「強力な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 bx, src1
    mov si, src2
    fld dword [bx]
    fldpi
    fmulp
    fld dword [si]
loopFPREM:
    fprem
    fstsw ax
    sahf
    jp loopFPREM
    nop
fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
    resb    1024 * 63
src1: dd 2.0
src2: dd 63.355

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

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

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

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

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

さて、上の緑色の部分の前後でレジスタスタックがどうなっているのか眺めてみます。こんな感じ。FPREMresult

黄色枠のところの一般角 10*2π + π/6相当を、赤枠の2π相当で割った余りをもとめたら、緑枠の π/6 相当の数がもとまったみたい。余りっちゃ余りだけれども、メンドクセーな、おい。

ぐだぐだ低レベルプログラミング(229)x86(16bit)、FPUつかった整数演算 へもどる

コメントを残す

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