ぐだぐだ低レベルプログラミング(108)ARM64(AArach64)FCVTxy、整数変換

Joseph Halfmoon

前回は単精度と倍精度の間のフォーマット変換を行うFCVT命令でした。同じ命令が丸めモードの設定で動作が変わるので厄介でした。今回のFCVTxy命令群(xyのところはいろいろ)は、浮動小数点数を整数に変換するもの。丸めモードフラグには関係なしと喜んだら、かえって組み合わせが増えているでないの?もうメンドくてやりきれない?

※「ぐだぐだ低レベルプログラミング」投稿順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

浮動小数点数から整数への変換命令群

浮動小数点レジスタの中の浮動小数点数(スカラー)を整数レジスタの中の整数に変換する命令群には、以下のような組合わせがあります。

    1. ソース、単精度/倍精度(半精度)
    2. デスティネーション、W(32ビット)レジスタ、X(64ビット)レジスタ
    3. 符号、あり、なし
    4. 丸め

ARMv8p2以降では、半精度浮動小数のサポートがあるので、組み合わせの数はさらに倍増してしまうのです。ターゲット機はARMv8p0なので半精度はありません。ホントよかった。

その組み合わせをまとめると以下の表のようになります。DesitinationのWは32ビット整数、Xは64ビット整数、SourceのSは単精度浮動小数、Dは倍精度浮動小数です。組み合わせとしては倍精度浮動小数を32ビット整数に変換するのもアリです。

また前回のFCVTはFPCRレジスタのRMビットで制御していた丸めモードは、命令自体にエンコードされてます。またRMビットとは異なる部分もあり。(※以下表は2024/7/3改定。)

INSN Signed/Unsigned Desitination Source Rounding Mode
FCVTAS Signed W/X S/D 最近接Away
FCVTAU Unsigned W/X S/D 最近接Away
FCVTMS Signed W/X S/D -∞
FCVTMU Unsigned W/X S/D -∞
FCVTNS Signed W/X S/D 最近接偶数
FCVTNU Unsigned W/X S/D 最近接偶数
FCVTPS Signed W/X S/D +∞
FCVTPU Unsigned W/X S/D +∞
FCVTZS Signed W/X S/D zero
FCVTZU Unsigned W/X S/D zero
FJCVTZS Signed W D zero

毎度繰り返しますが、ARM64の命令多すぎ。とても覚えきれませぬ。丸めに関しては今回はRMビットには影響されないので前回のようにまったく同じ命令をモードを変えて試行してみる必要はないです。しかし、RMビットでは4種類しかなかった丸めモードが5種類に。なんか増えてないかい?それにFJCVTZSというのは何?JavaScript専用?流石Armじゃね。

とても一度で触ってみれないので、今回はUnsignedの変換命令のみ、ソース、デスティネーションの組み合わせも限って実習してみます。それに丸めモードも、プラス、マイナス無限大は端折って、「最近接away」と「最近接偶数」、「zero」のみ比べてみます。

今回実験のアセンブリ言語関数

今回も例によって関数プロローグもエピローグもない、手抜きな被テストアセンブラ関数群が以下に。

.globl    fcvtauWS, fcvtauXS, fcvtauWD, fcvtauXD, fcvtzuWD, fcvtnuWD, readfpcr, writefpcr
.text
.balign    4

fcvtauWS:
    fcvtau w0, s0
    ret

fcvtauXS:
    fcvtau x0, s0
    ret

fcvtauWD:
    fcvtau w0, d0
    ret

fcvtauXD:
    fcvtau x0, d0
    ret

fcvtzuWD:
    fcvtzu w0, d0
    ret

fcvtnuWD:
    fcvtnu w0, d0
    ret

readfpcr:
    mrs x0, fpcr
    ret

writefpcr:
    msr fpcr, x0
    ret
C言語記述のmain関数

符号無に限っても組み合わせが多いので、かなり端折ってます。まず「最近接away」命令で、デスティネーションとソースの組み合わせを一通り動かした後(これとて被試験機がARMv8p0だということで単精度が無いので大分救われてます)、同じ数値に対して、3つの丸めモードの違いを見てます。

#include <stdio.h>
#include <stdint.h>

extern uint32_t fcvtauWS(uint32_t, float);
extern uint64_t fcvtauXS(uint64_t, float);
extern uint32_t fcvtauWD(uint32_t, double);
extern uint64_t fcvtauXD(uint64_t, double);
extern uint32_t fcvtzuWD(uint32_t, double);
extern uint32_t fcvtnuWD(uint32_t, double);
extern uint64_t readfpcr(uint64_t);
extern uint64_t writefpcr(uint64_t);

int main(void)
{
    union {
        double resultD;
        uint64_t result64;
    } u64;

    union {
        float resultS;
        uint32_t result32;
    } u32;

    u32.result32 = ((uint32_t)readfpcr(0) >> 22) & 3;
    printf ("RM    :    %d\n", u32.result32);
    u32.result32 = fcvtauWS(0, 5.55f);
    printf ("fcvtauWS(0 5.55f): 0x%08x\n", u32.result32);
    u32.result32 = fcvtauWD(0, 5.55);
    printf ("fcvtauWS(0 5.55): 0x%08x\n", u32.result32);
    u64.result64 = fcvtauXS(0, 1.123456789012e12);
    printf ("fcvtauXS(0, 1.123456789012e12): %ld\n", u64.result64);
    u64.result64 = fcvtauXD(0, 1.123456789012e12);
    printf ("fcvtauXD(0, 1.123456789012e12): %ld\n", u64.result64);

    u32.result32 = fcvtauWD(0, 5.5);
    printf ("fcvtauWD(0 5.5): 0x%08x\n", u32.result32);
    u32.result32 = fcvtzuWD(0, 5.5);
    printf ("fcvtzuWD(0 5.5): 0x%08x\n", u32.result32);
    u32.result32 = fcvtnuWD(0, 5.5);
    printf ("fcvtnuWD(0 5.5): 0x%08x\n", u32.result32);

    u32.result32 = fcvtauWD(0, 6.5);
    printf ("fcvtauWD(0 6.5): 0x%08x\n", u32.result32);
    u32.result32 = fcvtzuWD(0, 6.5);
    printf ("fcvtzuWD(0 6.5): 0x%08x\n", u32.result32);
    u32.result32 = fcvtnuWD(0, 6.5);
    printf ("fcvtnuWD(0 6.5): 0x%08x\n", u32.result32);

    return 0;
}
ビルドして実行

ビルドして実行した結果が以下に。

fcvt2Result
ピックアップした丸めモード3種のみ表にしてみると以下のとおりです。

丸めモード 元値=5.5 元値=6.5
最近接Away 6 7
最近接偶数 6 6
zero 5 6

整数と整数の真ん中の0.5の丸めを考える場合、EVEN、偶数方向丸め(上記では最近接偶数と表記)が「浮動小数点業界?」の定番じゃないかと思います。一方、最近接Awayを選択するとゼロから遠い側に変換してくれるみたいです。こちらの挙動の方が、慣れ親しんだ四捨五入ですかな。勿論、Zeroは、Zeroに近い方向の整数に丸め(切り捨て)てます。今回は符合無だけだったので、マイナスの数が扱えていないですが、マイナスも含めるともっと場合分けが増えるなあ、メンドイなあ。でも必要としている人はいる筈だし。もう1回くらい練習しておく?

ぐだぐだ低レベルプログラミング(107)ARM64(AArach64)FCVT へ戻る

ぐだぐだ低レベルプログラミング(109)ARM64(AArach64)FCVT、固定小数点 へ進む