不,这不是另一个"为什么是(1/3.0)*3!= 1"的问题.
我最近一直在读关于漂浮点的事情; 具体而言,相同的计算如何在不同的体系结构或优化设置上给出不同的结果.
对于存储重放的视频游戏来说,这是一个问题,或者是对等网络(而不是服务器 - 客户端),它依赖于每次运行程序时产生完全相同结果的所有客户端 - 一个小的差异浮点计算可能导致不同机器(甚至是同一台机器上)的游戏状态截然不同!
甚至在"跟随" IEEE-754的处理器中也会发生这种情况,这主要是因为某些处理器(即x86)使用双倍扩展精度.也就是说,它们使用80位寄存器进行所有计算,然后截断为64位或32位,导致与使用64位或32位进行计算的机器不同的舍入结果.
我在网上看到过这个问题的几种解决方案,但都是针对C++,而不是C#:
double使用_controlfp_s(Windows),_FPU_SETCW(Linux?)或fpsetprec(BSD)禁用双扩展精度模式(以便所有计算使用IEEE-754 64位).float和double完全.decimal可以用于此目的,但会慢得多,并且没有任何System.Math库函数支持它.那么,这在C#中是否也是一个问题? 如果我只打算支持Windows(而不是Mono)怎么办?
如果是,有没有办法强制我的程序以正常的双精度运行?
如果没有,是否有任何库可以帮助保持浮点计算的一致性?
浮点计算既不是关联的,也不是处理器上的分配.所以,
(a + b) + c 不等于 a + (b + c)
和a * (b + c)不等于a * b + a * c
有没有办法执行不会给出不同结果的确定性浮点计算.它对于单处理器来说是确定性的,但是如果线程增加一个总和,它在多线程程序中就不是确定性的,因为线程可能有不同的交错.
所以我的问题是,如何在多线程程序中实现浮点计算的确定性结果?
我正在开发一个使用锁步模型在网络上播放的跨平台游戏.作为简要概述,这意味着只传达输入,并且在每个客户端的计算机上模拟所有游戏逻辑.因此,一致性和决定性非常重要.
我正在编译使用GCC 4.8.1的MinGW32上的Windows版本,而在Linux上我正在使用GCC 4.8.2进行编译.
最近让我感到震惊的是,当我的Linux版本连接到我的Windows版本时,即使在两台机器上编译相同的代码,程序也会立即发生分歧或解除同步!原来问题是Linux构建是通过64位编译的,而Windows版本是32位.
在编译Linux 32位版本之后,我很幸运地解决了问题已经解决的问题.然而,它让我思考和研究浮点决定论.
这就是我收集的内容:
如果是,程序通常是一致的:
- 在相同的架构上运行
- 使用相同的编译器编译
因此,如果我假设,针对PC市场,每个人都有一个x86处理器,那么解决了一个要求.但是,第二个要求似乎有点傻.
MinGW,GCC和Clang(分别是Windows,Linux,Mac)都是基于/兼容GCC的不同编译器.这是否意味着实现跨平台决定论是不可能的?或者它只适用于Visual C++ vs GCC?
同样,优化标志-O1或-O2会影响这种决定论吗?让他们离开会更安全吗?
最后,我有三个问题要问:
- 1)在编译器中使用MinGW,GCC和Clang时,跨平台确定性是否可行?
- 2)应在这些编译器中设置哪些标志以确保操作系统/ CPU之间的最大一致性?
- 3)浮点精度对我来说并不重要 - 重要的是它们是一致的.有没有任何方法可以将浮点数减少到较低的精度(如3-4个小数位),以确保跨系统的小圆整错误不存在?(到目前为止我尝试写的每个实现都失败了)
编辑:我做过一些跨平台的实验.
使用浮点数表示速度和位置,我保持Linux Intel笔记本电脑和Windows AMD台式电脑同步,最多可以浮动值的15位小数.但是,这两个系统都是x86_64.测试很简单 - 它只是通过网络移动实体,试图确定任何可见的错误.
如果x86计算机连接到x86_64计算机,假设相同的结果会成立是否有意义?(32位与64位操作系统)
x87 FPU值得注意的是使用内部80位精度模式,这通常会导致编译器和机器出现意外和不可重现的结果.在我搜索 .NET上可重现的浮点数学时,我发现.NET(Microsoft和Mono)的两个主要实现都在64位模式下发出SSE指令而不是x87.
SSE(2)严格使用32位寄存器用于32位浮点数,严格使用64位寄存器用于64位浮点数.通过设置适当的控制字,可以选择将非正规数刷新为零.
因此,似乎SSE不会受到x87的精度相关问题的影响,并且唯一的变量是可以控制的非正规行为.
抛开超越函数的问题(SSE本身不像x87那样提供),是否使用SSE保证了机器和编译器之间可重现的结果?例如,编译器优化会转化为不同的结果吗?我发现了一些相互矛盾的观点:
如果您有SSE2,请使用它并从此过上幸福的生活.SSE2支持32b和64b操作,中间结果具有操作数的大小.- Yossi Kreinin,http://www.yosefk.com/blog/consistency-how-to-defeat-the-purpose-of-ieee-floating-point.html
...
SSE2指令(...)完全符合IEEE754-1985标准,它们具有更好的可重复性(由于静态舍入精度)和其他平台的可移植性.Muller et aliis, Handbook of Floating-Point Arithmetic - p.107
然而:
此外,您不能将SSE或SSE2用于浮点,因为它太低于指定而不具有确定性.- John Watte http://www.gamedev.net/topic/499435-floating-point-determinism/#entry4259411
我想知道在C或C ++中使用浮点运算的任何代码在任何基于x86的体系结构中是否都会产生精确的结果,而不管代码的复杂性如何。
据我所知,自从Intel 8087开始,任何x86架构都使用了准备处理IEEE-754浮点数的FPU单元,而且我看不出任何原因导致不同架构的结果不同。但是,如果它们不同(即由于不同的编译器或不同的优化级别),那么是否有某种方法可以通过仅配置编译器来产生位精确结果?