Closed20

浮動小数点

tkomatsutkomatsu

IEEE浮動小数点の標準規格では数vを以下の形式で表す

v = (-1)^s \times m \times 2^E
  • s: 符号
  • M: 仮数
  • E: 指数
tkomatsutkomatsu

浮動小数点では上記の値をエンコードするため、ビット表現を以下の3つに分ける

  • 1ビットの符号ビットs -> 符号s
  • kビットの指数フィールドexp -> 指数E
  • nビットの少数フィールドfrac -> 仮数M
tkomatsutkomatsu

Case1 正規化

expのビットパターンが全て0あるいは全て1のいずれでもない場合 (!(0000 || 1111))

E = 1 - Bias
Bias = 2^{k-1} - 1

fracは 0 \leq f<1 であるfを表すと、

M = 1 + f
tkomatsutkomatsu

Case3 Inf or Nan

指数フィールドが全て1である
少数フィールドが全て0の場合は、 -\infty or \infty
少数フィールドが全て0でない場合は、Nan

tkomatsutkomatsu

単精度

説明 exp frac 十進法
ゼロ 00...00 0...00 0 0.0
最小の非正規化 00...00 0...01 2^{-23} \times 2^{-126} 1.4 \times 10^{-45}
最大の非正規化 00...00 1...11 (1 - \epsilon ) \times 2^{-126} 1.2 \times 10^{-38}
最小の正規化 00...01 0...00 1 \times 2^{-126} 1.2 \times 10^{-38}
1 01...11 0...00 1 \times 2^{0} 1.0
最大の正規化 11...10 1...11 (2 - \epsilon) \times 2^{127} 3.4 \times 10^{38}
tkomatsutkomatsu

最大の非正規化と最小の非正規化が連続する理由は、非正規化数における E の取り方によるもの。-Bias ではなく、1-Biasとすることで非正規化の仮数が暗黙の先行する1を持たない。

tkomatsutkomatsu

暗黙の先行する1とは

正規化数において、fは二進小数点が最上位ビットの左にある二進表現0.f_{n-1}f_{n-2}...f_0。Mは1.f_{n-1}f_{n-2}...f_0としてみることができる。この先頭につく1を暗黙の先行する1とよぶ。

tkomatsutkomatsu

丸めモードのうち、デフォルトは偶数丸め。
最下位桁が偶数であるように切り上げ、切り下げを行う。

1.50 -> 2
2.50-> 2
3.50 -> 4
4.50 -> 4

偶数丸めを使うのは統計的な偏りを避けるため。

tkomatsutkomatsu

二進数における丸め計算
最も近い2分の1(二進小数点の右側に1ビット)に丸める
A) 10.010 (2 \frac{1}{4}) -> 10.00 -> 10.0 (2)
B) 10.011 (2 \frac{3}{8}) -> 10.10 -> 10.1 (2 \frac{1}{2})
C) 10.110 (2 \frac{3}{4}) -> 11.00 -> 11.0 (3)
D) 11.001 (3 \frac{1}{8}) -> 11.00 -> 11.0 (3)

tkomatsutkomatsu

A

10.010 は 10.0100。下位3ビットをみると、丸めの境界。右から4ビット目は0であるため、偶数である。よって下位3ビットを切り捨てる。

tkomatsutkomatsu

B

10.011 は 10.0110。下位3ビットをみると境界ではないので、通常通りに丸める。よって切り上げ。

tkomatsutkomatsu

C

10.110 は 10.1100。下位3ビットに着目すると丸めの境界。右から4ビット目は1であるため、機数にあたることがわかる。よって下位3ビットは繰り上げになる。

tkomatsutkomatsu

D

11.001 は 11.0010。下位3ビットに着目すると丸めの境界ではない。よって通常通り切り捨てる。

tkomatsutkomatsu

Cの標準規格ではIEEE浮動小数点を用いることは定められていない。
floatあるいはdoubleからintへのキャストの挙動もC標準規格で定められていない。

tkomatsutkomatsu

浮動小数点データは拡張命令セットで処理される。
Pentium/MMXから始まり、SSE、AVXなど。
AVX浮動小数点アーキテクチャでは、%ymm0 ~ %ymm15という名前のRMMレジスタにデータが保存される。レジスタのサイズは256ビット(32バイト)である。

tkomatsutkomatsu

C言語で書かれたコードからコンパイルしたアセンブリを読んだ。
アセンブリにコメントを追加して命令を追う。

fcvt.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;
}

fcvt.s
	.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

最適化オプションをつけずにコンパイルすると、全ての引数をスタックに格納する処理が追加されていた。浮動小数点命令にだけ絞って読みたかったので最適化オプションをつけた。

tkomatsutkomatsu

XMMレジスタにおける慣習

  • 最大8つの浮動小数点引数を%xmm0 ~ %xmm7を用いて渡すことができる。9つ目以降の引数はスタックを用いて渡される。
  • 浮動小数点を返り値とする関数は%xmm0を用いて値を返す。
  • XMMレジスタは全て呼び出し元退避レジスタである。

汎用レジスタと同様の考え方で用いられる。スタックポインタであるrsp以外の汎用レジスタとXMMレジスタでは呼び出し先で上書きされる可能性がある。

tkomatsutkomatsu

引数にポインタ、整数、浮動小数点が混在している場合の格納先は並び順と型で判別される。

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

このスクラップは2021/08/15にクローズされました