我想知道我是否可以假设在任何现代PC上以及大多数通用编程语言中,对相同的64位浮点数进行相同的操作都能获得完全相同的结果? (C++,Java,C#等)。我们可以假设,我们对数字进行运算,结果也是一个数字(没有NaN,INF等)。
我知道有两种使用浮点数的非常相似的计算标准(IEEE 854-1987和IEEE 754-2008)。但是我不知道它在实践中如何。
最佳答案
实现64位浮点的现代处理器通常会实现接近IEEE 754-1985标准的功能,该标准最近被754-2008标准所取代。
754标准指定了应从某些基本运算中获得的结果,特别是加,减,乘,除,平方根和求反。在大多数情况下,将精确指定数值结果:结果必须是在四舍五入模式指定的方向上最接近精确数学结果的可表示数字(最接近,朝着无穷大,朝着零或朝着负无穷大)。在“至最近”模式下,该标准还指定如何断开关系。
因此,不涉及异常条件(例如溢出)的操作将在符合标准的不同处理器上获得相同的结果。
但是,有几个问题会干扰在不同处理器上获得相同的结果。其中之一是,编译器通常可以自由地以各种方式来实现浮点运算序列。例如,如果您在C中编写“a = bc + d”,其中所有变量都声明为double,则编译器可以自由使用double-precision算术或具有更大范围或精度的东西来计算“bc”。例如,如果处理器具有能够保存扩展精度浮点数的寄存器,并且使用扩展精度进行算术不比使用 double 算术花费更多的CPU时间,则编译器很可能会使用扩展来生成代码-精确。在这样的处理器上,您可能无法获得与在另一个处理器上相同的结果。即使编译器定期执行此操作,在某些情况下也可能不会这样做,因为在复杂的序列中寄存器已满,因此它将中间结果临时存储在内存中。当这样做时,它可能只写64位 double 数而不是扩展精度数。因此,包含浮点算术的例程可能会给出不同的结果,这仅仅是因为它是使用不同的代码编译的,可能是内联在一个地方,而编译器需要用于其他内容的寄存器。
一些处理器具有一条指令来计算一个乘法和一个加法指令,因此与不首先进行bc然后加d的处理器相比,可以在不进行中间舍入的情况下计算“bc + d”,并获得更准确的结果。
您的编译器可能具有控制这种行为的开关。
在某些地方,754-1985标准并不需要唯一的结果。例如,当确定是否发生下溢(结果太小而无法准确表示)时,该标准允许实现在将有效位数(分数位)四舍五入到目标精度之前或之后进行确定。因此,某些实现将告诉您,其他实现则不会发生下溢。
处理器的一个共同特征是具有“几乎IEEE 754”模式,该模式通过替换零而不是返回标准要求的非常小的数字,消除了处理下溢的困难。自然地,在这种模式下执行时与在更兼容模式下执行时,您将获得不同的数字。出于性能原因,不兼容模式可能是编译器和/或操作系统设置的默认模式。
请注意,IEEE 754实现通常不是仅由硬件提供,而是由硬件和软件的组合提供。处理器可以完成大部分工作,但要依靠软件来处理某些异常,设置某些模式等等。
当您从基本的算术运算转向正弦和余弦之类的东西时,您将非常依赖于所使用的库。先验函数通常通过精心设计的近似值来计算。这些实现是由各个工程师独立开发的,并且彼此之间会得到不同的结果。在一个系统上,对于较小的自变量(小于pi左右),sin函数可能会在ULP(最小精度单位)内给出准确的结果,而对于较大的自变量,则误差较大。在另一个系统上,对于所有参数,sin函数可能会在几个ULP内给出准确的结果。尚无当前的数学库可为所有输入生成正确的舍入结果。有一个项目crlibm(正确舍入的Libm)朝着这个目标做了很好的工作,他们为数学库的重要部分开发了实现,这些实现正确舍入并具有良好的性能,但并不是所有的数学库然而。
总而言之,如果您拥有一组可管理的计算,了解编译器的实现并且非常小心,则可以将相同的结果依赖于不同的处理器。否则,您将不能依靠获得完全相同的结果。