2012-01-09 7 views
49

¿Por qué este código imprime False en .NET 4? Parece que algún comportamiento inesperado está siendo causado por el lanzamiento explícito.Lanzar un resultado para flotar en el método devolver resultados de flotador

Me gustaría obtener una respuesta más allá de que "el punto flotante es inexacto" o "no hago eso".

float a(float x, float y) 
{ 
    return (x * y); 
} 

float b(float x, float y) 
{ 
    return (float)(x * y); 
} 

void Main() 
{ 
    Console.WriteLine(a(10f, 1f/10f) == b(10f, 1f/10f)); 
} 

PD: Este código procede de una prueba de unidad, no de código de versión. El código fue escrito de esta manera deliberadamente. Sospeché que finalmente fallaría, pero quería saber exactamente cuándo y por qué. La respuesta demuestra la validez de esta técnica porque proporciona una comprensión que va más allá de la comprensión habitual del determinismo de punto flotante. Y ese era el punto de escribir este código de esta manera; exploración deliberada.

PPS: La unidad de prueba que pasaba en .NET 3.5, pero ahora falla después de la actualización a .NET 4.

+9

variables de coma flotante son, por definición, 100% exactas. Son números aproximados. http://msdn.microsoft.com/en-us/library/b1e65aza.aspx Vea también esta publicación: http://stackoverflow.com/questions/618535/what-is-the-difference-between-decimal-float -y-doble-en-c No hay garantía de que sean iguales en * cualquier * sabor del tiempo de ejecución. – David

+0

Un enlace más útil: http://msdn.microsoft.com/en-us/library/ms187912.aspx – David

+4

No obstante, es una pregunta interesante. Funcionalmente uno esperaría que estos métodos A y B realizaran la misma operación, incluso con la aproximación de coma flotante. Estoy interesado en escuchar la explicación. –

Respuesta

76

comentario de David es correcta, pero insuficientemente fuerte. No hay garantía de que al hacer ese cálculo dos veces en el mismo programa se obtendrán los mismos resultados.

El C# especificación es extremadamente claro en este punto:


operaciones de punto flotante se pueden realizar con una mayor precisión que el tipo de resultado de la operación. Por ejemplo, algunas arquitecturas de hardware admiten un tipo de punto flotante "extendido" o "largo doble" con mayor rango y precisión que el tipo doble, e implícitamente realizan todas las operaciones de coma flotante con este tipo de precisión superior. Solo a un costo excesivo en rendimiento pueden tales arquitecturas de hardware realizar operaciones de punto flotante con menos precisión, y en lugar de requerir una implementación que pierda rendimiento y precisión, C# permite que se use un tipo de mayor precisión para todas las operaciones de coma flotante . Además de ofrecer resultados más precisos, esto rara vez tiene efectos mensurables. Sin embargo, en expresiones del formulario x * y/z, donde la multiplicación produce un resultado que está fuera del rango doble, pero la división posterior devuelve el resultado temporal al rango doble, el hecho de que la expresión se evalúe en un formato de rango superior puede causar un resultado finito para ser producido en lugar de un infinito.


El compilador de C#, el jitter y el tiempo de ejecución todos tienen amplia LAttitude para darle resultados más precisos que los requeridos por la especificación, en cualquier momento, a su antojo - no son necesarios para elegir hacerlo de manera consistente y de hecho no lo hacen.

Si no te gusta, entonces no uses números de coma flotante binarios; usar decimales o racionales de precisión arbitraria.

No entiendo por qué la fundición a flotar en un método que devuelve flotador hace la diferencia que hace

Excelente punto.

Su programa de ejemplo demuestra cómo pequeños cambios pueden causar grandes efectos. Tenga en cuenta que en alguna versión del tiempo de ejecución, la conversión a flotación explícitamente da un resultado diferente que no hacerlo.Cuando seleccionas explícitamente flotar, el compilador de C# da una pista al tiempo de ejecución para decir "saca esto del modo de muy alta precisión si estás usando esta optimización". Como se especifica en la especificación, , esto tiene un costo de rendimiento potencial.

