.NET Math.Log10()在不同的计算机上表现不同

Joh*_*old 32 .net c# math epsilon

我发现跑步了

Math.Log10(double.Epsilon) 
Run Code Online (Sandbox Code Playgroud)

-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
  • 从使用多个第三方组件的大型应用程序中运行的代码收集的结果.但是,两台计算机上都运行相同的.exe和组件版本.
  • 打印Math.Log10(double.Epsilon)在机器B上打印一个简单的控制台应用程序-324,而不是-Infinity
  • 两台机器上的FPU控制字始终0x9001F(读取_controlfp()).

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

rsb*_*rro 22

基于由@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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有几点需要注意.首先,我必须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
Run Code Online (Sandbox Code Playgroud)

要确定机器A和机器B的运行情况,您可以使用上面的示例应用程序并在每台机器上运行它.我想你会发现:

  1. 机器A和机器B从一开始就对_controlfp_s使用不同的设置.示例应用程序将在机器A的第一个输出块中显示与机器B上不同的控制字值.应用程序强制将Denormal控件保存为SAVE后,输出应匹配.如果是这种情况,那么也许您可以在应用程序启动时强制将非正常控制在机器B上保存.
  2. 机器A和机器B对_controlfp_s使用相同的设置,并且示例应用程序的输出在两台机器上完全相同.如果是这种情况,那么你的应用程序中必须有一些代码(可能是DirectX,WPF?)正在翻转机器B上的_controlfp_s设置而不是机器A.

如果您有机会在每台计算机上试用示例应用程序,请使用结果更新注释.我很想知道会发生什么.

  • 您的上一条评论让我发现_controlfp()的行为有所不同,具体取决于使用的msvcr*.dll.与以前不同,我现在可以使用"msvcr100_clr0400.dll"而不是"msvcrt.dll"在任何32位CPU上重现该问题.我目前无法在机器B上运行测试,但我认为假设FPU控制字是解释不一致行为的根本原因是公平的.这个事实只是被_controlfp()的不一致实现所掩盖.谢谢你的帮助! (2认同)

Cod*_*aos 8

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

在jitted代码中也可能存在差异(浮点不需要在.net中以特定方式运行),但由于您使用相同的.net和OS版本,因此不太可能.

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

  • 我正在考虑非规范化标志.`Double.Epsilon`(**非常差**命名为btw,因为浮点的epsilon已经具有**非常不同的传统意义)被定义为"最大正双值大于零".这意味着它可能在您的机器上被非规范化,并且设置了非规范化标志,以便这些数字被截断为零,因为它们被馈送到"Log10".接得好. (3认同)