我想知道C或C++中使用浮点算术的任何代码是否会在任何基于x86的架构中产生位精确的结果,而不管代码的复杂性如何。
据我所知,自Intel 8087以来的任何x86体系结构都使用一个FPU单元来处理IEEE-754浮点数,我看不出有任何理由在不同的体系结构中得到不同的结果。然而,如果它们是不同的(即由于不同的编译器或不同的优化级别),是否有某种方法可以通过配置编译器来产生位精确的结果?
目录:
不,完全符合ISO C11和IEEE的C实现不能保证与其他C实现的位相同的结果,即使是在相同硬件上的其他实现。
相关:布鲁斯道森的浮点决定论博文是对这个问题的回答。他的开场白很有趣(后面还有很多有趣的东西):
IEEE浮点数学是确定性的吗?你总是从同样的输入得到同样的结果吗?答案是一个毫不含糊的“是”。不幸的是,答案也是一个毫不含糊的“不”。恐怕你需要澄清你的问题。
如果您正在考虑这个问题,那么您肯定想看看Bruce关于浮点数学的系列文章的索引,它是由x86上的C编译器实现的,也是asm和IEEE FP实现的。
其他数学库函数,如pow()
、log()
和sin()
允许实现者在速度和精度之间进行权衡。例如,glibc通常倾向于准确性,并且在某些函数方面比苹果的OS X数学库(IIRC)要慢。另请参阅Glibc的文档,其中说明了跨不同体系结构的每个libm函数的错误界限。
等等,情况会变得更糟。即使只使用正确舍入的基本操作的代码也不能保证相同的结果。
C规则还允许在保持更高精度的时间序列方面具有一定的灵活性。该实现定义了flt_eval_method
,这样代码就可以检测它的工作方式,但是如果您不喜欢该实现的工作,您就没有选择的余地。您可以选择(使用#pragma STDC FP_CONTRACT off
)来禁止编译器将a*b+c
转换为FMA,而在添加之前没有对临时的a*b
进行舍入。
在x86上,以32位非SSE代码为目标的编译器(即使用过时的x87指令)通常在操作之间在x87寄存器中保留FP临时文件。这会产生80位精度的flt_eval_method=2
行为。(该标准指定在每次赋值时仍会进行舍入,但像gcc这样的真正编译器实际上不会为舍入进行额外的存储/重新加载,除非您使用-ffloat-store
。请参阅https://gcc.gnu.org/wiki/floatingpointmath。该标准的这一部分似乎是在假设非优化编译器或硬件高效地为类型宽度提供舍入的情况下编写的,比如非x86,或者像x87这样将精度设置为round到64位double
而不是80位long double
。在每个语句之后存储正是gcc-o0
因此,当以x87为目标时,允许编译器使用两条x87fadd
指令计算三条float
的和,而不将前两条的和舍入为32位的float
。在这种情况下,临时有80位的精度...还是真的?并不总是这样,因为C实现的启动代码(或Direct3D库!!!)可能更改了x87控制字中的精度设置,因此x87寄存器中的值四舍五入为53或24位尾数。(这使得FDIV和FSQRT运行得更快一些。)所有这些都来自Bruce Dawson关于中间FP精度的文章)。
在舍入模式和精度设置相同的情况下,我认为每个x86 CPU都应该为相同的输入给出位相同的结果,即使是fsin这样的复杂x87指令也是如此。
Intel的手册并没有明确定义每种情况下的结果是什么,但我认为Intel的目标是位精确的向下兼容。例如,我怀疑他们是否会为FSIN添加扩展精度范围缩小功能。它使用通过fldpi
获得的80位pi常数(正确舍入的64位尾数,实际上是66位,因为精确值的后2位是零)。在布鲁斯·道森注意到最坏情况实际上有多糟糕之后,英特尔的最坏情况错误文档被修改了1.3万亿倍。但这只能通过扩展精度范围缩小来解决,所以在硬件上不便宜。
我不知道AMD是否实现了他们的FSIN和其他微编码指令总是给出比特相同的结果给英特尔,但我不会感到惊讶。我想,有些软件确实依赖于它。
由于SSE只提供了add/sub/mul/div/sqrt的说明,所以没有什么太有趣的东西可以说。它们精确地实现了IEEE操作,因此任何x86实现都不可能给您带来任何不同的结果(除非舍入模式设置不同,或者反正规化-为零和/或齐平-为零不同,并且您有任何反正规化)。
SSErsqrt
(快速近似倒数平方根)没有精确指定,我认为即使在牛顿迭代之后也可能得到不同的结果,但除此之外,SSE/sse2在asm中总是位精确的,假设MXCSR没有设置得很奇怪。所以唯一的问题是让编译器生成相同的代码,或者只是使用相同的二进制文件。
因此,如果静态链接使用SSE/SSE2的libm
并分发这些二进制文件,那么它们在任何地方都将运行相同的代码。除非该库使用运行时CPU检测来选择替代实现...
正如@Yan Zhou所指出的,您需要控制实现的每一点,直到asm,以获得精确的结果。
然而,有些游戏真的依赖于多人游戏,但通常会检测/纠正不同步的客户端。不是每帧都在网络上发送整个游戏状态,而是每个客户端计算接下来发生的事情。如果游戏引擎被仔细地实现为确定性的,它们将保持同步。
在Spring RTS中,客户端对其gamestate进行校验和,以检测Desync。我已经有一段时间没玩过了,但我确实记得至少5年前读过一篇文章,内容是他们试图通过确保所有的x86构建都使用SSE数学来实现同步,甚至是32位构建。
有些游戏不允许在PC和非x86控制台系统之间进行多人游戏的一个可能原因是,引擎在所有PC上给出的结果都是相同的,但在使用不同编译器的不同体系结构的控制台上给出的结果却是不同的。
进一步阅读:GAFFER谈游戏:浮点决定论。真实游戏引擎用来得到确定性结果的一些技术。例如,在未优化的函数调用中包装sin/cos/tan,以迫使编译器将它们保留为单精度。
问题内容: 我需要在Java中执行一些浮点运算,如下面的代码所示: 这是为了模拟Betfair Spinner小部件作为输出给出的值的范围。 Java中的浮点算术似乎引入了一些意外错误。例如,我得到2.180000000000001而不是2.18。浮点数有什么用,您不相信对它们执行的算术结果吗?我该如何解决这个问题? 问题答案: 如果您需要精确的十进制值,则应使用java.math.BigDeci
问题内容: 我正在尝试设置python 库,以便将包含其他字典作为元素的字典保存到文件中。浮点数很多,我想将位数限制为例如。 根据其他帖子,应使用SO 。但是,它不起作用。 例如,下面的代码在Python3.7.1中运行,将打印所有数字: 我该如何解决? 可能无关紧要,但我在macOS上。 编辑 该问题被标记为重复。但是,在原始帖子的已接受答案(到目前为止是唯一的答案)中明确指出: 注意:此解决方
问题内容: 我在运行于多台计算机上的应用程序中使用Java,并且所有计算机都需要获得相同的数学运算结果。使用Java的浮点原语是否安全?还是应该只使用定点数学库? 问题答案: 一般而言,不。但是,您可以使用表达式: 在FP-strict表达式中,所有中间值都必须是浮点值集或double值集的元素,这意味着所有FP-strict表达式的结果必须是IEEE 754算法对使用单格式和双格式表示的操作数预
在JavaScript中,大家都知道著名的计算:。但是为什么JavaScript打印这个值,而不是打印更精确、更精确的呢?
我们已经知道怎样编译C++内核并通过GRUB启动该二进制文件,现在我们能够用C/C++做一些很酷的事了。 输出文本到屏幕控制台 我们继续使用 VGA 默认模式(03h) 来对用户显示一些文本。屏幕可以通过起始地址为0xB8000的Video Memory(显存)直接访问,屏幕分辨率是8025,每个字符在屏幕上被定义为2个字节:一个是字符码,另一个是属性字节(描述了字符的表现形式,包括了字符颜色等属