Подтвердить что ты не робот

.NET Math.Log10() ведет себя по-разному на разных машинах

Я обнаружил, что запуск

Math.Log10(double.Epsilon) 

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

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

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

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

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

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

UPDATE: последняя точка (управляющее слово FPU) больше не верна: использование более новой версии _controlfp() выявило разные управляющие слова, что объясняет непоследовательное поведение. (Подробнее см. Rsbarro ниже).

4b9b3361

Ответ 1

Основываясь на комментариях @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, вы можете взять пример приложения выше и запустить его на каждой машине. Я думаю, вы найдете это:

  • Машины A и Machine B с самого начала используют разные настройки для _controlfp_s. Пример приложения отобразит различные значения управляющего слова в первом блоке выходов на машине А, чем на машине В. После того, как приложение заставит Денормальный элемент управления SAVE, результат должен совпадать. Если это так, возможно, вы можете просто заставить денормальный элемент управления СОХРАНИТЬ на машине B, когда ваше приложение запустится.
  • Машины A и Machine B используют одни и те же настройки для _controlfp_s, а выход примерного приложения на обеих машинах точно такой же. Если это так, то в вашем приложении должен быть какой-то код (возможно, DirectX, WPF?), Который переворачивает параметры _controlfp_s на машине B, но не на машине A.

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

Ответ 2

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

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

В константах .net выковываются в вызывающий код, поэтому между double.Epsilons не должно быть различий.