提问人:Mud 提问时间:6/29/2023 最后编辑:Cole TobinMud 更新时间:7/5/2023 访问量:354
System.Math.Round 损坏
System.Math.Round corruption
问:
我有一个系统,在运行几个小时后开始产生不正确的值。我在调试器下重现了它,发现问题是开始返回不正确的值。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
答:
@HansPassant在评论中发现了问题:
Hans:“你的进程的位数很重要,我猜是 32 位(又名 x86)。在这种情况下,Round() 由 FRNDINT fpu 指令实现。其行为受 FPU 控制寄存器中选择的舍入模式的影响。使用“调试”> Windows >寄存器,右键单击该工具窗口并勾选“浮点”。.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);
}
}
这解决了我们的舍入问题。
评论
CTRL = 027F
CTRL = 067F