web-dev-qa-db-ja.com

ループのカウンター変数として「double」を使用する

私が現在読んでいる本には、次の抜粋があります。

浮動小数点値をループカウンターとして使用することもできます。この種類のカウンターを使用したforループの例を次に示します。

double a(0.3), b(2.5);
for(double x = 0.0; x <= 2.0; x += 0.25)
    cout << "\n\tx = " << x << "\ta*x + b = " << a*x + b;

このコードフラグメントは、a*x+bから0.0までのxの値に対する2.0の値を、0.25のステップで計算します。ただし、ループで浮動小数点カウンタを使用する場合は注意が必要です。多くの10進値は2進浮動小数点形式で正確に表すことができないため、累積値によって差異が生じる可能性があります。これは、ループの終了が浮動小数点ループカウンターが正確な値に到達することに依存するようにforループをコーディングしないことを意味します。たとえば、次の不適切に設計されたループは終了しません。

for(double x = 0.0 ; x != 1.0 ; x += 0.2)
    cout << x;

このループの目的は、0.0から1.0まで変化するxの値を出力することです。ただし、0.2には2進浮動小数点値としての正確な表現がないため、xの値が正確に1になることはありません。したがって、2番目のループ制御式は常にfalseであり、ループは無期限に継続します。

誰かが最初のコードブロックがどのように実行され、2番目のコードブロックが実行されないかを説明できますか?

45
afaolek

xexactly 2.0 ...に達していなくても、最初の1つは最終的に終了します。結局は2.0よりもgreaterになり、したがって、発生します。

2つ目は、ブレークするためにxヒット正確に 1.0にする必要があります。

最初の例で0.25のステップを使用しているのは残念ですが、これは2進浮動小数点で正確に表現できます。両方の例でステップサイズとして0.2を使用する方が賢明でしょう。 (0.2は2進浮動小数点で正確に表現できません。)

71
Jon Skeet

最初のブロックは、以下の条件(<=)。

浮動小数点が不正確であっても、最終的には誤りになります。

15
SLaks

これは、より広範な問題の例です-ダブルを比較するとき、正確な等価性ではなく、等価性をチェックする必要がある場合があります許容範囲内

場合によっては、通常、変更されていないデフォルト値をチェックし、同等であることで問題ありません。

double x(0.0); 
// do some work that may or may not set up x

if (x != 0.0) {   
    // do more work 
}

ただし、一般に、期待値に対するチェックはそのようには実行できません。次のようなものが必要です。

double x(0.0); 
double target(10000.0);
double tolerance(0.000001);
// do some work that may or may not set up x to an expected value

if (fabs(target - x) < tolerance) {   
    // do more work 
}
9
Steve Townsend

浮動小数点数は内部では2進数として表され、ほとんどの場合、IEEE形式で表されます。

http://babbage.cs.qc.edu/IEEE-754/

たとえば、バイナリの0.25は0.01b +1.00000000000000000000000 * 2として表されます-2

これは、符号用に1ビット、指数用に8ビット(-127〜+128の値を表す)、および値用に23ビット(先頭の1.は格納されません)で内部的に格納されます。実際、ビットは次のとおりです。

[0] [01111101] [00000000000000000000000]

1/3が10進数で正確に表現されないのと同様に、2進数で0.2は正確に表現されません。

ここでの問題は、1/2が0.5として10進形式で正確に表現できるのと同じですが、1/3は0.3333333333に近似することしかできず、0.25は2進分数として正確に表現できますが、0.2はそうではありません。バイナリでは、0.0010011001100110011001100 ....b 最後の4桁が繰り返される場所。

コンピューターに保存するには、0.0010011001100110011001101にラウドされます。b。これは本当に非常に近いので、座標など絶対値が重要なものを計算する場合は問題ありません。

残念ながら、その値を5回追加すると、1.00000000000000000000001になります。b。 (または、0.2を0.00100に切り捨てた場合11001100110011001100b 代わりに、0.11111111111111111111100を取得しますb

どちらの方法でも、ループ条件が1.00000000000000000000001の場合b== 1.00000000000000000000000b 終了しません。代わりに<=を使用する場合、値が最後の値のすぐ下にある場合、1回余分に実行される可能性がありますが、停止します。

小さな10進値(小数点以下2桁のみの値など)を正確に表すことができるフォーマットを作成することが可能です。これらは財務計算などで使用されます。しかし、通常の浮動小数点値はそのように機能します。0.2のようないくつかの小さな「簡単な」数値を表す能力を、一貫した方法で広範囲を表す能力と交換します。

その正確な理由から、フロートをループカウンターとして使用しないことは一般的です。一般的な解決策は次のとおりです。

  • 追加のイテレーションが1つ問題ない場合は、<=を使用します
  • 問題がある場合は、代わりに条件を<= 1.0001にするか、他の値を増分より小さくして、0.0000000000000000000001によるエラーは問題にならないようにします
  • 整数を使用し、ループ中にそれを何かで割る
  • 分数の値を正確に表すために特別に作成されたクラスを使用する

コンパイラーがfloat "="ループを最適化してそれを意図したとおりに発生させることは可能ですが、それが標準で許可されているのか、それとも実際に発生するのかはわかりません。

6
Jack V.

この例には複数の問題があり、2つの点がケース間で異なります。

  • 浮動小数点の等価性を含む比較には、ドメインの専門知識が必要であるため、ループ制御に<または>を使用する方が安全です。

  • ループ増分0.25は実際にはdoesは正確な表現を持っています

  • ループの増分0.2notが正確に表現します

  • したがって、0.25(または1.0)の増分の合計を正確にチェックすることは可能ですが、正確にチェックすることはできません。単一0.2の増分でも一致します。

一般的な規則がよく引用されます:浮動小数点数の等価比較を行わないでください。これは、一般的なアドバイスですが、整数または整数と½+¼で構成される分数...正確な表現が期待できます。

そして、あなたはなぜを尋ねましたか?端的に言えば、分数は½+¼...として表されるため、2の累乗に因数分解できないため、ほとんどの10進数は正確な表現を持ちません。これは、FP内部表現は、出力の期待値に丸めするが実際にはそうではないビットの長い文字列であることを意味しますbeまさにその値。

2
DigitalRoss

一般的な慣習は、2つの浮動小数点数を比較しないことです。つまり、

// using System.Diagnostics;

double a = 0.2; a *= 5.0;
double b = 1.0;
Debug.Assert(a == b);

浮動小数点数が不正確であるため、a正確にbと等しくない場合があります。等しいかどうかを比較するには、2つの数値の差を許容値と比較します。

Debug.Assert(Math.Abs(a - b) < 0.0001);
1
Salman A