重複の可能性:
C# で double 値を比較する際の問題
どこかで読んだのですが、よくわからなかったのでこちらで再度質問させていただきます。このループは、どの言語でコーディングしても終わらないようです (私は C#、C++、Java でテストしています...):
double d = 2.0;
while(d != 0.0){
d = d - 0.2;
}
重複の可能性:
C# で double 値を比較する際の問題
どこかで読んだのですが、よくわからなかったのでこちらで再度質問させていただきます。このループは、どの言語でコーディングしても終わらないようです (私は C#、C++、Java でテストしています...):
double d = 2.0;
while(d != 0.0){
d = d - 0.2;
}
浮動小数点の計算は完全に正確ではありません。0.2 は 2 進浮動小数点数として正確に表現されていないため、値が正確にゼロに等しくならないため、表現エラーが発生します。問題を確認するには、デバッグ ステートメントを追加してみてください。
double d = 2.0;
while (d != 0.0)
{
Console.WriteLine(d);
d = d - 0.2;
}
2 1,8 1,6 1,4 1,2 1 0,8 0,6 0,4 0,2 2,77555756156289E-16 // 厳密にはゼロではありません!! -0,2 -0,4
それを解決する 1 つの方法は、 type を使用することですdecimal
。
(1 つには、全体で同じ変数を使用していないことですが、それはタイプミスだと思います:)
0.2 は実際には 0.2 ではありません。double
0.2に最も近い値です。2.0 から 10 回引いても、正確には0.0にはなりません。
decimal
C#では、代わりに型を使用するように変更できます。これは機能します。
// Works
decimal d = 2.0m;
while (d != 0.0m) {
d = d - 0.2m;
}
これは、10 進数型が0.2 のような 10 進数値を正確に表すため (制限内で、128 ビット型です)、機能します。関連するすべての値は正確に表現できるため、機能します。うまくいかないのはこれです:
decimal d = 2.0m;
while (d != 0.0m) {
d = d - 1m/3m;
}
ここで、「3 分の 1」は正確には表現できないため、前と同じ問題が発生します。
ただし、一般に、浮動小数点数間で正確な等値比較を実行することはお勧めできません。通常は、特定の許容範囲内で比較します。
C#/.NET コンテキストからの浮動小数点と浮動小数点に関する記事があり、より詳細に説明されています。
Sinclair ZX-81を購入し、優れた基本プログラミングマニュアルを読み進め、最初の浮動小数点の丸め誤差に遭遇したときに、ほぼ店に戻ったことを覚えています。
27.99998年後も人々がこれらの問題を抱えているとは想像もしていませんでした。
使ったほうがいい
while(f > 0.0)
*編集:以下のパスカルのコメントを参照してください。ただし、ループを整数の決定論的な回数だけ実行する必要がある場合は、整数データ型を使用してください。
問題は浮動小数点演算です。数値の正確な 2 進数表現がない場合は、それに最も近い数値のみを格納できます (数値1/3
を 10 進数で格納できないのと同じよう0.33333333
に、「3」の長さのようなものしか格納できません)。浮動小数点数の算術演算は完全に正確ではないことがよくあります。次のようなものを試してください(Java):
public class Looping {
public static void main(String[] args) {
double d = 2.0;
while(d != 0.0 && d >= 0.0) {
System.out.println(d);
d = d - 0.2;
}
}
}
出力は次のようになります。
2.0
1.8
1.6
1.4000000000000001
1.2000000000000002
1.0000000000000002
0.8000000000000003
0.6000000000000003
0.4000000000000003
0.2000000000000003
2.7755575615628914E-16
これで、この状態が発生しない理由がわかったはずですd == 0
。(最後の数値には、 0に非常に近い数値がありますが、完全ではありません。
浮動小数点の奇妙さの別の例については、これを試してください。
public class Squaring{
public static void main(String[] args) {
double d = 0.1;
System.out.println(d*d);
}
}
正確な のバイナリ表現はないため0.1
、2 乗しても期待する結果 ( 0.01
) は得られませんが、実際には0.010000000000000002
!
f は初期化されていません ;)
つまり:
double f = 2.0;
これは、double 変数に対する非正確な算術の影響である可能性があります。
これは浮動小数点の精度によるものです。while (d>0.0) を使用するか、必要に応じて、
while (Math.abs(d-0.0) > some_small_value){
}
他の人が言ったように、これは浮動小数点演算を任意の基数で行うときに発生する基本的な問題にすぎません。たまたま、基数 2 がコンピューターで最も一般的なものです (効率的なハードウェア実装が認められているため)。
可能であれば、ループの数値の何らかの商表現を使用するように切り替えて、そこから浮動小数点値を導出するのが最善の解決策です。OK、それは誇張されているように聞こえます。あなたの特定のケースでは、次のように書きます。
int dTimes10 = 20;
double d;
while(dTimes10 != 0) {
dTimes10 -= 2;
d = dTimes10 / 10.0;
}
ここでは、分数 [20/10, 18/10, 16/10, ..., 2/10, 0/10] を実際に扱っており、反復は整数で行われます (つまり、簡単に修正できます)。浮動小数点に変換する前に、分母が固定された分子。このように動作するように実際の反復を書き直すことができれば、大きな成功を収めることができます (とにかく、以前に行っていたものよりもはるかにコストがかかるわけではありません。これは、正確性を得るための大きなトレードオフです)。
これができない場合は、比較として equal-within-epsilon を使用する必要があります。ε (イプシロン) の選択は時々扱いにくい場合がありますd != target
。abs(d - target) < ε
基本的に、ε の適切な値は多くの要因に依存しますが、ステップ値のスケールが与えられた反復例ではおそらく 0.001 として選択するのが最適です (つまり、ステップの大きさの 0.5 パーセントなので、その範囲内であれば何でもかまいません)。情報ではなくエラーになります)。
0.2 i が 2 の補数で正確に表されていないため、ループが0.0==0.0
テストを実行しないため、停止しません。