Mud*_*Mud 19 .net c# floating-point
我的系统在运行几个小时后开始产生错误的值。我在调试器下重现了它,发现问题System.Math.Round
开始返回错误的值。
我有两个相同版本的 Visual Studio 实例,在同一台计算机上并行运行,具有相同的项目、相同的代码、堆栈跟踪的相同部分 - 一切都是相同的 - 除了一个一直在运行几个小时后已经开始失败,而另一个却没有。
我在各自的立即窗口中执行常量表达式,并获得不同的值。
在良好的运行中:
在糟糕的运行中:
这个小差异对我的应用程序有重大影响。
.NET 版本,从运行代码中转储:
System.Environment.Version
=> 4.0.30319.42000
(typeof(string).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false))[0]
=> 4.8.4644.0
有没有人见过这个?这是一个已知的错误吗?有什么办法可以解决这个问题吗?
编辑:@Kit 不信任立即窗口,所以这里有更多信息。我展示了立即窗口结果,因为它可以让您看到相同的常量表达式从 中产生不同的结果Math.Round
。下面是实际代码中相关的行,您可以看到它也在Math.Round
实际代码中产生了错误的值:
Mud*_*Mud 13
@HansPassant 在评论中指出了问题:
Hans:“进程的位数非常重要,我猜是 32 位(又名 x86)。在这种情况下,Round() 由 FRNDINT fpu 指令实现。其行为受到在FPU 控制寄存器。使用“调试”>“窗口”>“寄存器”,右键单击该工具窗口并勾选“浮点”。.NET 程序必须始终使用 CTRL = 027F 进行操作。单步执行您的程序,当您看到它发生变化时,您发现邪恶代码。”
这是完全正确的。它是一个 32 位 .NET 应用程序,这显然意味着它使用 FPU 而不是 SSE 指令。这些是好实例与坏实例中的浮点寄存器:
汉斯:“067F的意思是从‘四舍五入’改为‘四舍五入’。”
该代码库只需要在退役之前成功运行一次,因此我没有尝试查找哪个非托管依赖项正在更改此标志以及何时更改。相反,我只是将类似的内容添加到我的应用程序中,并在执行重要工作之前调用它:
[DllImport("msvcrt.dll")]
private static extern int _controlfp(int IN_New, int IN_Mask);
public static void VerifyFpuRoundingMode()
{
const int _MCW_RC = 0x00000300;
const int _RC_NEAR = 0x00000000;
int ctrl = _controlfp(0, 0);
if ((ctrl & _MCW_RC) != 0)
{
_controlfp(_RC_NEAR, _MCW_RC);
}
}
Run Code Online (Sandbox Code Playgroud)
这解决了我们的舍入问题。