.NET Math.Log10 () ведет себя по-разному на разных машинах - PullRequest
32 голосов
/ 09 августа 2011

Я обнаружил, что работает

Math.Log10(double.Epsilon) 

вернет около -324 на машине A, но вернет -Infinity на машине B.

Они изначально вели себя так же, возвращая -324.

Обе машины начали использовать одну и ту же ОС (WinXP SP3) и версию .NET (3.5 SP1). Возможно, на компьютере B были обновления Windows, но в противном случае неизвестно, что какие-либо изменения произошли.

Чем можно объяснить разницу в поведении?

Подробнее из обсуждений в комментариях:

  • Машина Процессор представляет собой 32-разрядный процессор Intel Core Duo T2500 2 ГГц
  • Процессор Machine B представляет собой 32-разрядный процессор Intel P4 2,4 ГГц
  • Результаты, полученные из кода, запущенного в большом приложении с использованием нескольких сторонних компонентов. Однако на обеих машинах работают одинаковые версии .exe и компонентов.
  • Печать Math.Log10(double.Epsilon) в простом консольном приложении на машине B печатает -324, НЕ -Infinity
  • Управляющее слово FPU на обеих машинах всегда 0x9001F (читается с _controlfp()).

ОБНОВЛЕНИЕ: последняя точка (управляющее слово FPU) больше не соответствует действительности: использование более новой версии _controlfp () выявило разные управляющие слова, что объясняет противоречивое поведение. (Подробнее см. Ответ rsbarro ниже.)

Ответы [ 2 ]

22 голосов
/ 09 августа 2011

Основываясь на комментариях @CodeInChaos и @Alexandre C, я смог собрать некоторый код для воспроизведения проблемы на моем ПК (Win7 x64, .NET 4.0).Похоже, что эта проблема связана с ненормальным управлением, которое можно установить с помощью _controlfp_s .Значение double.Epsilon одинаково в обоих случаях, но способ его оценки изменяется при переключении ненормального управления с SAVE на FLUSH.

Вот пример кода:

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();
        }
    }
}

Пара вещей, на которые стоит обратить внимание.Сначала я должен был указать CallingConvention = CallingConvention.Cdecl в объявлении ControlFPS, чтобы избежать получения несбалансированного стека во время отладки.Во-вторых, мне пришлось прибегнуть к небезопасному коду, чтобы получить значение контрольного слова в GetCurrentControlWord().Если кто-нибудь знает, как лучше написать этот метод, пожалуйста, сообщите мне.

Вот вывод:

_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

Чтобы определить, что происходит с машиной A и машиной B,Вы можете взять пример приложения выше и запустить его на каждой машине.Я думаю, вы обнаружите, что:

  1. Машина A и Машина B используют разные настройки для _controlfp_s с самого начала.Пример приложения покажет значения управляющего слова в первом блоке выходов на компьютере A, отличные от значений на компьютере B. После того, как приложение принудит Денормированный элемент управления к SAVE, выходные данные должны совпадать.Если это так, то, возможно, вы можете просто принудительно заставить денормализованный элемент управления SAVE на компьютере B при запуске приложения.
  2. На компьютере A и на машине B используются одинаковые настройки для _controlfp_s и вывода примераПриложение абсолютно одинаково на обеих машинах.Если это так, то в вашем приложении должен быть какой-то код (возможно, DirectX, WPF?), Который переворачивает настройки _controlfp_s на компьютере B, но не на компьютере A.

Если вы получилишанс попробовать пример приложения на каждой машине, пожалуйста, обновите комментарии с результатами.Мне интересно посмотреть, что произойдет.

8 голосов
/ 09 августа 2011

Возможно, что dll был загружен в процесс, который перепутался с флагами с плавающей запятой x87. Связанные с DirectX / OpenGL библиотеки печально известны этим.

Могут также быть различия в объединенном коде (не требуется, чтобы плавающие точки вели себя определенным образом в .net), но это очень маловероятно, поскольку вы используете одну и ту же версию .net и ОС.

В .net константы запекаются в вызывающем коде, поэтому между double.Epsilons.

не должно быть никаких различий.
...