web-dev-qa-db-ja.com

.NETで二重乗算が壊れていますか?

C#で次の式を実行すると、

double i = 10*0.69;

iは:6.8999999999999995。どうして?

1/3などの数値は、小数点以下の桁数が無限に繰り返されるため、2進数で表すのは難しい場合がありますが、0.69の場合はそうではありません。そして、0.69は2進数で簡単に表すことができます。1つは69の2進数で、もう1つは小数点以下の位置を示します。

これを回避するにはどうすればよいですか? decimalタイプを使用しますか?

53
Dan

浮動小数点演算とデータの格納方法を誤解しているためです。

実際、この特定のケースでは、コードは実際には実行時に算術を実行していません-コンパイラがそれを実行し、次に定数を保存します生成された実行可能ファイル。ただし、正確な値6.9を格納することはできないため、その値は、1/3ができるように、浮動小数点形式で正確に表すことができません有限の10進表現で正確に格納することはできません。

この記事 が役立つかどうか確認してください。

156
Jon Skeet

なぜフレームワークはこれを回避し、この問題を私から隠し、正しい答えを与えないのですか、0.69 !!!

ディルバートのマネージャーのように振る舞うのをやめて、クールで素晴らしいけれどもコンピュータには限界があることを受け入れてください。あなたの特定のケースでは、あなたは具体的にそれをしないように言ったので、それは単に問題を「隠す」だけではありません。言語(コンピューター)は、ユーザーが選択しなかった形式の代替を提供します。あなたはdoubleを選択しましたが、これは10進数に比べて特定の利点と特定の欠点があります。さて、答えを知って、あなたは不利な面が魔法のように消えないことに怒っています。

プログラマーとして、あなたはこの欠点をマネージャーから隠す責任があります。それを行う方法はたくさんあります。ただし、C#の作成者は、浮動小数点を正しく機能させる責任があり、正しい浮動小数点は、正しくない計算になることがあります。

無限ビットがないので、他のすべての数値格納方法もそうです。プログラマーとしての私たちの仕事は、クールなことを実現するために限られたリソースで作業することです。彼らはあなたにそこへの道の90%を手に入れました、ただトーチを家に帰してください。

53
Russell Steen

また、0.69は2進数で簡単に表すことができます。1つは69の2進数で、もう1つはdecimalの位置を示します。

これはよくある間違いだと思います-浮動小数点数を10進数(つまり10進数)であるかのように考えているので、私が強調している点です。

したがって、このdoubleには2つの整数部分があると考えています。69および100で除算で小数点以下の桁数を取得します-これも表現できますなので:
69 x 10の-2乗

ただし、フロートは「ポイントの位置」をbase-2として格納します。

あなたのフロートは実際には次のように保存されます:
68999999999999995 x 2の大きな負の数の累乗

慣れればこれはそれほど問題にはなりません。ほとんどの人は、1/3が小数またはパーセンテージとして正確に表現できないことを知っており、期待しています。 base-2で表現できない分数が異なるだけです。

15
Keith

しかし、なぜフレームワークがこれを回避してこの問題を私から隠し、正しい答えを与えないのですか、0.69 !!!

2進浮動小数点を使用するように指示し、解決策は10進浮動小数点を使用することなので、フレームワークが指定した型を無視して使用することを提案しています- decimal代わりに、ハードウェアに直接実装されていないため、非常に遅くなります。

より効率的なソリューションは、表現の完全な値を出力せず、出力に必要な精度を明示的に指定することです。出力を小数点以下2桁にフォーマットすると、期待どおりの結果が表示されます。ただし、これが金融アプリケーションである場合は、10進数を使用する必要があります。スーパーマンIII(およびオフィススペース)にはないはずです;)

これはすべて無限範囲の有限近似であることに注意してください。decimalおよびdoubleが異なる近似のセットを使用しているだけです。 decimalの利点は、計算を自分で実行する場合と同じ近似を生成することです。たとえば、1/3を計算した場合、それが「十分」であった場合、最終的に3の書き込みを停止します。

11
Clifford

同じ理由で、10進法の1/3は0.3333333333333333333333333333333333333333333として出力され、無限に長い正確な分数ではありません。

8
erikkallen

これを回避するには(たとえば、画面に表示するには)、次のことを試してください。

double i = (double) Decimal.Multiply(10, (Decimal) 0.69);

誰もが最初の質問に答えたようですが、2番目の部分は無視しました。

2
Richard