Asi*_*sik 47 .net c# floating-point ieee-754
我一直在阅读很多关于.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运行时设置?
Eri*_*ert 28
这对运行时的"提示"是什么?
正如您猜想的那样,编译器会跟踪源代码中是否实际存在转换为double或float,如果是,则始终插入适当的转换操作码.
C#规范是否规定显式转换为float会导致在IL中插入conv.r4?
不,但我向您保证,在编译器测试用例中有单元测试可以确保它完成.虽然规范不要求它,但您可以依赖此行为.
规范的唯一注释是任何浮点运算都可以以比运行时突发奇想所需的精度更高的精度完成,并且这可以使您的结果意外地更准确.见4.1.6节.
CLR规范是否规定conv.r4指令会使值缩小到其原始大小?
是的,在第I.1节的第12.1.3节中,我注意到你可以自己抬头而不是要求互联网为你做这件事.这些规范在网上免费提供.
你没有问过但可能应该有的问题:
除了强制模式之外,还有除了浮动之外的任何操作吗?
是.分配给静态字段,实例字段double[]或float[]数组的元素会截断.
是否一致截断足以保证机器间的重复性?
不,我鼓励你阅读第12.1.3节,其中有关非正规和NaN的主题有很多有趣的说法.
最后,你没有问的另一个问题,但可能应该有:
我怎样才能保证可重复的算术?
使用整数.
Han*_*ant 25
8087浮点单元芯片设计是英特尔十亿美元的错误.这个想法在纸上看起来不错,给它一个8寄存器堆栈,以80位扩展精度存储值.这样您就可以编写中间值不太可能丢失有效数字的计算.
然而,野兽无法优化.将FPU堆栈中的值存储回内存非常昂贵.因此,将它们保留在FPU中是一个强大的优化目标.不可避免的是,如果计算足够深,只有8个寄存器就需要回写.它也被实现为堆栈,而不是可自由寻址的寄存器,因此需要体操也可能产生回写.回写不可避免地会将值从80位截断回64位,从而失去精度.
因此,非优化代码不会产生与优化代码相同的结果.当中间值最终需要写回时,计算的微小变化会对结果产生很大影响./ fp:strict选项是一个黑客攻击,它强制代码生成器发出回写以保持值一致,但是不可避免且相当大的性能损失.
这是一个完整的岩石和一个艰难的地方.对于x86抖动,他们只是没有尝试解决问题.
英特尔在设计SSE指令集时没有犯同样的错误.XMM寄存器可自由寻址,不存储额外的位.如果您想要一致的结果,那么使用AnyCPU目标和64位操作系统进行编译是快速解决方案.x64抖动使用SSE而不是FPU指令进行浮点数学运算.虽然这增加了计算可以产生不同结果的第三种方式.如果计算错误,因为它丢失了太多有效数字,那么它将始终是错误的.实际上,这有点像溴化物,但通常只有程序员看起来.
| 归档时间: |
|
| 查看次数: |
4835 次 |
| 最近记录: |