web-dev-qa-db-ja.com

floatをdoubleに変換すると精度が失われますが、ToStringを介しては失われません

私は次のコードを持っています:

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString());

結果は次と同等です。

d1 = 0.30000001192092896;
d2 = 0.3;

これがなぜなのか知りたいのですが?

23
JoshG

精度の低下.3は 浮動小数点で表現可能 ではありません。システムが文字列に変換すると、丸められます。十分な有効数字を出力すると、より意味のある結果が得られます。

よりはっきりと見るために

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString("G20"));

string s = string.Format("d1 : {0} ; d2 : {1} ", d1, d2);

出力

"d1 : 0.300000011920929 ; d2 : 0.300000012 "
24
rerun

精度を失うことはありません。より精度の低い表現(float、32ビット長)からより正確な表現(double、64ビット長)にアップキャストしています。より正確な表現(ある時点を過ぎて)で得られるのは単なるゴミです。 doubleからfloatにキャストし直した場合、以前とまったく同じ精度になります。

ここでは、フロートに32ビットが割り当てられています。次に、doubleにアップキャストし、数値を表すためにさらに32ビットを追加します(合計64)。これらの新しいビットは最下位(小数点の右端)であり、以前は不定であったため、実際の値には影響しません。その結果、これらの新しいビットには、アップキャストを実行したときにたまたま持っていた値が含まれます。彼らは以前と同じように不確定です-言い換えればゴミです。

Double型からfloat型にダウンキャストすると、最下位ビットが失われ、0.300000(7桁の精度)になります。

文字列からフロートに変換するメカニズムは異なります。コンパイラーは、文字ストリング「0.3f」のセマンティックな意味を分析し、それが浮動小数点値とどのように関連するかを理解する必要があります。 float/double変換のようにビットシフトを使用して行うことはできません。つまり、期待する値です。

浮動小数点数がどのように機能するかについての詳細は、IEEE 754-1985規格に関する this ウィキペディアの記事をご覧ください。のこと)、および this wikiの記事で、2008年の標準への更新に関するものです。

edit:

第1に、@ phoogが以下で指摘するように、floatからdoubleへのアップキャストは、数値を記録するために予約されているスペースに32ビットを追加するほど簡単ではありません。実際には、指数に合計3ビット(合計11)、分数に合計29ビット(合計52)が追加されます。符号ビットを追加すると、ダブルの合計が64ビットになります。

さらに、それらの最下位の場所に「ごみビット」があり、全体的に一般化されており、おそらくC#では正しくないことを示唆しています。少し説明し、以下のいくつかのテストは、これがC#/。NETの決定論であり、おそらく追加の精度のためにメモリを予約するのではなく、変換における特定のメカニズムの結果であることを示唆しています。

昔のように、コードがマシン言語のバイナリにコンパイルされる場合、コンパイラ(少なくともCおよびC++コンパイラ)は、CPU用のスペースを予約したときにメモリの値を「クリア」または初期化するためのCPU命令を追加しませんでした。変数。したがって、プログラマーが変数をある値に明示的に初期化しない限り、その場所に予約されたビットの値は、そのメモリを予約する前の値を維持します。

.NETランドでは、C#またはその他の.NET言語は中間言語(CIL、共通中間言語)にコンパイルされ、CLRによってJust-In-Timeコンパイルされてネイティブコードとして実行されます。 C#コンパイラーまたはJITコンパイラーによって追加される変数初期化ステップがある場合とない場合があります。よく分かりません。

これが私が知っていることです:

  • フロートを3つの異なるdoubleにキャストすることで、これをテストしました。それぞれの結果はまったく同じ値でした。
  • その値は上記の@rerunの値とまったく同じでした:double d1 = System.Convert.ToDouble(f);結果:_d1 : 0.300000011920929_
  • double d2 = (double)f;を使用してキャストしても同じ結果が得られます。結果:_d2 : 0.300000011920929_

私たち3人が同じ値を取得しているため、アップキャスト値は確定的であり(実際にはガベージビットではない)、. NETが何かをすべて同じように実行していることを示しています私たちのマシン。 0.3fは0.3に正確に等しいわけではなく、0.3に等しいため、最大7桁の精度であるため、追加の桁が以前よりも正確ではないということは依然として本当です。最初の7桁を超える追加の桁の値については何も知りません。

14
Joe

この場合と他の同じ場合に正しい結果を得るために10進キャストを使用します

float ff = 99.95f;
double dd = (double)(decimal)ff;
4