私は次の簡単なコードを持っています:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
およびspeed2
は同じ値でなければなりませんが、実際には次のようになります。
speed1 = 61
speed2 = 62
おそらくキャストの代わりにMath.Roundを使用する必要があることは知っていますが、値が異なる理由を理解したいと思います。
生成されたバイトコードを見ましたが、ストアとロードを除いて、オペコードは同じです。
Javaでも同じコードを試しましたが、62と62を正しく取得しました。
誰かがこれを説明できますか?
編集:実際のコードでは、6.2f * 10ではなく、関数呼び出し*定数です。私は次のバイトコードを持っています:
ために speed1
:
IL_01b3: ldloc.s V_8
IL_01b5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ba: ldc.r4 10.
IL_01bf: mul
IL_01c0: conv.i4
IL_01c1: stloc.s V_9
ために speed2
:
IL_01c3: ldloc.s V_8
IL_01c5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ca: ldc.r4 10.
IL_01cf: mul
IL_01d0: stloc.s V_10
IL_01d2: ldloc.s V_10
IL_01d4: conv.i4
IL_01d5: stloc.s V_11
オペランドは浮動小数点数であり、唯一の違いはstloc/ldloc
。
仮想マシンについては、Mono/Win7、Mono/MacOS、および.NET/Windowsで試しましたが、同じ結果になりました。
まず、浮動小数点の丸めにより6.2f * 10
が正確に62ではないことを知っていると仮定します(double
として表される場合、実際には値61.99999809265137です)。一見同一の計算は間違った値をもたらします。
答えは、(int)(6.2f * 10)
の場合、double
値61.99999809265137を取得して整数に切り捨てて、61を生成するということです。
float f = 6.2f * 10
の場合、double値61.99999809265137とroundingを最も近いfloat
(62)に取っています。次に、そのfloat
を整数に切り捨てると、結果は62になります。
演習:次の一連の操作の結果を説明します。
double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2
更新:コメントで述べたように、式6.2f * 10
は正式にfloat
です。これは、2番目のパラメーターがfloat
に暗黙的に変換されるためです。これは better than double
への暗黙的な変換。
実際の問題は、コンパイラーが 正式な型(セクション11.2.2)よりも高い精度 である中間体を使用することを許可されていることです(必須ではありません)。そのため、システムごとに異なる動作が見られます。式(int)(6.2f * 10)
には、int
に変換する前に、値6.2f * 10
を高精度の中間形式に保つオプションがあります。一致する場合、結果は61です。一致しない場合、結果は62です。
2番目の例では、float
への明示的な割り当てにより、整数への変換の前に丸めが行われます。
浮動小数点数はめったに正確ではありません。 6.2f
は6.1999998...
。これをintにキャストすると、切り捨てられ、この* 10は61になります。
Jon Skeets DoubleConverter
クラスを確認してください。このクラスを使用すると、浮動小数点数の値を文字列として実際に視覚化できます。 Double
とfloat
は両方とも浮動小数点数であり、10進数ではありません(固定小数点数です)。
DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875
ILを見てください:
IL_0000: ldc.i4.s 3D // speed1 = 61
IL_0002: stloc.0
IL_0003: ldc.r4 00 00 78 42 // tmp = 62.0f
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000A: conv.i4
IL_000B: stloc.2
コンパイラはコンパイル時の定数式を定数値に減らします。定数をint
に変換すると、ある時点で誤った近似を行うと思います。の場合 speed2
、この変換はコンパイラではなくCLRによって行われ、異なる規則を適用するようです...
このコードをコンパイルして逆アセンブルしました(Win7/.NET 4.0で)。コンパイラは浮動定数式をdoubleとして評価すると思います。
int speed1 = (int)(6.2f * 10);
mov dword ptr [rbp+8],3Dh //result is precalculated (61)
float tmp = 6.2f * 10;
movss xmm0,dword ptr [000004E8h] //precalculated (float format, xmm0=0x42780000 (62.0))
movss dword ptr [rbp+0Ch],xmm0
int speed2 = (int)tmp;
cvttss2si eax,dword ptr [rbp+0Ch] //instrunction converts float to Int32 (eax=62)
mov dword ptr [rbp+10h],eax
私の推測では、6.2f
float精度の実数表現は6.1999999
while 62f
はおそらく62.00000001
。 (int)
常にキャストする10進数値を切り捨てるであるため、この動作が発生します。
[〜#〜] edit [〜#〜]:コメントによると、int
キャストの動作をより正確な定義に言い換えました。
Single
は7桁のみを保持し、Int32
にキャストすると、コンパイラーはすべての浮動小数点を切り捨てます。変換中に、1つ以上の有効数字が失われる可能性があります。
Int32 speed0 = (Int32)(6.2f * 100000000);
結果は619999980になるので、(Int32)(6.2f * 10)は61になります。
2つのSingleが乗算されると異なります。その場合、切り捨て操作は行われず、近似のみが行われます。
http://msdn.Microsoft.com/en-us/library/system.single.aspx を参照してください