精度と誤差の発生要因
浮動小数点数は、コンピュータが有限のメモリで実数を近似的に表現するための仕組みです。しかしこの近似には限界があり、さまざまな誤差が発生します。特に数値計算においては、これらの誤差が結果に大きな影響を与えることがあるため、理解しておくことが重要です。本章では、浮動小数点数における代表的な誤差の発生要因について解説します。
10進数を2進数で表現する際の丸め誤差(rounding error)
コンピュータは内部的に2進数で数値を扱いますが、10進数の小数は2進数で正確に表現できない場合が多々あります。たとえば0.1
といったシンプルな10進数の小数でさえ、2進数では0.000110011...
と無限に続く循環小数となり、有限のビット数では表現を打ち切って近似的に数値を丸めるしかありません。このような丸めにより、演算結果やその表示において微小な誤差が生じる可能性があります。
double x = 0.1;
Console.WriteLine(x); // 出力: 0.10000000000000000555...
対策
- .NETにおける
decimal
型のような10進数の固定小数点数を使用する
演算結果の丸め誤差とその蓄積
演算を行う際にも丸め誤差が発生します。特に、乗除算や加減算を繰り返すと、丸め誤差が蓄積され、最終的な結果に大きな影響を与えることがあります。たとえば、非常に小さい値を大量に加算する場合、丸め誤差が無視できないレベルになることがあります。
double a = 1.0 / 3.0;
Console.WriteLine(a); // 出力: 0.3333333333333333
この値は実際の 1/3 とは異なり、丸められた近似値です。
対策
float
よりもdouble
といった精度の高い型を使用する。- 丸め誤差の蓄積を避けるため、演算順序を工夫する。
桁落ち(loss of significance)
非常に近い値同士の引き算では、有効桁が失われることがあります。これは桁落ちと呼ばれ、数値解析で特に問題になります。
double a = 1.000001;
double b = 1.000000;
double result = a - b;
Console.WriteLine(result); // 0.000001 だが精度が落ちる可能性あり
対策
- 式の変形によって桁落ちの影響を抑制、軽減する。
- 安定なアルゴリズム(数値解析での工夫)を選択する。
情報落ち(cancellation)
大きな値と小さな値を加算する際、小さな値が無視されることがあります。これは情報落ちと呼ばれます。
double large = 1e16;
double small = 1;
double result = large + small;
Console.WriteLine(result == large); // True
対策
- 多くの数の加算を行う場合は小さい値から先に加算する。
- 値のスケーリングや正規化を行う。
実例:0.1 + 0.2 != 0.3
の原因と解説
0.1
や0.2
は2進数で正確に表現できず誤差を含んだ状態で加算されます。また、0.3
も同様に2進数で表現できないため、結果として0.1 + 0.2
は0.3
とは異なる値になります。
double a = 0.1;
double b = 0.2;
double c = 0.3;
Console.WriteLine(a + b == c); // False
Console.WriteLine(a + b); // 0.30000000000000004
対策
- 浮動小数点数を使用する場合は
Math.Abs(a + b - c) < ε
のような誤差許容比較を行う。 decimal
型 (10進数ベースの型) を使用する。
コンパイラやランタイムによる最適化の影響
最適化によって、数値演算の順序や精度が変わることがあります。特にC++では、コンパイラの最適化オプション(例:-ffast-math)が演算結果に影響を与える可能性があります。
double a = 1e16;
double b = 1.0;
double c = -1e16;
double result = (a + b) + c; // 最適化により (a + c) + b になる可能性あり
このような変更により、桁落ちや情報落ちが発生することがあります。
対策
- 最適化オプションを明示的に制御する。
- パフォーマンスより精度が重要な部分では、volatileや#pragmaなどで最適化を抑制する。
クロスプラットフォーム開発時の注意点(例:x86 vs x64)
同じコードでも、32bit(x86)と64bit(x64)環境では浮動小数点演算の精度やレジスタの扱いが異なる場合があります。
例:x86とx64での違い
- x86では、FPU(浮動小数点ユニット)が80bit精度を使うことがある。
- x64では、SSE2命令により64bit精度に固定されることが多い。
対策
- 精度が重要な処理では、処理系や環境ごとの挙動をテストする。