まず第一に、浮動小数点値の動作は「ランダム」ではありません。正確な比較は、現実世界の多くの用途で意味をなす可能性があり、実際に意味があります。ただし、浮動小数点を使用する場合は、その動作を認識する必要があります。浮動小数点が実数のように機能すると仮定すると、すぐに壊れるコードが得られます。浮動小数点の結果に大きなランダムファズが関連付けられていると仮定する側で誤りを犯すと(ここでのほとんどの回答が示唆するように)、最初は機能しているように見えますが、最終的には大きなエラーと壊れたコーナーケースが発生するコードが得られます。
まず第一に、浮動小数点でプログラミングしたい場合は、これを読むべきです:
すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと
はい、全部読んでください。それが負担が大きすぎる場合は、読む時間ができるまで、計算に整数/固定小数点を使用する必要があります。:-)
さて、そうは言っても、正確な浮動小数点比較の最大の問題は次のようになります。
scanf
ソースに書き込んだり、またはstrtod
で読み取ったりする可能性のある多くの値は、浮動小数点値として存在せず、最も近い近似値に静かに変換されるという事実。これは、demon9733 の回答が話していたことです。
実際の結果を表すのに十分な精度がないために、多くの結果が丸められるという事実。これを確認できる簡単な例は、フロートとしてx = 0x1fffffe
とを追加することです。y = 1
ここでx
は、仮数部に 24 ビットの精度があり (ok)、y
1 ビットしかありませんが、それらを加算すると、それらのビットは重複する場所にないため、結果には 25 ビットの精度が必要になります。代わりに、丸められます (0x2000000
デフォルトの丸めモードで)。
正しい値を得るために無限に多くの場所が必要なため、多くの結果が丸められるという事実。これには、1/3 のような有理数の結果 (10 進数では無限に多くの桁をとることからおなじみです) と 1/10 (5 は 2 の累乗ではないため、2 進数でも無限の桁をとります) の両方が含まれます。完全な平方ではないものの平方根のような不合理な結果も同様です。
二重丸め。一部のシステム (特に x86) では、浮動小数点式は公称型よりも高い精度で評価されます。これは、上記のタイプの丸めのいずれかが発生した場合、2 つの丸めステップを取得することを意味します。最初に結果をより精度の高いタイプに丸め、次に最終的なタイプに丸めます。例として、1.49 を整数 (1) に丸めると 10 進数でどうなるか、最初に小数点以下 1 桁 (1.5) に丸め、その結果を整数 (2) に丸めるとどうなるかを考えてみましょう。コンパイラの動作 (特に GCC のようなバグのある非準拠のコンパイラの場合) は予測できないため、これは実際には浮動小数点で処理するのが最も厄介な領域の 1 つです。
超越関数 ( trig
、exp
、log
など) は、結果が正しく丸められるように指定されていません。結果は、精度の最後の桁 (通常は1ulpと呼ばれます) で 1 単位以内で正しいと指定されているだけです。
浮動小数点コードを作成するときは、結果が不正確になる原因となる数値の扱いに注意し、それに応じて比較を行う必要があります。多くの場合、「イプシロン」と比較することは理にかなっていますが、そのイプシロンは、絶対定数ではなく、比較する数値の大きさに基づいている必要があります。(絶対定数イプシロンが機能する場合は、浮動小数点ではなく固定小数点が適切なツールであることを強く示しています!)
編集:特に、マグニチュード相対イプシロン チェックは次のようになります。
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
FLT_EPSILON
定数はどこからのもので ( for s またはfor sfloat.h
に置き換えてください)、計算の累積誤差が最後の桁の単位によって確実に制限されるように選択した定数です(エラーが発生したかどうかわからない場合)バインドされた計算が正しく、あなたの計算がそうあるべきだと言っているよりも数倍大きくしてください)。DBL_EPSILON
double
LDBL_EPSILON
long double
K
K
K
FLT_EPSILON
最後に、これを使用する場合、非正規化には意味がないため、ゼロに近い特別な注意が必要になる場合があることに注意してください。簡単な修正は、次のようにすることです。
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
DBL_MIN
double を使用する場合も同様に置き換えます。