Tengo el siguiente código simple:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1y speed2debería tener el mismo valor, pero de hecho, tengo:
speed1 = 61
speed2 = 62
Sé que probablemente debería usar Math.Round en lugar de lanzar, pero me gustaría entender por qué los valores son diferentes.
Miré el bytecode generado, pero excepto 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 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
para 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
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.
fuente

Respuestas:
En primer lugar, supongo que sabe que
6.2f * 10no es exactamente 62 debido al redondeo de coma flotante (en realidad es el valor 61.99999809265137 cuando se expresa como adouble) y que su pregunta es solo por qué dos cálculos aparentemente idénticos resultan en un valor incorrecto.La respuesta es que, en el caso de
(int)(6.2f * 10), está tomando eldoublevalor 61.99999809265137 y truncándolo a un número entero, lo que produce 61.En el caso de
float f = 6.2f * 10, está tomando el valor doble 61.99999809265137 y redondeando al más cercanofloat, que es 62. Luego lo truncafloata un entero, y el resultado es 62.Ejercicio: explique los resultados de la siguiente secuencia de operaciones.
Actualización: Como se señaló en los comentarios, la expresión
6.2f * 10es formalmente unafloatya que el segundo parámetro tiene una conversión implícita a lafloatcual es mejor que la conversión implícitadouble.El problema real es que el compilador tiene permiso (pero no es obligatorio) para usar un intermedio que es de mayor precisión que el tipo formal (sección 11.2.2) . 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 valor6.2f * 10en una forma intermedia de alta precisión antes de convertirint. 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
floatobliga al redondeo a realizarse antes de la conversión a entero.fuente
(int)(6.2f * 10)tomando eldoublevalor, comofespecifica que es unfloat? Creo que el punto principal (aún sin respuesta) está aquí.6.2f * 10es en realidadfloat, nodouble. Creo que el compilador está optimizando el intermedio, como lo permite el último párrafo de 11.1.6 .floatprimero por una conversión.Descripción
Los números flotantes son raramente exactos.
6.2fes algo así como6.1999998.... Si lanza esto a un int, lo truncará y esto * 10 da como resultado 61.Echa un vistazo a la
DoubleConverterclase Jon Skeets . Con esta clase realmente puede visualizar el valor de un número flotante como una cadena.Doubleyfloatson ambos números flotantes , el decimal no es (es un número de punto fijo).Muestra
Más información
fuente
Mira el IL:
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 a
int. En el caso despeed2, esta conversión no la realiza el compilador, sino el CLR, y parece que aplican reglas diferentes ...fuente
Supongo que
6.2fla representación real con precisión flotante es6.1999999mientras que62fes probablemente algo similar a62.00000001.(int)la conversión siempre trunca el valor decimal, por eso es que obtienes ese comportamiento.EDITAR : Según los comentarios, he reformulado el comportamiento del
intcasting a una definición mucho más precisa.fuente
inttrunca el valor decimal, no se redondea.float->intimplica redondeo. = DCompilé y desensamblé este código (en Win7 / .NET 4.0). Supongo que el compilador evalúa la expresión constante flotante como doble.
fuente
Singlemantiene solo 7 dígitos y cuando lo envíaInt32al compilador trunca todos los dígitos de coma flotante. Durante la conversión, se pueden perder uno o más dígitos significativos.da el resultado de 619999980, entonces (Int32) (6.2f * 10) da 61.
Es diferente cuando se multiplican dos Single, en ese caso no hay operación truncada sino solo aproximación.
Ver http://msdn.microsoft.com/en-us/library/system.single.aspx
fuente
¿Hay alguna razón por la que estás escribiendo en
intlugar de analizar?entonces leería
La diferencia probablemente tenga que ver con el redondeo: si lanzas a
doublelanzas probablemente obtendrás algo como 61.78426.Tenga en cuenta la siguiente salida
¡Es por eso que obtienes valores diferentes!
fuente
Int.Parsetoma una cadena como parámetro.