Extraño comportamiento al lanzar un flotante a int en C #

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

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 mejor que la conversión implícita a double .

El problema real es que se permite (pero no se requiere) que el comstackdor 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 comstackdor 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.

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

  • Clase DoubleConverter de Jon Skeet
  • Assert.AreEqual () con System.Double consiguiendo realmente confuso
  • Lo que todo informático debería saber sobre la aritmética de coma flotante

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 comstackdor reduce las expresiones constantes en tiempo de comstackció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 comstackdor, 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 comstackdor 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

Compilé y desensamblé este código (en Win7 / .NET 4.0). Supongo que ese comstackdor 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 

¿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!