. NET Math.Log10() se comporta de manera diferente en diferentes máquinas


Encontré que correr

Math.Log10(double.Epsilon) 

Regresará alrededor de -324 en la máquina A, pero regresará -Infinity en la máquina B.

Originalmente se comportaron de la misma manera devolviendo -324.

Ambas máquinas comenzaron usando el mismo sistema operativo (WinXP SP3) y la versión.NET (3.5 SP1). Es posible que haya habido actualizaciones de Windows en la máquina B, pero por lo demás no se sabe que hayan ocurrido cambios.

¿Qué podría explicar la diferencia en el comportamiento?

Más detalles de las discusiones en observaciones:

  • Máquina Una CPU es un 32-bit Intel Core Duo T2500 2 GHz
  • La CPU de la máquina B es una Intel P4 de 32 bits de 2,4 GHz
  • Resultados obtenidos del código que se ejecuta en una aplicación grande utilizando varios componentes de terceros. Sin embargo, igual .las versiones exe y component se ejecutan en ambas máquinas.
  • Imprimir Math.Log10(double.Epsilon) en una aplicación de consola simple en la máquina B imprime -324, NO -Infinity
  • La palabra de control FPU en ambas máquinas es siempre 0x9001F (leer con _controlfp()).

ACTUALIZACIÓN: El último punto (palabra de control FPU) ya no es verdadero: El uso de una versión más reciente de _controlfp() reveló diferentes palabras de control, lo que explica el comportamiento inconsistente. (Vea la respuesta de rsbarro a continuación para más detalles.)

Author: Johannes Petzold, 2011-08-09

2 answers

Basado en los comentarios de @CodeInChaos y @Alexandre C, pude juntar algo de código para reproducir el problema en mi PC (Win7 x64,. NET 4.0). Parece que este problema se debe al control denormal que se puede establecer usando _controlfp_s. El valor del doble.Epsilon es el mismo en ambos casos, pero la forma en que se evalúa cambia cuando el control denormal se cambia de GUARDAR a FLUSH.

Aquí está el código de ejemplo:

using System;
using System.Runtime.InteropServices;

namespace fpuconsole
{
    class Program
    {
        [DllImport("msvcrt.dll", EntryPoint = "_controlfp_s",
            CallingConvention = CallingConvention.Cdecl)]
        public static extern int ControlFPS(IntPtr currentControl, 
            uint newControl, uint mask);

        public const int MCW_DN= 0x03000000;
        public const int _DN_SAVE = 0x00000000;
        public const int _DN_FLUSH = 0x01000000;

        static void PrintLog10()
        {
            //Display original values
            Console.WriteLine("_controlfp_s Denormal Control untouched");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}",
                Math.Log10(double.Epsilon));
            Console.WriteLine("");

            //Set Denormal to Save, calculate Math.Log10(double.Epsilon)
            var controlWord = new UIntPtr();
            var err = ControlFPS(controlWord, _DN_SAVE, MCW_DN);
            if (err != 0)
            {
                Console.WriteLine("Error setting _controlfp_s: {0}", err);
                return;
            }
            Console.WriteLine("_controlfp_s Denormal Control set to SAVE");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}", 
                Math.Log10(double.Epsilon));
            Console.WriteLine("");

            //Set Denormal to Flush, calculate Math.Log10(double.Epsilon)
            err = ControlFPS(controlWord, _DN_FLUSH, MCW_DN);
            if (err != 0)
            {
                Console.WriteLine("Error setting _controlfp_s: {0}", err);
                return;
            }
            Console.WriteLine("_controlfp_s Denormal Control set to FLUSH");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}", 
                Math.Log10(double.Epsilon));
            Console.WriteLine("");
        }

        static int GetCurrentControlWord()
        {
            unsafe
            {
                var controlWord = 0;
                var controlWordPtr = &controlWord;
                ControlFPS((IntPtr)controlWordPtr, 0, 0);
                return controlWord;
            }
        }

        static void Main(string[] args)
        {
            PrintLog10();
        }
    }
}

Un par de cosas a tener en cuenta. Primero, tuve que especifique CallingConvention = CallingConvention.Cdecl en la declaración ControlFPS para evitar obtener una excepción de pila no balanceada durante la depuración. En segundo lugar, tuve que recurrir al código inseguro para recuperar el valor de la palabra de control en GetCurrentControlWord(). Si alguien sabe de una mejor manera de escribir ese método, por favor hágamelo saber.

Aquí está la salida:

_controlfp_s Denormal Control untouched
        Current _controlfp_s control word: 0x0009001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -323.306215343116

_controlfp_s Denormal Control set to SAVE
        Current _controlfp_s control word: 0x0009001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -323.306215343116

_controlfp_s Denormal Control set to FLUSH
        Current _controlfp_s control word: 0x0109001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -Infinity

Para determinar qué está pasando con la máquina A y la máquina B, puede tomar la aplicación de muestra anterior y ejecutarla en cada máquina. Creo que vas a encontrar que o bien:

  1. La máquina A y la máquina B están usando diferentes configuraciones para _controlfp_s desde el principio. La aplicación de ejemplo mostrará diferentes valores de palabras de control en el primer bloque de salidas en la máquina A que lo hace en la máquina B. Después de que la aplicación fuerza al control Denormal a GUARDAR, entonces la salida debe coincidir. Si este es el caso, entonces tal vez pueda forzar el control denormal para GUARDAR en la Máquina B cuando se inicie su aplicación.
  2. La máquina A y la máquina B están utilizando la misma configuración para _controlfp_s, y la salida de la aplicación de ejemplo es exactamente la misma en ambas máquinas. Si ese es el caso, entonces debe haber algún código en su aplicación (posiblemente DirectX, WPF?) que está cambiando la configuración _controlfp_s en la máquina B pero no en la máquina A.

Si tiene la oportunidad de probar la aplicación de ejemplo en cada máquina, actualice los comentarios con los resultados. Estoy interesado en ver qué pasa.

 22
Author: rsbarro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-10 05:37:31

Es posible que se haya cargado una dll en el proceso que se haya metido con las banderas de coma flotante x87. Las bibliotecas relacionadas con DirectX / OpenGL son notorias por esto.

También podría haber diferencias en el código jitted(no hay ningún requisito para que los puntos flotantes se comporten de una manera específica en.net), pero eso es muy poco probable ya que usa la misma versión de. net y SO.

En.net las constantes se hornean en el código de llamada, por lo que no debería haber diferencias entre double.Epsilons.

 8
Author: CodesInChaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-09 09:33:47