我已经阅读了很多关于 .NET 中浮点确定性的内容,即确保具有相同输入的相同代码在不同的机器上给出相同的结果。由于 .NET 缺少 Java 的 fpstrict 和 MSVC 的 fp:strict 之类的选项,因此一致认为使用纯托管代码无法解决此问题。C# 游戏 AI Wars 已决定使用定点数学,但这是一个繁琐的解决方案。
主要问题似乎是 CLR 允许中间结果存在于精度高于类型的本机精度的 FPU 寄存器中,从而导致无法预测的高精度结果。CLR 工程师 David Notario的MSDN 文章解释了以下内容:
请注意,使用当前规范,它仍然是提供“可预测性”的语言选择。该语言可以在每个 FP 操作之后插入 conv.r4 或 conv.r8 指令以获得“可预测”的行为。 显然,这确实很昂贵,而且不同的语言有不同的折衷方案。例如,C# 什么都不做,如果你想缩小,你将不得不手动插入 (float) 和 (double) 强制转换。
这表明可以通过为每个表达式和计算结果为 float 的子表达式插入显式强制转换来实现浮点确定性。有人可能会围绕 float 编写一个包装器类型来自动执行此任务。这将是一个简单而理想的解决方案!
然而,其他评论表明它并不那么简单。Eric Lippert 最近表示(强调我的):
在运行时的某些版本中,显式转换为浮动会产生与不这样做不同的结果。当您显式转换为浮点数时,C# 编译器会提示运行时说“如果您碰巧使用此优化,请将此事物退出超高精度模式”。
这个运行时的“提示”是什么?C# 规范是否规定显式强制转换为浮点数会导致在 IL 中插入 conv.r4?CLR 规范是否规定 conv.r4 指令会导致将值缩小到其本机大小?只有当这两个都为真时,我们才能依靠显式转换来提供浮点“可预测性”,正如 David Notario 所解释的那样。
最后,即使我们确实可以将所有中间结果强制转换为类型的原生大小,这是否足以保证跨机器的可重复性,或者是否还有其他因素,例如 FPU/SSE 运行时设置?