61 votos

¿El alcance de las variables de punto flotante afectar sus valores?

Si ejecutamos el siguiente código C # en una aplicación de consola, obtendremos un mensaje como The sums are Not equal .

Si lo ejecutamos después eliminando el comentario de la línea System.Console.WriteLine() , obtendremos un mensaje como The sums are equal .

     static void Main(string[] args)
    {
        float f = Sum(0.1f, 0.2f);
        float g = Sum(0.1f, 0.2f);

        //System.Console.WriteLine("f = " + f + " and g = " + g);

        if (f == g)
        {
            System.Console.WriteLine("The sums are equal");
        }
        else
        {
            System.Console.WriteLine("The sums are Not equal");
        }
    }

    static float Sum(float a, float b)
    {
        System.Console.WriteLine(a + b);
        return a + b;
    }
 

¿Cuál es la verdadera razón de este comportamiento?

46voto

Mishax Puntos 1562

No relacionadas con el ámbito. Es la combinación de la pila dinámica de punto flotante y de manejo. Algunos de los conocimientos de los compiladores ayudará a hacer este comportamiento contrario a la intuición clara.

Cuando la Console.WriteLine está comentado, los valores de f y g son en la evaluación de la pila y permanecer allí hasta después de haber pasado la prueba de la igualdad en el método Main.

Cuando Console.Writeline no está comentada, los valores de f y g se mueven a partir de la evaluación de la pila de la pila de llamadas en el momento de la invocación, de ser restaurados a la evaluación de la pila cuando Console.WriteLine devuelve. Y su comparación if (f == g) se realiza a posteriori. Algunos de redondeo pueden ocurrir durante este almacenamiento de los valores de la pila de llamadas y algunos datos pueden ser perdidos.

En el escenario donde hacer invocar Console.WriteLine, f y a las g en la prueba de comparación no son los mismos valores. Ellos han sido copiados y restaurado a un formato que tiene reglas diferentes sobre la precisión y redondeo, por la máquina virtual.

En su código particular, cuando la invocación de Console.WriteLine está comentado, la evaluación de la pila nunca se almacena en la pila de llamadas y no se redondea. Porque está permitido para las implementaciones de la plataforma para proporcionar una mayor precisión en la evaluación de la pila, esta discrepancia puede surgir.

EDITAR Lo que estamos llegando en este caso es permitido por la especificación CLI. En la sección I. 12.1.3 se lee:

Ubicaciones de almacenamiento de los números de punto flotante (estática, elementos de la matriz, y los campos de clases) son de tamaño fijo. El soportados tamaños de almacenamiento son float32 y float64. En todas partes (en la evaluación de la pila, como los argumentos, como los tipos de devolución, y como variables locales) de punto flotante los números se representan utilizando una interna de tipo de punto flotante. En cada uno de los tal instancia, el tipo nominal de la variable o expresión es ya sea float32or float64, pero su valor puede ser representado internamente con un rango adicional y/o precisión. El tamaño de la interna representación de punto flotante es dependiente de la implementación, puede variar, y tendrá la precisión de, al menos, tan grande como la de la variable o la expresión ser representado.

Las palabras clave de esta cita son "dependiente de la implementación" y "puede variar". En la OP del caso, vemos su aplicación, de hecho, varían.

No strictfp aritmética de punto flotante en la plataforma Java también tiene un problema relacionado, para más info revise también mi respuesta a la Voluntad de operaciones de punto flotante sobre la JVM dan los mismos resultados en todas las plataformas?

24voto

Jon Skeet Puntos 692016

¿Cuál es la razón para este comportamiento?

Yo no puedo dar detalles de exactamente lo que está pasando en este caso específico, pero entiendo que el general del problema, y por qué utilizar Console.WriteLine puede cambiar las cosas.

Como vimos en el post anterior, a veces se realizan operaciones sobre los tipos de punto flotante en precisión mayor que la especificada en el tipo de variable. Para las variables locales, que pueden incluir cómo el valor se almacena en la memoria durante la ejecución de un método.

Sospecho que, en su caso:

  • la Sum método en línea (ver más adelante)
  • la misma suma se realiza con mayor precisión que la de 32 bits en coma flotante que usted esperaría
  • el valor de una de las variables (f dicen) está siendo almacenada en una alta precisión de registro
    • para esta variable, el "más precisos" el resultado está siendo almacenada directamente
  • el valor de la otra variable (g) se almacena en la pila como un valor de 32 bits
    • para esta variable, el "más precisos" el resultado está siendo reducido a 32 bits
  • cuando la comparación se realiza, la variable en la pila está siendo promovido a un mayor valor de precisión y en comparación con otros de mayor valor de precisión, y la diferencia es debido a uno de ellos tras haber perdido información y los otros no

Cuando se quite el comentario de la Console.WriteLine declaración, supongo que (por el motivo que sea) de las fuerzas de ambas variables para que sean almacenados en sus "propias" de 32 bits de precisión, por lo que ambos están siendo tratados de la misma manera.

Esta hipótesis está todo un poco desordenado por el hecho de que la adición de

[MethodImpl(MethodImplOptions.NoInlining)]

... hace que no cambie el resultado de tan lejos como puedo ver. Puedo estar haciendo algo mal a lo largo de esas líneas, aunque.

Realmente, debemos buscar en la asamblea de código que se está ejecutando - no tengo tiempo de hacerlo ahora, por desgracia.

14voto

Bas Brekelmans Puntos 13799

(No es una respuesta real, pero espero que algún tipo de documentación de apoyo)

Configuración: Core i7, Windows 8.1, Visual Studio 2013

Plataforma x86:

 Version      Optimized Code?        Debugger Enabled?          Outcome
4.5.1        Yes                    No                         Not equal
4.5.1        Yes                    Yes                        Equal
4.5.1        No                     No                         Equal
4.5.1        No                     Yes                        Equal
2.0          Yes                    No                         Not Equal
2.0          Yes                    Yes                        Equal
2.0          No                     No                         Equal
2.0          No                     Yes                        Equal
 

X64 Plataforma:

 Version      Optimized Code?        Debugger Enabled?          Outcome
4.5.1        Yes                    No                         Equal
4.5.1        Yes                    Yes                        Equal
4.5.1        No                     No                         Equal
4.5.1        No                     Yes                        Equal
2.0          Yes                    No                         Equal
2.0          Yes                    Yes                        Equal
2.0          No                     No                         Equal
2.0          No                     Yes                        Equal
 

La situación sólo parece ocurrir con código optimizado en configuraciones x86.

4voto

He aquí una explicación de una entrada del blog de ​​MSDN, CLR y punto flotante: Algunas respuestas a preguntas comunes .

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X