浮動小数点数 (C では float と double を含む) は 2 つの部分で表され、どちらも値を保持する固定数のビットを持ちます。
- 仮数と呼ばれる 2 進小数 (2 進小数点の左側にビットがなく、2 進小数点のすぐ右側にゼロがない)。(これは数値の 10 進数表現と比較します。小数点の左側の桁は 2 進小数点の左側のビットと比較でき、小数点の右側の小数桁は小数バイナリ ビットと比較して、 2 進小数点の右側)。
- その仮数に掛ける 2 の累乗を示す指数。(これを科学表記法 0.1e5 と比較すると、仮数に掛ける 10 の累乗を示す指数として 5 があります)
10 進数では、端数の 1/3 を固定の小数桁数で表すことはできません。たとえば、3 は無限に繰り返す必要があるため、0.333333 は 1/3 と正確には等しくありません。
2 進数では、固定数の小数ビットで小数 1/10 を表すことはできません。この場合、2 進数 0.00011001100110011 は、0011 を無期限に繰り返す必要があるため、1/10 と正確には等しくありません。したがって、1/10 が浮動小数点に変換されると、この部分は使用可能なビットに合わせて切り取られます。
バイナリでは、分母 (底) が 10 で割り切れる分数は無限に繰り返されます。これは、多くの float 値が不正確であることを意味します。
追加すると、それらは不正確になります。それらをたくさん足し合わせると、無限に繰り返される 2 進分数を固定桁数の 2 進分数に変えたときに切り落とされたビットの値に応じて、不正確さがキャンセルまたは強化される可能性があります。
また、大きな数、桁数の多い分数、または非常に異なる数を追加する場合にも不正確になります。たとえば、10 億プラス .0000009 は、使用可能なビット数では表現できないため、端数は四捨五入されます。
複雑になっていることがわかります。特定のケースでは、浮動小数点表現を考え出し、切り捨てられたビットと乗算または除算時の丸めによるエラーを評価できます。その時点で、トラブルに遭遇した場合になぜそれが間違っているのかが正確にわかります。
簡単な例 - 不正確な表現
指数を無視し、仮数を正規化しない例を次に示します。つまり、左側のゼロは削除されません。(0.0001100 = 1/10 および 0.0011001 = 1/20 は、7 ビットの後にチョップされた場合)
0.0001100 = 1/10
0.0001100
0.0001100
0.0001100 0.0011001 = 2/10 (1/5)
0.0001100 0.0011001
0.0001100 0.0011001
--------- ---------
00 <- sum of right 2 columns 11 <- sum of right column
11000 <- sum of next column 00 <- sum of next two columns
110 <- sum of next column 11 <- sum of next column
000 <- sum of other columns 11 <- sum of next column
------- 000 <- sum of other columns
0.1001000 <- sum ---------
0.1001011 <- sum
私の例の7ビットに収まらない0.12345678901234567890のような分数でも同じ問題が発生する可能性があります。
何をすべきか
まず、浮動小数点数は正確ではない可能性があることに注意してください。加算または減算、さらには乗算または除算を行うと、不正確な結果が生じることが予想されます。
第 2 に、2 つの float (または double) 値を比較するときは、その差を何らかの「イプシロン」と比較するのが最善です。したがって、万一、米ドルの計算を float 変数に格納しているとしたら、次のようになります。0.5 セント未満のものは気にしません。
if (fabsf(f1 - f2) >= 0.005f) ...
これは、数値が互いに近く、目的のために十分に近いことを意味します。(@EricPostpischil は、「十分に近い」という一般的な定義はないことを指摘しています。それは、計算が達成したいことと関係があります。)
いくつかの小さな値との比較を行うと、浮動小数点演算が行われた後に下位の小数桁にある可能性があるすべての緩いビットが処理されます。
定数と比較すると、似ていることに注意してください。
if (fabsf(f1 - 1.0f) >= 0.000001f) ...
または、同じ範囲の違いを確認するために 2 つの比較を行うことができます。
if (f1 < 0.999999f || f1 > 1.000001f) ...
繰り返しますが、各問題には独自の興味深い小数部の桁数があることを指摘しておきます。
たとえば、Google が地球上の 2 つの位置の距離をキロメートルで表示する場合、0.001 (1000 分の 1 キロメートル) 以内の任意の 2 つの位置は機能的に同一であると言えます。差を 0.0005 と比較します。または、最も近いブロックだけを気にする場合は、差を 0.03 (300 メートル) と比較します。その差を 0.015 と比較してください。
同じことは、測定ツールの精度が非常に高い場合にも当てはまります。物差しで測定する場合、結果が 1/100 インチまで正確であるとは思わないでください。