在.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运行时设置?
有只小跳蛙