32

我发现正在运行

Math.Log10(double.Epsilon) 

-324在机器 A 上返回,但将-Infinity在机器 B 上返回。

他们最初的行为方式与返回相同-324

两台机器一开始都使用相同的操作系统 (WinXP SP3) 和 .NET 版本 (3.5 SP1)。机器 B 上可能有 Windows 更新,但除此之外没有发生任何更改。

什么可以解释行为上的差异?

评论中讨论的更多细节:

  • 机器 A CPU 是 32 位 Intel Core Duo T2500 2 GHz
  • 机器 B CPU 是 32 位 Intel P4 2.4 GHz
  • 从使用多个第 3 方组件的大型应用程序中运行的代码收集的结果。但是,两台机器上都运行相同的 .exe 和组件版本。
  • 在机器 B 上打印Math.Log10(double.Epsilon)一个简单的控制台应用程序-324,而不是-Infinity
  • 两台机器上的 FPU 控制字总是0x9001F(用 读取_controlfp())。

更新:最后一点(FPU 控制字)不再正确:使用较新版本的 _controlfp() 显示不同的控制字,这解释了不一致的行为。(有关详细信息,请参阅下面 rsbarro 的答案。)

4

2 回答 2

22

根据@CodeInChaos 和@Alexandre C 的评论,我能够拼凑一些代码来在我的PC(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.CdeclControlFPS声明中指定以避免在调试时出现不平衡的堆栈异常。其次,我不得不求助于不安全的代码来检索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 上不同的控制字值。应用程序强制非正规控制保存后,输出应该匹配。如果是这种情况,那么也许您可以在应用程序启动时强制非规范控制在机器 B 上保存。
  2. 机器 A 和机器 B 对 _controlfp_s 使用相同的设置,并且示例应用程序的输出在两台机器上完全相同。如果是这种情况,那么您的应用程序(可能是 DirectX、WPF?)中一定有一些代码正在翻转机器 B 上的 _controlfp_s 设置,而不是机器 A 上的设置。

如果您有机会在每台机器上试用示例应用程序,请使用结果更新评论。我有兴趣看看会发生什么。

于 2011-08-09T15:05:13.927 回答
8

有可能将 dll 加载到与 x87 浮点标志混淆的进程中。DirectX/OpenGL 相关库因此而臭名昭著。

jitted 代码也可能存在差异(浮点在 .net 中的行为没有特定要求),但这不太可能,因为您使用相同的 .net 和 OS 版本。

在 .net 中,常量被嵌入到调用代码中,因此 .net 之间应该没有区别double.Epsilons

于 2011-08-09T09:27:27.400 回答