.Чистая математика.Log10 () ведет себя по-разному на разных машинах
Я обнаружил, что бегу
Math.Log10(double.Epsilon)
Вернет о -324
на машину А, но вернет -Infinity
на машину Б.
Первоначально они вели себя точно так же, возвращаясь -324
.
Подробнее из обсуждений в комментарии:
- Машина процессор-это 32-разрядный Intel Core Duo T2500 2 ГГц
- процессор Machine B представляет собой 32-разрядный процессор Intel P4 2,4 ГГц
- результаты, полученные из кода, запущенного в большом приложении с использованием нескольких сторонних компонентов. Впрочем, то же самое .версии exe и компонентов работают на обеих машинах.
- печать
Math.Log10(double.Epsilon)
в простом консольном приложении на машине B печатает-324
, а не-Infinity
- управляющее слово FPU на обеих машинах всегда
0x9001F
(читается с помощью_controlfp()
).
UPDATE: последний пункт (управляющее слово FPU) больше не соответствует действительности: использование более новой версии _controlfp () выявило различные управляющие слова, что объясняет несогласованное поведение. (Подробнее см. ответ rsbarro ниже.)
2 ответа:
Основываясь на комментариях @CodeInChaos и @Alexandre C, я смог собрать вместе некоторый код для воспроизведения проблемы на моем ПК (Win7 x64, .NET 4.0). По-видимому, эта проблема связана с денормальным управлением, которое может быть установлено с помощью _controlfp_s. Значение двойное.Эпсилон одинаков в обоих случаях, но способ его оценки меняется, когда денормальный контроль переключается с сохранения на сброс.
Вот пример кода:
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()
. Если кто-нибудь знает лучший способ написать этот метод, пожалуйста, дайте мне знать.Вот результат:
Чтобы определить, что происходит с машинами A и B, вы можете взять пример приложения выше и запустить его на каждой машине. Я думаю, ты найдешь это. либо:_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 с самого начала используют разные настройки для _controlfp_s. Пример приложения покажет различные значения управляющих слов в первом блоке выходных данных на машине а, чем на машине Б. После того, как приложение принудительно сохранит Денормальный элемент управления, выходные данные должны совпадать. Если это так, то, возможно, вы можете просто заставить denormal control сэкономить на машине B, Когда ваше приложение запускается.
- машина А и машина б используют одни и те же настройки для _controlfp_s, и выходные данные примера приложения точно такие же на обеих машинах. Если это так, то в вашем приложении должен быть какой-то код (возможно DirectX, WPF?) то есть переключение настроек _controlfp_s на машине B, но не на машине A.
Если у вас есть возможность попробовать пример приложения на каждой машине, пожалуйста, обновите комментарии с результатами. Мне интересно посмотреть, что произойдет.
Возможно, что dll была загружена в процесс, который испортил флаги x87 с плавающей запятой. Библиотеки, связанные с DirectX/OpenGL, печально известны этим.
Также могут быть различия в jitted-коде (нет требования, чтобы плавающие точки вели себя определенным образом в .net), но это очень маловероятно, так как вы используете одну и ту же версию .net и OS.
В .net константы запекаются в вызывающем коде, поэтому не должно быть никаких различий между
double.Epsilons
.