根据@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.Cdecl
在ControlFPS
声明中指定以避免在调试时出现不平衡的堆栈异常。其次,我不得不求助于不安全的代码来检索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 的情况,您可以使用上面的示例应用程序并在每台机器上运行它。我想你会发现:
- 机器 A 和机器 B 从一开始就使用不同的 _controlfp_s 设置。示例应用程序将在机器 A 上的第一个输出块中显示与机器 B 上不同的控制字值。应用程序强制非正规控制保存后,输出应该匹配。如果是这种情况,那么也许您可以在应用程序启动时强制非规范控制在机器 B 上保存。
- 机器 A 和机器 B 对 _controlfp_s 使用相同的设置,并且示例应用程序的输出在两台机器上完全相同。如果是这种情况,那么您的应用程序(可能是 DirectX、WPF?)中一定有一些代码正在翻转机器 B 上的 _controlfp_s 设置,而不是机器 A 上的设置。
如果您有机会在每台机器上试用示例应用程序,请使用结果更新评论。我有兴趣看看会发生什么。