単精度浮動小数点数

 単精度浮動小数点の表現方法、最近接偶数方向丸め、加算時の丸め誤差についてまとめる。

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 での単精度浮動小数点数の形式では
の 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言語でも用いられている。
 この最近接偶数方向丸めでは
 簡単のため仮数部が 3 bit、数値が 4 bitの場合を考える( $\mid$ の右側は溢れた bit を表す)。

3. 浮動小数点の加算と丸め誤差

 浮動小数点の加算[2]は次のように行われる。

  1. 10 進数を内部表現に変換する。[3, 4]
  2. 仮数部の先頭に 1 を追加し、24 bit とする。
  3. 桁合わせのために指数部の差を求める。
  4. 小さい方の数の仮数部を指数部の差の分だけ右にシフトする。
  5. 最近接偶数方向丸めに従って仮数部を丸める(この段階で丸め誤差が発生)。[1]
  6. 仮数部の加算を行い、和の内部表現を得て、それを 10 進数に変換する。[3, 4]
 以下では
の加算を 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)

参考文献

[1] 浮動小数点の丸めの方向と性質 (1)
[2] 浮動小数点加算器 - 桁合わせと加算
[3] 情報の表現
[4] 浮動小数点数内部表現シミュレーター
inserted by FC2 system