在.NET中强制浮点是确定性的吗?

在.NET中强制浮点是确定性的吗?

我一直在阅读很多关于.NET中浮点确定性的内容,即确保具有相同输入的相同代码将在不同的机器上提供相同的结果。由于.NET缺少Java的fpstrict和MSVC的fp:strict等选项,因此似乎一致认为使用纯托管代码无法绕过这个问题。C#游戏AI Wars已经决定使用定点数学,但这是一个麻烦的解决方案。

主要问题似乎是CLR允许中间结果存在于FPU寄存器中,这些寄存器具有比类型的原始精度更高的精度,从而导致不可预测的更高精度结果。CLR工程师David Notario撰写MSDN文章解释了以下内容:

请注意,对于当前规范,它仍然是提供“可预测性”的语言选择。在每次FP操作之后,该语言可以插入conv.r4或conv.r8指令以获得“可预测的”行为。 显然,这非常昂贵,不同的语言有不同的妥协。例如,C#什么都不做,如果你想缩小范围,你必须手动插入(浮点)和(双)强制转换。

这表明,只需为每个表达式和计算浮点数的子表达式插入显式强制转换,就可以实现浮点确定性。有人可能会在float周围编写一个包装器类型来自动执行此任务。这将是一个简单而理想的解决方案!

然而,其他评论表明它并非如此简单。Eric Lippert最近表示(强调我的):

在某些版本的运行时中,显式转换为float会产生与不这样做不同的结果。当你明确地转换为float时,C#编译器会给运行时提供一个提示,说“如果碰巧使用这个优化,就把这个东西从超高精度模式中取出”。

这对运行时的“提示”是什么?C#规范是否规定显式转换为float会导致在IL中插入conv.r4?CLR规范是否规定conv.r4指令会使值缩小到其原始大小?只有当这两者都成立时,我们才能依靠显式转换来提供浮点“可预测性”,如David Notario所解释的那样。

最后,即使我们确实能够将所有中间结果强制转换为类型的原生大小,这是否足以保证跨机器的可重复性,还是有其他因素如FPU / SSE运行时设置?


犯罪嫌疑人X
浏览 481回答 2
2回答

有只小跳蛙

8087浮点单元芯片设计是英特尔十亿美元的错误。这个想法在纸上看起来不错,给它一个8寄存器堆栈,以80位扩展精度存储值。这样您就可以编写中间值不太可能丢失有效数字的计算。然而,野兽无法优化。将FPU堆栈中的值存储回内存非常昂贵。因此,将它们保留在FPU中是一个强大的优化目标。不可避免的是,如果计算足够深,只有8个寄存器就需要回写。它也被实现为堆栈,而不是可自由寻址的寄存器,因此需要体操也可能产生回写。回写不可避免地会将值从80位截断回64位,从而失去精度。因此,非优化代码不会产生与优化代码相同的结果。当中间值最终需要写回时,计算的微小变化会对结果产生很大影响。/ fp:strict选项是一个黑客攻击,它强制代码生成器发出回写以保持值一致,但是不可避免且相当大的性能损失。这是一个完整的岩石和一个艰难的地方。对于x86抖动,他们只是没有尝试解决问题。英特尔在设计SSE指令集时没有犯同样的错误。XMM寄存器可自由寻址,不存储额外的位。如果您想要一致的结果,那么使用AnyCPU目标和64位操作系统进行编译是快速解决方案。x64抖动使用SSE而不是FPU指令进行浮点数学运算。虽然这增加了计算可以产生不同结果的第三种方式。如果计算错误,因为它丢失了太多有效数字,那么它将一直是错误的。实际上,这有点像溴化物,但通常只有程序员看起来。
打开App,查看更多内容
随时随地看视频慕课网APP