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í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 * 10
no 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 eldouble
valor 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 truncafloat
a 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 * 10
es formalmente unafloat
ya que el segundo parámetro tiene una conversión implícita a lafloat
cual 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 * 10
en 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
float
obliga al redondeo a realizarse antes de la conversión a entero.fuente
(int)(6.2f * 10)
tomando eldouble
valor, comof
especifica que es unfloat
? Creo que el punto principal (aún sin respuesta) está aquí.6.2f * 10
es en realidadfloat
, nodouble
. Creo que el compilador está optimizando el intermedio, como lo permite el último párrafo de 11.1.6 .float
primero por una conversión.Descripción
Los números flotantes son raramente exactos.
6.2f
es algo así como6.1999998...
. Si lanza esto a un int, lo truncará y esto * 10 da como resultado 61.Echa un vistazo a la
DoubleConverter
clase Jon Skeets . Con esta clase realmente puede visualizar el valor de un número flotante como una cadena.Double
yfloat
son 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.2f
la representación real con precisión flotante es6.1999999
mientras que62f
es 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
int
casting a una definición mucho más precisa.fuente
int
trunca el valor decimal, no se redondea.float
->int
implica 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
Single
mantiene solo 7 dígitos y cuando lo envíaInt32
al 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
int
lugar de analizar?entonces leería
La diferencia probablemente tenga que ver con el redondeo: si lanzas a
double
lanzas probablemente obtendrás algo como 61.78426.Tenga en cuenta la siguiente salida
¡Es por eso que obtienes valores diferentes!
fuente
Int.Parse
toma una cadena como parámetro.