
前回まで浮動小数点数を整数または固定小数点数に変換するFCVT命令群を練習してきました。今回は逆、整数または固定小数点数を浮動小数点数に変換するSCVTF(符合付整数から)、UCVTF(符合無整数から)命令です。そんなん簡単じゃろう、と思うと意外とメンドイ奴らであります。元は整数なのに「丸め」が関係してくる、どゆこと?
※「ぐだぐだ低レベルプログラミング」投稿順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
整数から浮動小数に変換するときの「丸め」
IEEE754フォーマットが身に染みついている偉大な人には申し上げません。整数を浮動小数に変換するなど簡単「.(ピリオド)」1個つけるだけと思っていたよゐこには困った事実であります。整数から浮動小数に変換するときにはあのメンドイ「丸めモード」が多いに関係します。
FCVT命令群では命令ニーモニックにエンコードされていた丸めモードが、逆変換であるSCVTF、UCVTF命令では例のFPCRレジスタのRMビットの制御に逆戻りです。
結論から言えば、丸めが必要になるのは整数型よりも浮動小数点型の方が仮数部のビット幅が少ないケースがあり得るからです。
-
- 32ビット整数、32ビット全体を「仮数」相当の表現に使える
- 64ビット整数、64ビット全体を「仮数」相当の表現に使える
- 単精度浮動小数、仮数部23ビット、隠れ最上位ビットを入れても仮数は24ビット幅
- 倍精度浮動小数、仮数部52ビット、隠れ最上位ビットを入れても仮数は53ビット幅
単精度の場合、24ビット幅以内で表現できる整数であればそのまま変換できるけれども、それを超える大きい数は下の方の桁を丸めないと表現できない、ということであります。浮動小数点数は指数部があるのでダイナミックレンジは広いけど、その分細々した表現能力は落ちていた、と。愕然?
今回実験のアセンブリ言語関数
気を取り直して、被テストのアセンブリ言語記述関数を書いていきます。今回も例によって関数プロローグもエピローグもない、手抜きな1命令1関数スタイルです。
ターゲットとなるニーモニックはSCVTFとUCVTFの2つですが、オペランド的には
-
- デスティネーションは単精度と倍精度
- ソースはW(32ビット整数)とX(32ビット整数)
- 整数か固定小数点数か
という組み合わせがあるので、ひととおりの組み合わせをすべてやると16通りとな。全部やるのは怠いので、かなり手抜きになってます。
.globl scvtfSW, scvtfDW, scvtfSWQ12, ucvtfSW, ucvtfDW, ucvtfSWQ12, readfpcr, writefpcr
.text
.balign 4
scvtfSW:
scvtf s0, w0
ret
scvtfDW:
scvtf d0, w0
ret
scvtfSWQ12:
scvtf s0, w0, #12
ret
ucvtfSW:
ucvtf s0, w0
ret
ucvtfDW:
ucvtf d0, w0
ret
ucvtfSWQ12:
ucvtf s0, w0, #12
ret
readfpcr:
mrs x0, fpcr
ret
writefpcr:
msr fpcr, x0
ret
C言語記述のmain関数
これまた手抜きなC言語記述のテスト駆動部が以下に。
今回は、問題部分を「抉る」コードといたしました。わざと「丸め」のところを踏んずけているのね。そのくせ「丸めモード」は成り行き、ということで。初期設定のままなら「最近傍」でモード0と出力される筈。
#include <stdio.h>
#include <stdint.h>
extern float scvtfSW(float, int32_t);
extern double scvtfDW(double, int32_t);
extern float scvtfSWQ12(float, int32_t);
extern float ucvtfSW(float, uint32_t);
extern double ucvtfDW(double, uint32_t);
extern float ucvtfSWQ12(float, uint32_t);
extern uint64_t readfpcr(uint64_t);
extern void writefpcr(uint64_t);
void printFixedPoint(uint32_t arg, uint32_t fxp)
{
uint32_t ipart = arg >> fxp;
uint32_t msk = 0;
for (int i=0; i < fxp; i++) {
msk = (msk << 1) | 1;
}
float fpart = (float)(arg & msk) / (msk + 1);
printf("%d %f\n", ipart, fpart);
}
//Do not use! BUG in it.
void printFixedPointS(int32_t arg, uint32_t fxp)
{
int32_t ipart = arg >> fxp;
int32_t msk = 0;
for (int i=0; i < fxp; i++) {
msk = (msk << 1) | 1;
}
float fpart = (float)(arg & msk) / (msk + 1);
printf("%d %f\n", ipart, fpart);
}
int main(void)
{
float resultS;
double resultD;
uint32_t fxpworkU;
int32_t fxpworkS;
printf ("RM : %d\n", ((uint32_t)readfpcr(0) >> 22) & 3);
resultS = ucvtfSW(0.0f, 123456789);
printf ("ucvtfSW(0.0f, 123456789): %f\n", resultS);
resultD = ucvtfDW(0.0f, 123456789);
printf ("ucvtfDW(0.0, 123456789) : %f\n", resultD);
fxpworkU = 0xFFFFF800;
printFixedPoint(fxpworkU, 12);
resultS = ucvtfSWQ12(0.0f, fxpworkU);
printf ("ucvtfSWQ12(0.0f, 0xFFFFF800): %f\n", resultS);
resultS = scvtfSW(0.0f, -123456789);
printf ("scvtfSW(0.0f, -123456789): %f\n", resultS);
resultD = scvtfDW(0.0f, -123456789);
printf ("scvtfDW(0.0, -123456789) : %f\n", resultD);
fxpworkS = 0xFFFFF800;
printFixedPointS(fxpworkS, 12);
resultS = scvtfSWQ12(0.0f, fxpworkS);
printf ("scvtfSWQ12(0.0f, 0xFFFFF800): %f\n", resultS);
return 0;
}
ビルドして実行
ビルドして実行した結果が以下に。問題の部分に赤線引いておきましたぞ。
まず、丸めモードRMは、予定どおり0みたいです。最近傍。
最初にUCVTF命令で、デスティネーションが単精度と倍精度のケースを比べてます。赤字の部分にご着目あれ。本当は123456789という値になるべきで、倍精度ではそうなっているけれど、あれれ、単精度の方は下の桁が違っているじゃん。123456789という整数は24ビットに収まらないので下の方が丸められちまったんであります。まあここは予定通りか。
続いて 1048575.5 というQ12フォーマット(前回参照)の固定小数点数(符合無)を浮動小数に変換してます。これはOKね。なお、この数は16進表現すると0xFFFFF800です。後で再登場しますです。
続いて符合付のSCVTF命令に入ります。符合が有ろうと無かろうと(浮動小数点フォーマットでは専用の符合ビットがあるので)仮数部の幅は変わりません。さきほどのUCVTF同様、単精度に変換しようとすると収まりきらず丸めが発動しています。
続いてQ12フォーマットの固定小数点数の変換です。先ほどと同じ0xFFFFF800というビットパターンです。前回固定小数点数の表示用に作った(符合無前提)表示ルーチンを流用した方法では上位24ビットが0xFFFFFなのでー1、下位12ビットが0x800なので0.5と別々に変換して分割表示してますが、これはバグです。実際は-0.5です。SCVT命令は正しく(当たり前か)解釈して-0.5と表示しとります。