.NET Math.Log10() 在不同机器上的行为不同

发布于 2024-11-28 22:53:10 字数 805 浏览 1 评论 0原文

我发现在机器 A 上运行

Math.Log10(double.Epsilon) 

将返回大约 -324,但在机器 B 上将返回 -Infinity

它们最初的行为方式相同,返回 -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 和组件版本。
  • 在机器 B 上的简单控制台应用程序中打印 Math.Log10(double.Epsilon) 会打印 -324,而不是 -Infinity
  • FPU 控制字两台机器始终为 0x9001F(使用 _controlfp() 读取)。

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

I found that running

Math.Log10(double.Epsilon) 

will return about -324 on machine A, but will return -Infinity on machine B.

They originally behaved the same way by returning -324.

Both machines started out using the same OS (WinXP SP3) and .NET version (3.5 SP1). There may have been Windows updates on machine B, but otherwise no changes are known to have happened.

What could explain the difference in behavior?

More details from discussions in comments:

  • Machine A CPU is a 32-bit Intel Core Duo T2500 2 GHz
  • Machine B CPU is a 32-bit Intel P4 2.4 GHz
  • Results collected from code running in a large application using several 3rd party components. However, same .exe and component versions are running on both machines.
  • Printing Math.Log10(double.Epsilon) in a simple console application on machine B prints -324, NOT -Infinity
  • The FPU control word on both machines is always 0x9001F (read with _controlfp()).

UPDATE: The last point (FPU control word) is no longer true: Using a newer version of _controlfp() revealed different control words, which explains the inconsistent behavior. (See rsbarro's answer below for details.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

愁杀 2024-12-05 22:53:10

根据 @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 上不同的控制字值。应用程序强制非正规控制保存后,输出应该匹配。如果是这种情况,那么也许您可以在应用程序启动时强制非规范控制在机器 B 上保存。
  2. 机器 A 和机器 B 对 _controlfp_s 使用相同的设置,并且示例应用程序的输出在两台机器上完全相同。如果是这种情况,那么您的应用程序中一定有一些代码(可能是 DirectX、WPF?)会翻转机器 B 上的 _controlfp_s 设置,但不会翻转机器 A 上的 _controlfp_s 设置。

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

Based on the comments by @CodeInChaos and @Alexandre C, I was able to throw together some code to reproduce the issue on my PC (Win7 x64, .NET 4.0). It appears this issue is due to the denormal control that can be set using _controlfp_s. The value of double.Epsilon is the same in both cases, but the way it is evaluated changes when the denormal control is switched from SAVE to FLUSH.

Here is the sample code:

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

A couple things to note. First, I had to specify CallingConvention = CallingConvention.Cdecl on the ControlFPS declaration to avoid getting an unbalanced stack exception while debugging. Second, I had to resort to unsafe code to retrieve the value of the control word in GetCurrentControlWord(). If anyone knows of a better way to write that method, please let me know.

Here is the output:

_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

To determine what is going on with machine A and machine B, you could take the sample app above and run it on each machine. I think you're going to find that either:

  1. Machine A and Machine B are using different settings for _controlfp_s right from the start. The sample app will show different control word values in the first block of outputs on Machine A than it does on Machine B. After the app forces the Denormal control to SAVE, then the output should match. If this is the case then maybe you can just force the denormal control to SAVE on Machine B when your application starts up.
  2. Machine A and Machine B are using the same settings for _controlfp_s, and the output of the sample app is exactly the same on both machines. If that is the case, then there must be some code in your application (possibly DirectX, WPF?) that is flipping the _controlfp_s settings on Machine B but not on Machine A.

If you get a chance to try out the sample app on each machine, please update the comments with the results. I'm interested to see what happens.

束缚m 2024-12-05 22:53:10

进程中加载​​的 dll 可能会扰乱 x87 浮点标志。 DirectX/OpenGL 相关库因此而臭名昭著。

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

在 .net 中,常量被嵌入到调用代码中,因此 double.Epsilons 之间不应该有任何差异。

It's possible that a dll was loaded into the process that messed with the x87 floating-point flags. DirectX/OpenGL related libraries are notorious for this.

There could also be differences in the jitted code(There is no requirement for floating points to behave a specific way in .net), but that's very unlikely since you use the same .net and OS version.

In .net constants get baked into the calling code, so there should be no differences between the double.Epsilons.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文