x87 による浮動小数点数の比較

# この記事のねらい

* x87 で浮動小数点数の大小を比較する命令がわかる
* 比較結果を x86 で取得・解釈する方法がわかる

# 浮動小数点数を比較する命令

x87 では、「ST(0) (スタックトップ) と〇〇を比較する」命令がいくつかある。

[FCOM 命令および FCOMP 命令](https://c9x.me/x86/html/file_module_x86_id_87.html)は、比較対象としてメモリ上または x87 のレジスタスタック上の**浮動小数点数**を用いる。
[FICOM 命令および FICOMP 命令](https://c9x.me/x86/html/file_module_x86_id_94.html)は、比較対象としてメモリ上の**整数**を用いる。

[FCOMPP 命令](https://c9x.me/x86/html/file_module_x86_id_87.html)は、比較対象として x87 のレジスタスタック上のトップの次の値 ST(1) を用いる。
[FTST 命令](https://c9x.me/x86/html/file_module_x86_id_123.html)は、比較対象として固定値のゼロ (0.0) を用いる。

FCOM 命令・FICOM 命令・FTST 命令は、比較後もレジスタスタック上の値を変更しない。
FCOMP 命令・FICOMP 命令は、比較後、レジスタスタックから値1個をポップする。(ST(0) を除去する)
FCOMPP 命令は、比較後、レジスタスタックから値2個をポップする。(ST(0) および ST(1) を除去する)

|命令|比較対象|スタックからのポップ|
|---|---|---|
|FCOM|メモリ上の 32bit 浮動小数点数<br>メモリ上の 64bit 浮動小数点数<br>スタック上の値 ST(i)|しない|
|FCOMP|メモリ上の 32bit 浮動小数点数<br>メモリ上の 64bit 浮動小数点数<br>スタック上の値 ST(i)|1個する|
|FCOMPP|スタック上の次の値 ST(1)|2個する|
|FICOM|メモリ上の 16bit 整数<br>メモリ上の 32bit 整数|しない|
|FICOMP|メモリ上の 16bit 整数<br>メモリ上の 32bit 整数|1個する|
|FTST|ゼロ (0.0)|しない|

これらの命令は、比較結果に応じて、x87 のステータスワードのうち C0、C2、C3 を以下のように設定する。

|結果|C0|C2|C3|
|:-:|:-:|:-:|:-:|
|ST(0) &gt; 比較対象|0|0|0|
|ST(0) = 比較対象|0|0|1|
|ST(0) &lt; 比較対象|1|0|0|
|比較不能|1|1|1|

「比較不能」は、ST(0) または比較対象のいずれか、または両方が NaN または未対応のフォーマットの場合である。
このとき、IE (invalid operation) 例外が発生し、この例外がマスクされていない場合は割り込み要求が行われる。

:::note warn
C1 は、8087 のデータシートでは「変化なし」となっているが、最近のCPUでは「0にする」となっているようである。
:::

# 比較結果の取得・解釈

[FNSTSW 命令](https://c9x.me/x86/html/file_module_x86_id_120.html)により、2バイトのステータスワードをメモリに読み込むことができる。
また、80286 以降では、ステータスワードを直接 AX レジスタに読み込むこともできる。

:::note info
FSTSW 命令は、FNSTSW 命令の直前に [FWAIT 命令](https://c9x.me/x86/html/file_module_x86_id_324.html)を実行する。
:::

ステータスワードの値を AX レジスタに格納し、さらに [SAHF 命令](https://c9x.me/x86/html/file_module_x86_id_284.html)を用いると、以下のように x86 のフラグレジスタが x87 の C0、C2、C3 に従って設定される。

|x87 ステータスワード|x86 フラグレジスタ|
|:-:|---|
|C0|CF (キャリーフラグ)|
|C2|PF (パリティフラグ)|
|C3|ZF (ゼロフラグ)|

:::note info
C0、C2、C3 はステータスワードの上位に配置されているため、フラグレジスタの下位を AH レジスタ (AX レジスタの上位) に従って設定する SAHF 命令によりフラグレジスタに反映できる。
ステータスワード全体を AX レジスタに格納せず、上位だけを AH レジスタにロードして SAHF 命令を用いてもよさそうである。
:::

:::note info
C1 もステータスワードの上位に配置されているが、予約されているビットに対応するため、フラグレジスタには反映されない。
:::

:::note warn
POPF 命令によりフラグレジスタ全体の値を設定するには、ステータスワードの上位と下位を入れ替える、IF (割り込みフラグ) などの重要だが計算と関係ないフラグを適切に設定する、といった手間がかかる。
SAHF 命令により計算結果を表すフラグレジスタの下位のみを設定したほうが簡単だろう。
:::

これを先ほどの比較結果とステータスワードの対応関係と比較すると、[Jcc 命令](https://c9x.me/x86/html/file_module_x86_id_146.html) を用いて以下のように判定することができることがわかる。

|命令|判定|ジャンプする条件|比較不能時ジャンプ|
|---|---|---|:-:|
|JP|PF=1|(比較不能)|する|
|JNP|PF=0|(比較不能でない)|しない|
|JB / JNAE|CF=1|ST(0) &lt; 比較対象|する|
|JBE / JNA|CF=1 or ZF=1|ST(0) ≦ 比較対象|する|
|JE / JZ|ZF=1|ST(0) = 比較対象|する|
|JNE / JNZ|ZF=0|ST(0) ≠ 比較対象|しない|
|JAE / JNB|CF=0|ST(0) ≧ 比較対象|しない|
|JA / JNBE|CF=0 and ZF=0|ST(0) &gt; 比較対象|しない|

また、[TEST 命令](https://c9x.me/x86/html/file_module_x86_id_315.html) (ビット AND 演算の結果に基づいてフラグを設定する) などを用い、取得したステータスワードの上位の値を直接チェックすることでも、判定が可能である。
上記の表と同様の判定は、TEST 命令と JZ/JNZ 命令を用いて以下の方法で可能である。

|ジャンプする条件|判定|AND を取る値|分岐命令|
|---|---|:-:|:-:|
|比較不能|PF=1|0x04|JNZ|
|比較不能でない|PF=0|0x04|JZ|
|ST(0) &lt; 比較対象 or 比較不能|CF=1|0x01|JNZ|
|ST(0) ≦ 比較対象 or 比較不能|CF=1 or ZF=1|0x41|JNZ|
|ST(0) = 比較対象 or 比較不能|ZF=1|0x40|JNZ|
|ST(0) ≠ 比較対象|ZF=0|0x40|JZ|
|ST(0) ≧ 比較対象|CF=0|0x01|JZ|
|ST(0) &gt; 比較対象|CF=0 and ZF=0|0x41|JZ|

# まとめ

* x87 では、スタックトップの値 ST(0) と比較対象の大小関係を比較する命令がある
* 比較結果は x87 のステータスワードに反映される
* 比較結果が入ったステータスワードの上位を SAHF 命令でフラグレジスタに反映すると、JA 命令などの条件分岐命令で比較結果の解釈ができる