浮動小数点の丸め誤差
浮動小数点
数値 \(x\) を浮動小数点で \begin{eqnarray} x &\simeq& \pm m\cdot 2^e \end{eqnarray} と近似表現する場合を考えます。ここで \(m\) は仮数で \(B\) bit の符号なし整数 \(0\leq m\lt 2^B\)、\(e\) は指数で符号付き整数です。
丸め誤差
仮数部ビット長 \(B=2\) の場合の浮動小数点の「丸め」を、関数としてグラフ化したものを以下に例示します。
丸め関数と、その誤差
(上段:丸め関数, 下段:誤差)
図から解るように、浮動小数点では \(2^m\) を境界にして絶対値が大きいほど誤差の「振れ幅」が段階的に大きくなりますが、 \(2^m\) と \(2^{m+1}\) の間は固定小数点と同様にノコギリ波状の誤差が発生します (ノコギリの歯の数は仮数部ビット数で決まります)。
仮数部 3 bit では 0.5 と 1 の間の数値は 0.5, 0.625, 0.75, 0.875, 1 と 0.125 刻みでしか表現できず、0捨1入してもワーストで \(\pm\)0.0625 の誤差を生じます。
さらに 1 と 2 の間のワースト誤差は\(\pm\)0.125、 2 と 4 の間は \(\pm\)0.25 、 4 と 8 の間は \(\pm\)0.5 … という具合に誤差が倍々になっていきます。
このため浮動小数点の精度が高いのは 0 付近のみで、信号に直流成分が乗っていると、思ったほどの精度は得られません。
丸め誤差の例
仮数部 24bit の浮動小数点 (C/C++ の float に相当) でも無視できないほど大きな誤差を生じることがあります。
例えば、サンプリング周波数 48000[kHz] で 10 分間、1[kHz] の正弦波を鳴らしたいと思って、うっかり
const float FS = 48000; const float dT = 1/FS; const float f = 1000; const float sec = 10*60; for (float t=0; t<sec; t+=dT) { // sinf() は sin() の float 版 float y = sinf(2*M_PI*f*t); : : }
浮動小数点で信号処理を行う場合
浮動小数点で信号処理を行う場合、大雑把にいうと誤差の大きさは浮動小数点数の指数部で決まりますので、扱う波形の振幅確率密度関数によって発生している誤差のパワーをある程度推定できます。