今回は2つのソースオペランドのうち大きい方を求めるFMAXと小さい方を求めるFMINです。でもね、この命令それほどシンプルでもありません。メンドクセー奴、NaN(Not a Number)がからんでくるから。2命令FMAXとFMAXNMの差はNaNのとりあつかい次第。NaNにあまり深入りせずにお楽に行きたいです。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※実機動作確認には以下を使用しております。
-
- Raspberry Pi 4 model B、Cortex-A72コア(ARMv8-A)
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
※A64の最新のマニュアルは以下でダウンロード可能です。
Arm Architecture Reference Manual for A-profile architecture
2つのFMAX、2つのFMIN
ソースオペランド2個を比較して大きい方をデスティネーションに書き込むFMAXと小さい方を書き込むFMINです。ほぼほぼ同じ命令がニーモニック的には2個づつあります。
-
- FMAX
- FMAXNM
- FMIN
- FMINNM
2つのFMAX、2つのFMINの違いはといえば、ソースオペランドの片方にqNaN(静かな?NaN)が現れたときにわかります。FMAXとFMINの場合、比較対象の片方にqNaNがやってきた場合、結果もqNaNです。NaNと何かを比較してもなんだか分からんからNaN(ナン)だろ~ってか。
しかし、FMAXNMとFMINNMは違うようです。IEEE754-2008標準のお作法に則り、片方がqNaNでも、もう片方がフツーの数値ならその数値が返るようです。知らんけど。
これ以外にもNaNをゼロに”flush”する、flush to zeroモードのときにはゼロにも2種類あって。。。などメンドクセーことこの上ないですが、今回は省略。さっと「命令をなでる」だけにします。
-
- 例によって単精度と倍精度の両方あるけれど単精度だけ(ArmV8.0には半精度はないよ~ん)
- FMAXとFMAXNM、FMINとFMINNMの差が見えるところだけNaN(qNaN)を使用
今回実験のアセンブリ言語関数
「いつものように手抜きな」関数プロローグもエピローグもない1命令1関数スタイルの被テスト関数です。浮動小数点例外フラグの確認用にFPSR読み出し関数も置いておきましたが使いませんでした。手抜き。
.globl fmaxS, fmaxnmS, fminS, fminnmS, readfpsr .text .balign 4 fmaxS: fmax s0, s1, s2 ret fmaxnmS: fmaxnm s0, s1, s2 ret fminS: fmin s0, s1, s2 ret fminnmS: fminnm s0, s1, s2 ret readfpsr: mrs x0, fpsr ret
C言語記述のmain関数
いつもの通り「通り一遍さわるだけの手抜きな」C言語記述のテスト駆動部が以下に。今回は各命令間の挙動の差が見えるように、全命令に似たオペランド組2パターンづつを与えてます。最初のパターンは素直に大小比較するもの。2番目のパターンはNaNが現れるもの。
C言語の場合、math.h をインクルードすればqNaN相当のNANというマクロがつかえるようです。sNaN(シグナルNaN、五月蠅い方のNaN)はどうしたらよいの?テキトーにビット演算する?まあ、今回はsNaNには踏み込まんのでいいか。
#include <stdio.h> #include <stdint.h> #include <math.h> extern float fmaxS(float, float, float); extern float fmaxnmS(float, float, float); extern float fminS(float, float, float); extern float fminnmS(float, float, float); extern uint32_t readfpsr(); int main(void) { union { float s; uint32_t u; } f32; f32.s = fmaxS(0.0f, 1.23f, 4.56f); printf ("fmaxS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fmaxS(0.0f, 1.23f, NAN); printf ("fmaxS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fmaxnmS(0.0f, 1.23f, 4.56f); printf ("fmaxnmS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fmaxnmS(0.0f, 1.23f, NAN); printf ("fmaxnmS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fminS(0.0f, 1.23f, 4.56f); printf ("fminS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fminS(0.0f, NAN, 4.56f); printf ("fminS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fminnmS(0.0f, 1.23f, 4.56f); printf ("fminnmS=%2.3f(0x%08x)\n", f32.s, f32.u); f32.s = fminnmS(0.0f, NAN, 4.56f); printf ("fminnmS=%2.3f(0x%08x)\n", f32.s, f32.u); return 0; }
ビルドして実行
数値同士をくらべたときは分かりやすい結果ですが、NaNがからむとナンだかな~。でもお作法は守らないと。