単精度浮動小数点数
単精度浮動小数点の表現方法、最近接偶数方向丸め、加算時の丸め誤差についてまとめる。
1. 単精度浮動小数点数 (float)
一般に 2 進 $p$ 桁の浮動小数点は以下のように表現される:
\begin{eqnarray}
& & (-1)^{s}\times\left(1+\dfrac{m_{1}}{2^{1}}+\dfrac{m_{2}}{2^{2}}+\,\cdots\,+\dfrac{m_{p-1}}{2^{p-1}}\right)\times2^{e}\nonumber \\
& & =(-1)^{s}\left(1.m_{1}m_{2}\,\cdots\,m_{p-1}\right)_{2}\times2^{e}\label{eq:1}\\
& & =(\text{符号部})\times(\text{仮数部})\times2^{(\text{指数部})}\nonumber
\end{eqnarray}
ただし
\[
s,\,m_{i}\in\left\{ 0,\,1\right\} ,\,e\in Z
\]
である。
IEEE 754 での単精度浮動小数点数の形式では
- 符号ビット : 1 ビット
- 指数部の幅 : 8 ビット
- 仮数部の幅 : 23 ビット
の 32 bit を用いて以下のように表現している(図1)。式(\ref{eq:1})の仮数部はつねに $1$ から始まるので、この 1 を省略して残りの部分を 23
bitで表す。このためIEEE 754 の仮数部は実質 24 bitを表すので、10 進に換算したとき表現できる桁数は $\log_{10}(2^{24})\thickapprox7.225$
桁となり有効数字は 7 桁となる。
また、図1 の exponent の 8 bit を 10 進数に変換したものから 127 を引いた値が式(\ref{eq:1})の指数部となる。すなわち
\begin{equation}
(指数部)=(\mathrm{exponent\,8\,\mathrm{bit}})_{10}-127\label{eq:2}
\end{equation}
である。
図1 IEEE 754 での binary32 の標準化されたレイアウト
図1 の場合を 10 進数で表すと、指数部が
\[
\left(2^{6}+2^{5}+2^{4}+2^{3}+2^{2}\right)-127=124-127=-3
\]
となるので
\[
(-1)^{0}\times\left(1+\dfrac{1}{2^{2}}\right)\times2^{-3}=\dfrac{5}{4}\times\dfrac{1}{8}=\dfrac{5}{32}=0.15625
\]
となる。
2. 最近接偶数方向丸め (round to nearest even)
IEEE 754 の仮数部は実質 24 bitであるので、24 bit を超える場合は 24 bitに丸めることが必要となる。この丸め方の一つが
最近接偶数方向丸め
(RN : round to nearest even)[1]である。この方法はC言語でも用いられている。
この最近接偶数方向丸めでは
- (I) 数値が最近接の2数の浮動小数点のちょうど真ん中の数以外
$\Rightarrow$ 最近接の2数の浮動小数点のうち、最も近い浮動小数点数に選択する。
- (II) 数値が最近接の2数の浮動小数点のちょうど真ん中の数
$\Rightarrow$ 最近接の2数の浮動小数点のうち、仮数部の最下位が 偶数 (0) の方を選択する。
簡単のため仮数部が 3 bit、数値が 4 bitの場合を考える( $\mid$ の右側は溢れた bit を表す)。
- $x=1.00\mid0$ の場合:最近接する 2 つの浮動小数点 $x_{1},\,x_{2}$ とそのちょうど真ん中の値 $x_{\mathrm{mid}}$ は
\begin{eqnarray*}
x_{1} & = & 1.00\\
x_{2} & = & 1.01\\
x_{\mathrm{mid}} & = & 1.00\mid1
\end{eqnarray*}
であり、$x < x_{\mathrm{mid}}$ は (I) の場合に相当するので、最も近い浮動小数点 $x_{1}=1.00$ を選択し、$x=1.00$ と丸められる。
- $x=1.00\mid1$ の場合:最近接する 2 つの浮動小数点 $x_{1},\,x_{2}$ とそのちょうど真ん中の値 $x_{\mathrm{mid}}$ は
\begin{eqnarray*}
x_{1} & = & 1.00\\
x_{2} & = & 1.01\\
x_{\mathrm{mid}} & = & 1.00\mid1
\end{eqnarray*}
であり、$x=x_{\mathrm{mid}}$ は (II) の場合に相当するので、仮数部の最下位が 偶数 (0) の $x_{1}=1.00$ を選択し、$x=1.00$ と丸められる。
- $x=1.01\mid0$ の場合:最近接する2つの浮動小数点 $x_{1},\,x_{2}$ とそのちょうど真ん中の値 $x_{\mathrm{mid}}$ は
\begin{eqnarray*}
x_{1} & = & 1.01\\
x_{2} & = & 1.10\\
x_{\mathrm{mid}} & = & 1.01\mid1
\end{eqnarray*}
であり、$x < x_{\mathrm{mid}}$ は (I) の場合に相当するので、最も近い浮動小数点 $x_{1}=1.01$ を選択し、$x=1.01$ と丸められる。
- $x=1.01\mid1$ の場合:最近接する 2 つの浮動小数点 $x_{1},\,x_{2}$ とそのちょうど真ん中の値 $x_{\mathrm{mid}}$ は
\begin{eqnarray*}
x_{1} & = & 1.01\\
x_{2} & = & 1.10\\
x_{\mathrm{mid}} & = & 1.01\mid1
\end{eqnarray*}
であり、$x=x_{\mathrm{mid}}$ は (II) の場合に相当するので、仮数部の最下位が 偶数 (0) の $x_{2}=1.10$ を選択し、$x=1.10$ と丸められる。
3. 浮動小数点の加算と丸め誤差
- 10 進数を内部表現に変換する。[3, 4]
- 仮数部の先頭に 1 を追加し、24 bit とする。
- 桁合わせのために指数部の差を求める。
- 小さい方の数の仮数部を指数部の差の分だけ右にシフトする。
- 最近接偶数方向丸めに従って仮数部を丸める(この段階で丸め誤差が発生)。[1]
- 仮数部の加算を行い、和の内部表現を得て、それを 10 進数に変換する。[3, 4]
以下では
- 16777216.0 + 1.0
- 16777216.0 + 2.0
- 16777216.0 + 3.0
- 16777216.0 + 1.1
の加算を C 言語で計算した場合の丸め誤差の発生原因について考える。
int main()
float a = 1 << 24; // 16777216
float b = 1; // b の値を変化させて丸め誤差の影響を調べる
float c;
c = a + b;
printf("a = %f \n", a);
printf("b = %f \n", b);
printf("c = %f \n", c);
return 0;
}
// results
a = 16777216.000000
b = 1.000000
c = 16777216.000000
a = 16777216.000000
b = 2.000000
c = 16777218.000000
a = 16777216.000000
b = 3.000000
c = 16777220.000000
a = 16777216.000000
b = 1.100000
c = 16777218.000000
3.1 $a=16777216,\,b=1\;\Rightarrow\;a+b=16777216$
$a=16777216$ と $b=1$ の指数部の差は $24$ bitとなるため、右シフト後の $b$ の仮数部は
\[
b\rightarrow b'=(0000\,0000\,0000\,0000\,0000\,0000\mid1000\,0000\,0000\,0000\,0000\,0000)_{2}
\]
となる。この $b'$ に最隣接する $2$ つの浮動小数点 $b_{1},\,b_{2}$ とそのちょうど真ん中の値 $b_{\mathrm{mid}}$ は
\begin{eqnarray*}
b_{1} & = & (0000\,0000\,0000\,0000\,0000\,0000)_{2}\\
b_{2} & = & (0000\,0000\,0000\,0000\,0000\,0001)_{2}\\
b_{\mathrm{mid}} & = & (0000\,0000\,0000\,0000\,0000\,0000\mid1)_{2}
\end{eqnarray*}
である。この場合、$b'=b_{\mathrm{mid}}$ であるので (II) の場合に相当し、$b_{1}$ が選択され
\[
b=(0000\,0000\,0000\,0000\,0000\,0000)_{2}
\]
と丸められる。この場合には $0$ が仮数部に加算されることになるので、加算の結果は $16777216$ となり値が変化しない。
図2 浮動小数点の加算と丸め誤差 (1)
したがって、以下に示すような $1.0$ を $n=20000000$ 回加えるような計算を行った場合、float 型では $16777216.0$ を超えると $1.0$ を加えても丸め誤差のため値が変化しないので、注意が必要である。
int main()
{
int n = 20000000;
float sum1 = 0.0;
int sum2 = 0;
for (int j = 0; j < n; j++) {
sum1 += 1.0;
sum2 += 1;
}
printf("sum1 = %f \n", sum1);
printf("sum2 = %d \n", sum2);
return 0;
}
// results
sum1 = 16777216.000000
sum2 = 20000000
この場合には、float を double へ変更することで正しく計算できる。
3.2 $a=16777216,\,b=2\;\Rightarrow\;a+b=16777218$
$a=16777216$ と $b=2$ の指数部の差は $23$ bitとなるため、右シフト後の $b$ の仮数部は
\[
b\rightarrow b'=(0000\,0000\,0000\,0000\,0000\,0001\mid0000\,0000\,0000\,0000\,0000\,000)_{2}
\]
となる。この $b'$ に最隣接する $2$ つの浮動小数点 $b_{1},\,b_{2}$ とそのちょうど真ん中の値 $b_{\mathrm{mid}}$ は
\begin{eqnarray*}
b_{1} & = & (0000\,0000\,0000\,0000\,0000\,0001)_{2}\\
b_{2} & = & (0000\,0000\,0000\,0000\,0000\,0010)_{2}\\
b_{\mathrm{mid}} & = & (0000\,0000\,0000\,0000\,0000\,0001\mid1)_{2}
\end{eqnarray*}
である。この場合、$b' < b_{\mathrm{mid}}$ であるので (I) の場合に相当し、$b_{1}$ が選択され
\[
b=(0000\,0000\,0000\,0000\,0000\,0001)_{2}
\]
と丸められる。この $b$ が仮数部に加算され、加算結果の内部表現は
\[
a+b=(0\,100\,1011\,1\,000\,0000\,0000\,0000\,0000\,0001)_{2}
\]
となる。これを $10$ 進数に変換すれば $16777218$ となる。
図3 浮動小数点の加算と丸め誤差 (2)
3.3 $a=16777216,\,b=3\;\Rightarrow\;a+b=16777220$
$a=16777216$ と $b=3$ の指数部の差は $23$ bitとなるため、右シフト後の $b$ の仮数部は
\[
b\rightarrow b'=(0000\,0000\,0000\,0000\,0000\,0001\mid1000\,0000\,0000\,0000\,0000\,000)_{2}
\]
となる。この $b'$ に最隣接する $2$ つの浮動小数点 $b_{1},\,b_{2}$ とそのちょうど真ん中の値 $b_{\mathrm{mid}}$ は
\begin{eqnarray*}
b_{1} & = & (0000\,0000\,0000\,0000\,0000\,0001)_{2}\\
b_{2} & = & (0000\,0000\,0000\,0000\,0000\,0010)_{2}\\
b_{\mathrm{mid}} & = & (0000\,0000\,0000\,0000\,0000\,0001\mid1)_{2}
\end{eqnarray*}
である。この場合、$b'=b_{\mathrm{mid}}$ であるので (II) の場合に相当し、$b_{2}$ が選択され
\[
b=(0000\,0000\,0000\,0000\,0000\,0010)_{2}
\]
と丸められる。この $b$ が仮数部に加算され、加算結果の内部表現は
\[
a+b=(0\,100\,1011\,1\,000\,0000\,0000\,0000\,0000\,0010)_{2}
\]
となる。これを $10$ 進数に変換すれば $16777220$ となる。
図4 浮動小数点の加算と丸め誤差 (3)
3.4 $a=16777216,\,b=1.1\;\Rightarrow\;a+b=16777218$
$a=16777216$ と $b=1.1$ の指数部の差は $24$ bitとなるため、右シフト後の $b$ の仮数部は
\[
b\rightarrow b'=(0000\,0000\,0000\,0000\,0000\,0000\mid1000\,1100\,1100\,1100\,1100\,1100)_{2}
\]
となる。この $b'$ に最隣接する $2$ つの浮動小数点 $b_{1},\,b_{2}$ とそのちょうど真ん中の値 $b_{\mathrm{mid}}$ は
\begin{eqnarray*}
b_{1} & = & (0000\,0000\,0000\,0000\,0000\,0000)_{2}\\
b_{2} & = & (0000\,0000\,0000\,0000\,0000\,0001)_{2}\\
b_{\mathrm{mid}} & = & (0000\,0000\,0000\,0000\,0000\,0000\mid\1)_{2}
\end{eqnarray*}
である。この場合、$b'>b_{\mathrm{mid}}$ であるので (I) の場合に相当し、$b_{2}$ が選択され
\[
b=(0000\,0000\,0000\,0000\,0000\,0001)_{2}
\]
と丸められる。この $b$ が仮数部に加算され、加算結果の内部表現は
\[
a+b=(0\,100\,1011\,1\,000\,0000\,0000\,0000\,0000\,0001)_{2}
\]
となる。これを$10$進数に変換すれば $16777218$ となる。
図5 浮動小数点の加算と丸め誤差 (5)
参考文献