Que hacerlo llega a la "respuesta correcta" es simplemente un accidente feliz; se obtiene la respuesta correcta porque en este caso perdiendo precisión pasó a perderla en la dirección correcta.

¿Cómo es .net 4 diferente?

Usted pregunta cuál es la diferencia entre 3.5 y 4.0 tiempos de ejecución; la diferencia es claramente que en 4.0, el jitter elige ir a una mayor precisión en su caso particular, y el jitter 3.5 elige no hacerlo. Eso no quiere decir que esta situación sea imposible en 3.5; Ha sido posible en todas las versiones del tiempo de ejecución y en todas las versiones del compilador de C#. Acabas de encontrar un caso donde, en tu máquina, difieren en sus detalles. Pero el jitter tiene siempre permitido hacer esta optimización, y siempre lo ha hecho a su antojo.

El compilador C# también tiene todos los derechos para elegir realizar optimizaciones similares al calcular flotantes constantes en tiempo de compilación. Dos cálculos aparentemente idénticos en constantes pueden tener diferentes resultados dependiendo de los detalles del estado de tiempo de ejecución del compilador.

En general, su expectativa de que los números de punto flotante deberían tener las propiedades algebraicas de los números reales está completamente fuera de línea con la realidad; ellos no tienen esas propiedades algebraicas. Las operaciones de coma flotante no son ni siquiera asociativo; ciertamente no obedecen las leyes de los inversos multiplicativos, como parece esperar que lo hagan. Los números de coma flotante son solo una aproximación de la aritmética real; una aproximación lo suficientemente cercana para, por ejemplo, simular un sistema físico o calcular estadísticas de resumen, o algo por el estilo.

+6

Lamentablemente, ya sabía que el punto flotante no es preciso. Lo que no sabía era todo lo demás sobre el casting y la optimización. Sería más fácil hacer la pregunta correcta si supiera la respuesta. –

+2

No me gusta que se aplique el término "impreciso" a los números de coma flotante. '(float) 1' es 100% exacto. Agregar números enteros pequeños y la multiplicación por una potencia de dos con un resultado normal también es 100% exacto. Las diferencias en este ejemplo son el resultado no de cálculos "imprecisos", sino (como Eric explicó extensamente) de la libertad del lenguaje y el tiempo de ejecución para elegir ciertos detalles de implementación. –

+1

@JeffreySax: como quiera llamarlo, es una cosa. Y es eso a lo que muchas personas atribuyeron este problema de forma errónea o incompleta. El objetivo de la pregunta era llegar a "otras cosas". Se necesitaron ediciones tanto para la pregunta como para la respuesta. Desearía que nadie mencionara los conceptos básicos de la "precisión" de los puntos flotantes, porque este problema (como Eric explicó en detalle) es más complicado que lo que comúnmente se conoce de la "precisión" de los puntos flotantes. Es por eso que los comentarios sobre la "precisión" de los puntos flotantes aún se están subiendo. –

0

No tengo compilador de Microsoft en este momento y Mono no tiene tal efecto. Por lo que sé GCC 4.3+ uses gmp and mpfr to calculate some stuff in compile time. El compilador de C# puede hacer lo mismo para métodos no virtuales, estáticos o privados en el mismo ensamblado. El lanzamiento explícito puede interferir con dicha optimización (pero no veo ninguna razón por la cual no pueda tener el mismo comportamiento). Es decir. puede coincidir con el cálculo de la expresión constante a algún nivel (para b() puede ser, por ejemplo, hasta el modelo).

GCC también tiene la optimización que promueve el funcionamiento con la más alta precisión si tiene sentido.

Así que consideraría tanto la optimización como una posible razón. Pero para ambos no veo ninguna razón por la cual hacer un casting explícito del resultado pueda tener algún significado adicional como "estar más cerca del estándar".

Cuestiones relacionadas