¿Cómo evalúa un C # el punto flotante en el cursor sobre la ventana intermedia y la compilada?

StackOverflow https://stackoverflow.com/questions/1405486

  •  05-07-2019
  •  | 
  •  

Pregunta

Estoy viendo algo extraño con el almacenamiento de dobles en un diccionario, y estoy confundido en cuanto a por qué.

Aquí está el código:

            Dictionary<string, double> a = new Dictionary<string, double>();
            a.Add("a", 1e-3);

            if (1.0 < a["a"] * 1e3)
                Console.WriteLine("Wrong");

            if (1.0 < 1e-3 * 1e3)
                Console.WriteLine("Wrong");

La segunda sentencia if funciona como se esperaba; 1.0 no es menor que 1.0. Ahora, la primera instrucción if se evalúa como verdadera. Lo más extraño es que cuando paso el mouse sobre el if, el intellisense me dice falso, pero el código felizmente se mueve a Console.WriteLine.

Esto es para C # 3.5 en Visual Studio 2008.

¿Es este un problema de precisión de coma flotante? Entonces, ¿por qué funciona la segunda instrucción if? Siento que me estoy perdiendo algo muy fundamental aquí.

Cualquier percepción es apreciada.

Edit2 (Reutilizando un poco la pregunta):

Puedo aceptar el problema de precisión matemática, pero mi pregunta ahora es: ¿por qué el desplazamiento sobre evalúa correctamente? Esto también es cierto para la ventana intermedia. Pego el código de la primera instrucción if en la ventana intermedia y evalúa falso.

Actualizar

En primer lugar, muchas gracias por todas las excelentes respuestas.

También tengo problemas para recrear esto en otro proyecto en la misma máquina. Mirando la configuración del proyecto, no veo diferencias. Mirando la IL entre los proyectos, no veo diferencias. En cuanto al desmontaje, no veo diferencias aparentes (además de las direcciones de memoria). Sin embargo, cuando depuro el proyecto original, veo: captura de pantalla del problema

La ventana inmediata me dice que si es falso, pero el código cae en el condicional.

En cualquier caso, creo que la mejor respuesta es prepararse para la aritmética de punto flotante en estas situaciones. La razón por la que no podía dejar pasar esto tiene más que ver con los cálculos del depurador que difieren del tiempo de ejecución. Así que muchas gracias a Brian Gideon y stephentyrone por algunos comentarios muy perspicaces.

¿Fue útil?

Solución

Es un problema de precisión flotante.

La segunda declaración funciona porque el compilador cuenta la expresión 1e-3 * 1e3 antes de emitir el archivo .exe.

Búscalo en ILDasm / Reflector, emitirá algo así como

 if (1.0 < 1.0)
                Console.WriteLine("Wrong");

Otros consejos

El problema aquí es bastante sutil. El compilador de C # no (siempre) emite código que realiza el cálculo en doble, incluso cuando ese es el tipo que ha especificado. En particular, emite código que realiza el cálculo en " extendido " precisión utilizando instrucciones x87, sin redondear los resultados intermedios al doble.

Dependiendo de si 1e-3 se evalúa como doble doble o largo, y si la multiplicación se calcula en doble o largo doble, es posible obtener cualquiera de los siguientes tres resultados:

  • (doble largo) 1e-3 * 1e3 calculado en doble largo es 1.0 - épsilon
  • (doble) 1e-3 * 1e3 calculado en doble es exactamente 1.0
  • (doble) 1e-3 * 1e3 calculado en doble largo es 1.0 + épsilon

Claramente, la primera comparación, la que no está cumpliendo con sus expectativas, se está evaluando de la manera descrita en el tercer escenario que enumeré. 1e-3 se redondea al doble ya sea porque lo está almacenando y cargando nuevamente, lo que fuerza el redondeo, o porque C # reconoce 1e-3 como un literal de doble precisión y lo trata de esa manera. La multiplicación se está evaluando en el doble de largo porque C # tiene un modelo numérico de muerte cerebral , así es como el compilador está generando el código.

La multiplicación en la segunda comparación se está evaluando usando uno de los otros dos métodos, (Puedes averiguar cuál al intentar " 1 > 1e-3 * 1e3 "), o el compilador está redondeando el resultado de la multiplicación antes de compararlo con 1.0 cuando evalúa la expresión en tiempo de compilación.

Es probable que le digas al compilador que no use precisión extendida sin que le indiques a través de alguna configuración de compilación; habilitar codegen para SSE2 también puede funcionar.

Vea las respuestas aquí

Umm ... extraño. No soy capaz de reproducir tu problema. Estoy usando C # 3.5 y Visual Studio 2008 también. He escrito en su ejemplo exactamente tal como se publicó y no veo ninguna de las declaraciones de Console.WriteLine ejecutada.

Además, el compilador optimiza la segunda declaración if. Cuando examino las compilaciones de debug y release en ILDASM / Reflector no veo evidencia de ello. Eso se debe a que recibo una advertencia del compilador que dice que se detectó un código inalcanzable en él.

Finalmente, no veo cómo esto podría ser un problema de precisión de coma flotante de todos modos. ¿Por qué el compilador de C # evaluaría estáticamente dos dobles de manera diferente a lo que haría CLR en tiempo de ejecución? Si ese fuera realmente el caso, se podría argumentar que el compilador de C # tiene un error.

Editar: Después de pensar un poco más en esto, estoy aún más convencido de que esto no es un problema de precisión de punto flotante. Debe haber tropezado con un error en el compilador o el depurador o el código que publicó no es exactamente representativo de su código real que se está ejecutando. Soy muy escéptico acerca de un error en el compilador, pero parece más probable que haya un error en el depurador. Intenta reconstruir el proyecto y ejecutarlo de nuevo. Tal vez la información de depuración compilada junto con exe se haya desincronizado o algo así.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top