. net Math.Log10()在不同的机器上表现不同

本文关键字:机器 Math net Log10 | 更新日期: 2023-09-27 18:04:56

我发现运行

Math.Log10(double.Epsilon) 

将返回机器A上的-324,但将返回机器b上的-Infinity

它们最初以相同的方式返回-324

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

如何解释行为上的差异?

在评论中讨论更多细节:

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

UPDATE:最后一点(FPU控制字)不再为真:使用较新版本的_controlfp()显示不同的控制字,这解释了不一致的行为。

. net Math.Log10()在不同的机器上表现不同

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

有几件事需要注意。首先,我必须在ControlFPS声明上指定CallingConvention = CallingConvention.Cdecl,以避免在调试时得到不平衡的堆栈异常。其次,我不得不使用不安全的代码来检索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上不同的控制字值。应用程序强制dennormal控件保存后,输出应该匹配。如果是这种情况,那么当你的应用程序启动时,你可以在机器B上强制正常的控件SAVE。
  2. 机器A和机器B对_controlfp_s使用相同的设置,并且示例应用程序的输出在两台机器上完全相同。如果是这种情况,那么你的应用程序中一定有一些代码(可能是DirectX, WPF?)在机器B上翻转_controlfp_s设置,但在机器a上没有。

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

有可能是一个dll被加载到一个与x87浮点标志混淆的进程中。DirectX/OpenGL相关库在这方面臭名昭著。

在编译的代码中也可能存在差异(在。net中没有要求浮点以特定的方式运行),但这是非常不可能的,因为您使用相同的。net和操作系统版本。

在。net中,常量被嵌入到调用代码中,因此double.Epsilons .

之间应该没有区别。