浮動小数点
IEEE浮動小数点の標準規格では数vを以下の形式で表す
- s: 符号
- M: 仮数
- E: 指数
浮動小数点では上記の値をエンコードするため、ビット表現を以下の3つに分ける
- 1ビットの符号ビットs -> 符号s
- kビットの指数フィールドexp -> 指数E
- nビットの少数フィールドfrac -> 仮数M
C float(32bit)
- s = 1
- k = 8
- n = 23
C double(64bit)
- s = 1
- k = 11
- n = 52
Case1 正規化
expのビットパターンが全て0あるいは全て1のいずれでもない場合 (!(0000
|| 1111
))
fracは
Case2 非正規化
Case3 Inf or Nan
指数フィールドが全て1である
少数フィールドが全て0の場合は、
少数フィールドが全て0でない場合は、
単精度
説明 | exp | frac | 値 | 十進法 |
---|---|---|---|---|
ゼロ | 00...00 | 0...00 | 0 | 0.0 |
最小の非正規化 | 00...00 | 0...01 | ||
最大の非正規化 | 00...00 | 1...11 | ||
最小の正規化 | 00...01 | 0...00 | ||
1 | 01...11 | 0...00 | ||
最大の正規化 | 11...10 | 1...11 |
最大の非正規化と最小の非正規化が連続する理由は、非正規化数における
暗黙の先行する1とは
正規化数において、
丸めモードのうち、デフォルトは偶数丸め。
最下位桁が偶数であるように切り上げ、切り下げを行う。
1.50 -> 2
2.50-> 2
3.50 -> 4
4.50 -> 4
偶数丸めを使うのは統計的な偏りを避けるため。
二進数における丸め計算
最も近い2分の1(二進小数点の右側に1ビット)に丸める
A) 10.010 (
B) 10.011 (
C) 10.110 (
D) 11.001 (
A
10.010 は 10.0100。下位3ビットをみると、丸めの境界。右から4ビット目は0であるため、偶数である。よって下位3ビットを切り捨てる。
B
10.011 は 10.0110。下位3ビットをみると境界ではないので、通常通りに丸める。よって切り上げ。
C
10.110 は 10.1100。下位3ビットに着目すると丸めの境界。右から4ビット目は1であるため、機数にあたることがわかる。よって下位3ビットは繰り上げになる。
D
11.001 は 11.0010。下位3ビットに着目すると丸めの境界ではない。よって通常通り切り捨てる。
Cの標準規格ではIEEE浮動小数点を用いることは定められていない。
floatあるいはdoubleからintへのキャストの挙動もC標準規格で定められていない。
浮動小数点データは拡張命令セットで処理される。
Pentium/MMXから始まり、SSE、AVXなど。
AVX浮動小数点アーキテクチャでは、%ymm0 ~ %ymm15という名前のRMMレジスタにデータが保存される。レジスタのサイズは256ビット(32バイト)である。
C言語で書かれたコードからコンパイルしたアセンブリを読んだ。
アセンブリにコメントを追加して命令を追う。
double fcvt(int i, float *fp, double *dp, long *lp)
{
float f = *fp;
double d = *dp;
long l = *lp;
*lp = (long)d;
*fp = (float)i;
*dp = (double)l;
return (double)f;
}
↓
.file "fcvt.c"
.text
.p2align 4
.globl fcvt
.type fcvt, @function
fcvt:
.LFB0:
.cfi_startproc
endbr64
pxor %xmm1, %xmm1 # xmm1 = 0
cvttsd2siq (%rdx), %r8 # r8 = *dp
movq (%rcx), %rax # rax = *lp
cvtsi2ssl %edi, %xmm1 # float xmm1 = (float)i
movss (%rsi), %xmm0 # float xmm0 = *fp
cvtss2sd %xmm0, %xmm0 # xmm0 = (double)xmm0
movq %r8, (%rcx) # *lp = r8
movss %xmm1, (%rsi) # *fp = xmm1
pxor %xmm1, %xmm1 # xmm1 = 0
cvtsi2sdq %rax, %xmm1 # xmm1 = (double)rax
movsd %xmm1, (%rdx) # *dp = xmm1
ret
.cfi_endproc
.LFE0:
.size fcvt, .-fcvt
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
コンパイルの環境
$ name -a
Linux d8688f9dc901 5.10.25-linuxkit #1 SMP Tue Mar 23 09:27:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -S -c -O3 fcvt.c
最適化オプションをつけずにコンパイルすると、全ての引数をスタックに格納する処理が追加されていた。浮動小数点命令にだけ絞って読みたかったので最適化オプションをつけた。
XMMレジスタにおける慣習
- 最大8つの浮動小数点引数を%xmm0 ~ %xmm7を用いて渡すことができる。9つ目以降の引数はスタックを用いて渡される。
- 浮動小数点を返り値とする関数は%xmm0を用いて値を返す。
- XMMレジスタは全て呼び出し元退避レジスタである。
汎用レジスタと同様の考え方で用いられる。スタックポインタであるrsp以外の汎用レジスタとXMMレジスタでは呼び出し先で上書きされる可能性がある。
引数にポインタ、整数、浮動小数点が混在している場合の格納先は並び順と型で判別される。
double f1(int x, double y, long z);
x: edi, y: xmm0, z: rsi
double f2(double x, int y, long z);
x: xmm0, y: edi, y: xmm1
double f3(float x, double *y, long *z);
x: xmm0, y: rdi, z: rsi