parse - modulo 2 c#
Extraño comportamiento al lanzar un flotante a int en C# (7)
Tengo el siguiente código simple:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1 y speed2 deberían tener el mismo valor, pero de hecho, tengo:
speed1 = 61
speed2 = 62
Sé que probablemente debería usar Math.Round en lugar de casting, pero me gustaría entender por qué los valores son diferentes.
Miré el bytecode generado, pero a excepción de una tienda y una carga, los códigos de operación son los mismos.
También probé el mismo código en Java, y obtuve correctamente 62 y 62.
¿Alguien puede explicar esto?
Editar: en el código real, no es directamente 6.2f * 10, sino una función llamada * una constante. Tengo el siguiente bytecode:
para la velocidad 1:
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
para la velocidad 2:
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
podemos ver que los operandos son flotadores y que la única diferencia es el stloc / ldloc
En cuanto a la máquina virtual, probé con Mono / Win7, Mono / MacOS y .NET / Windows, con los mismos resultados
Descripción
Los números flotantes son raramente exactos. 6.2f
es algo así como 6.1999998...
Si transfiere esto a un int lo truncará y esto * 10 resulta en 61.
Consulte la clase de DoubleConverter
Jon Skeets. Con esta clase puedes visualizar realmente el valor de un número flotante como cadena. Double
y el float
son ambos números flotantes , el decimal no (es un número de punto fijo).
Muestra
DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875
Más información
¿Hay alguna razón por la que está realizando conversión a int
lugar de analizar?
int speed1 = (int)(6.2f * 10)
entonces leería
int speed1 = Int.Parse((6.2f * 10).ToString());
La diferencia probablemente tiene que ver con el redondeo: si lanzas al double
probablemente obtendrás algo como 61.78426.
Tenga en cuenta la siguiente salida
int speed1 = (int)(6.2f * 10);//61
double speed2 = (6.2f * 10);//61.9999980926514
¡Es por eso que estás obteniendo diferentes valores!
Compilé y desensamblé este código (en Win7 / .NET 4.0). Supongo que ese compilador evalúa la expresión constante flotante como doble.
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
En primer lugar, supongo que sabes que 6.2f * 10
no es exactamente 62 debido al redondeo de punto flotante (en realidad es el valor 61.99999809265137 cuando se expresa como un double
) y que tu pregunta es solo por qué dos cálculos aparentemente idénticos dan como resultado valor equivocado
La respuesta es que en el caso de (int)(6.2f * 10)
, está tomando el double
valor 61.99999809265137 y truncarlo en un entero, que arroja 61.
En el caso de float f = 6.2f * 10
, está tomando el doble valor 61.99999809265137 y redondeando al float
más cercano, que es 62. Luego trunca ese float
en un entero, y el resultado es 62.
Ejercicio: explique los resultados de la siguiente secuencia de operaciones.
double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2
Actualización: Como se señaló en los comentarios, la expresión 6.2f * 10
es formalmente float
ya que el segundo parámetro tiene una conversión implícita a float
que es better que la conversión implícita a double
.
El problema real es que se permite (pero no se requiere) que el compilador utilice un intermediario que sea de mayor precisión que el tipo formal . Es por eso que ve un comportamiento diferente en diferentes sistemas: en la expresión (int)(6.2f * 10)
, el compilador tiene la opción de mantener el valor 6.2f * 10
en una forma intermedia de alta precisión antes de convertir a int
. Si lo hace, entonces el resultado es 61. Si no lo hace, entonces el resultado es 62.
En el segundo ejemplo, la asignación explícita a float
obliga al redondeo a tener lugar antes de la conversión a entero.
Mira el 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
El compilador reduce las expresiones constantes en tiempo de compilación a su valor constante, y creo que hace una aproximación incorrecta en algún momento cuando convierte la constante en int
. En el caso de speed2
, esta conversión no la realiza el compilador, sino el CLR, y parece que aplican reglas diferentes ...
Supongo que la representación real de 6.2f
con precisión de flotación es 6.1999999
mientras que 62f
es probablemente algo similar a 62.00000001
. (int)
casting siempre trunca el valor decimal por lo que es por eso que obtienes ese comportamiento.
EDITAR : De acuerdo con los comentarios, he reformulado el comportamiento de int
casting a una definición mucho más precisa.
Single
mantiene solo 7 dígitos y al convertirlo a Int32
el compilador trunca todos los dígitos del punto flotante. Durante la conversión, se podrían perder uno o más dígitos significativos.
Int32 speed0 = (Int32)(6.2f * 100000000);
da el resultado de 619999980 entonces (Int32) (6.2f * 10) da 61.
Es diferente cuando se multiplican dos individuales, en ese caso no hay operación truncada sino solo aproximación.
Ver http://msdn.microsoft.com/en-us/library/system.single.aspx