ぐだぐだ低レベルプログラミング(223)x86(16bit)、SIN、COS、TANの計算

Joseph Halfmoon

前回、SIN、COS、TANについて「コマケー話」を書いたつもりが「誤ってました」その理由はほぼ半世紀前の書物に頼ったことにあります。今回、実機の挙動が異なるのでこれまた半世紀前のインテル公式マニュアルみたらば違ってました。詳しくは本文にて。まあ、訂正してお詫びして実際に動作したコードを貼付いたします。ぐだぐだ。

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

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

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

※オリジナルの8086+8087の組み合わせでは8086と8087の同期をとるためにWAIT命令が必用になる場合があります。後継機種ではWAIT命令が不要(演習に使用しているエミュレータQEMUでも不要)なのでWAIT命令は使用しません。メンドイし。

FCOSとFSIN

前回、8087にはTANを計算する命令のみあり、SINやCOSを直接計算する命令が無いと書きました。SINやCOSを直接計算できる命令が備わったのは80387以降です。80387のFSIN、FCOS命令の挙動は、ほぼほぼ半世紀前の以下の文書(日本語版)を参照させていただいておりました。

80386プログラミング、 クロフォード&ゲルシンガー著、岩谷訳 工学社 1988

上記の書物を以下の別シリーズにて取り上げさせていただいております。登場したての386の宣伝?のために書かれた古い本ですが、80386の設計の中心人物であった、敬愛する偉大なエンジニア様2人の共著であります。お惚け老人はそこに書いてあることを(ちょっと引っかかりながらも)長年信じ込んでおりました。

L.W.R.(57)80386プログラミング、クロフォード、ゲルシンガー著 岩谷訳 1988

心の底でちょっと引っかかったのは、FSIN命令とFCOS命令が返してくる結果です。該当命令から戻った時点のレジスタスタック上、ST(1) にFSINなりFCOSの結果が入り、スタックトップであるST(0)には何やら不思議な1.0 が格納されることになっております。

8087から存在するFPTAN命令においては、FPTANの結果がST(1)に入り、ST(0)には何やら不思議な1.0という形になってます。ある意味「8087」に合わせた形ではあるのです。お惚け老人が勝手に妄想するに、8087はFPTANを求める計算シーケンスで「プログラマに見える」レジスタを1個潰してしまう必要があり、このような形でその内部使用を糊塗したんではないかと。知らんけど。

しかし、 この1.0、無駄じゃないですかね。多分、80387が公式登場するまでに、プログラマから見えるレジスタを1個潰すという暴挙にでることなく、計算できるように内部回路が改良されたんではないかと。結果、現物のFSIN、FCOS命令の実装は、スタックトップの入力(ラジアン)を読んで、スタックトップにFSINあるいはFCOSの結果を返すという「素直」な方向になったのでしょう。今になって、以下の約50年前の「インテルの公式マニュアル」をひっくり返すと

80387 プログラマーズ・リファレンス・マニュアル インテル 1987

FSINやFCOSが謎の1.0を返すという記述はありませんでした。

想像するに偉大な設計者のお二人が解説書を御執筆された時点ではまだ「8087方式」のレジスタの使い方で実装される予定が、後になって改良されたのではないかと勝手に推測しております。まあね、インテル公式のマニュアルやデータシートが常にあっているとは限らんのだけれども。386も「途中で引っ込んだ命令」あるくらいだし。

なお、FPTAN命令は既に8087に実装されて世に出てしまっているので、その後も「不思議な1.0」は残ったままです。互換性命。

今回実験のプログラム

さて今回実験のプログラムは、中学生?でも計算できるありがちな三角関数値を各1点だけ、計算するというものです。

    • 入力は π/6 ラジアン(30°ね。)浮動小数的にはπ/6 = 0.523498…
    • SIN(π/6)= 0.5、ピッタンコ。
    • COS(π/6)= (√3)/2 = 0.866025…
    • TAN(π/6)=0.577350…

上記のような値が見えれば、まあ、SIN、COS、TANの計算できとるということ。例によって強力な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:
    fldpi
    mov bx, src1
    fld dword [bx]
    fdiv
    fld st0
    fsin
    fld st1
    fcos
    fld st2
    fptan
fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
    resb    1024 * 63
src1: dd 6.0

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

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

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

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

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

FDIVPのところ(π/6を計算してスタックトップに置くところ)まで進めます。こんな感じ。FDIV

ちょっと見ずらいですが、上の青枠のところで π/6 = 0.523498…が計算されております。

つづいて折角求めた π/6 をが消えてしまわないように「複製」してます。DUP1st

緑の値 π/6 がコピーされて黄色枠の新たなスタックトップに現れました。

そしてFSINを実行。FSIN

予定通り、0.5ピッタンコの結果が出てよかった。

続いて再度π/6をスタックトップに複製し、FCOS。FCOS

黄色枠のFCOSの結果は、 (√3)/2であるようです。よかった。

再度π/6をスタックトップに複製し、最後にFPTANFPTAN

FPTANは8087時代の命令なので、ST1にTANの結果が入り、ST0(スタックトップ)には謎の1が入ってます。まあ、これはこれで納得。

ぐだぐだ低レベルプログラミング(222)x86(16bit)、SINとCOSのコマケー話 へ戻る