10

我想知道我是否可以假设对相同 64 位浮点数的相同操作在任何现代 PC 和最常见的编程语言中给出完全相同的结果?(C++、Java、C# 等)。我们可以假设,我们正在对数字进行操作,结果也是一个数字(没有 NaN、INF 等)。

我知道有两个非常相似的使用浮点数的计算标准(IEEE 854-1987 和 IEEE 754-2008)。但是我不知道它在实践中如何。

4

7 回答 7

8

实现 64 位浮点的现代处理器通常实现接近 IEEE 754-1985 标准的东西,最近被 754-2008 标准取代。

754 标准规定了你应该从某些基本运算中得到什么结果,特别是加法、减法、乘法、除法、平方根和否定。在大多数情况下,数字结果是精确指定的:结果必须是在舍入模式指定的方向上最接近精确数学结果的可表示数字(最接近、接近无穷大、接近零或接近负无穷大)。在“到最近”模式下,该标准还规定了如何打破联系。

正因为如此,不涉及溢出等异常条件的操作在符合标准的不同处理器上会得到相同的结果。

但是,有几个问题会干扰在不同处理器上获得相同的结果。其中之一是编译器通常可以自由地以各种方式实现浮点运算序列。例如,如果您在 C 中编写“a = b c + d”,其中所有变量都声明为 double,则编译器可以自由计算“bc" 在双精度算术或具有更大范围或精度的运算中。例如,如果处理器具有能够保存扩展精度浮点数的寄存器,并且使用扩展精度进行算术不会花费更多的 CPU 时间使用双精度进行算术运算,编译器可能会使用扩展精度生成代码。在这样的处理器上,您可能不会得到与在另一个处理器上相同的结果。即使编译器定期执行此操作,它也可能不会在某些情况下,由于在复杂序列期间寄存器已满,所以它会将中间结果临时存储在内存中。这样做时,它可能只写入 64 位双精度数而不是扩展精度数。所以一个包含浮点运算的例程可能会给出不同的结果,因为它是用不同的代码编译的,可能内联在一个地方,而编译器需要寄存器来做其他事情。

一些处理器具有在一条指令中计算乘法和加法的指令,因此“b c + d”可以在没有中间舍入的情况下计算,并且比在首先计算 b c 然后加 d的处理器上获得更准确的结果。

你的编译器可能有开关来控制这样的行为。

有些地方 754-1985 标准不需要唯一的结果。例如,当确定是否发生下溢时(结果太小而无法准确表示),该标准允许实现在将有效数字(小数位)四舍五入到目标精度之前或之后进行确定。所以一些实现会告诉你发生了下溢,而其他实现不会。

处理器的一个共同特点是具有“几乎 IEEE 754”模式,该模式通过替换零而不是返回标准要求的非常小的数字来消除处理下溢的困难。自然,在这种模式下执行时,您会得到与在更兼容模式下执行时不同的数字。出于性能原因,不兼容模式可能是您的编译器和/或操作系统设置的默认模式。

请注意,IEEE 754 实现通常不仅仅由硬件提供,而是由硬件和软件的组合提供。处理器可能会完成大部分工作,但依赖软件来处理某些异常、设置某些模式等。

当您从基本算术运算转向正弦和余弦等运算时,您非常依赖于您使用的库。超越函数通常使用精心设计的近似值来计算。这些实现是由不同的工程师独立开发的,并且彼此得到不同的结果。在一个系统上,对于小参数(小于 pi 左右),sin 函数可以在 ULP(最低精度单位)内给出准确的结果,但对于大参数,误差较大。在另一个系统上,sin 函数可能会在几个 ULP 内为所有参数提供准确的结果。没有已知的当前数学库可以为所有输入产生正确的舍入结果。有一个项目,crlibm(Correctly Rounded Libm),朝着这个目标做了一些很好的工作,

总之,如果您有一组可管理的计算,了解您的编译器实现并且非常小心,那么您可以依赖不同处理器上的相同结果。否则,获得完全相同的结果不是您可以依赖的。

于 2010-01-27T21:26:25.570 回答
7

如果你的意思是得到完全相同的结果,那么答案是否定的。

在某些情况下,您甚至可能会在同一台机器上获得调试(非优化)版本与发布版本(优化)的不同结果,因此甚至不要假设结果在不同机器上可能总是相同的。

(这可能发生在例如具有 Intel 处理器的计算机上,如果优化器将中间结果的变量保存在寄存器中,该变量存储在未优化构建中的内存中。由于 Intel FPU 寄存器是 80 位,而双变量是 64位,中间结果将以更高的精度存储在优化的构建中,导致以后结果中的值不同。)。

然而,在实践中,您可能经常得到相同的结果,但您不应该依赖它。

于 2010-01-27T20:28:26.283 回答
2

现代 FPU 都以单双格式实现 IEEE754 浮点数,还有一些以扩展格式实现。支持一组特定的操作(几乎所有的操作math.h),其中有一些特殊的指令。

于 2010-01-27T20:12:29.843 回答
1

对于 x86 上的 C#,使用 80 位 FP 寄存器。

C# 标准规定处理器必须以与类型本身相同或更高的精度运行(即,在“双精度”的情况下为 64 位)。允许促销,但存储除外。这意味着局部变量和参数的精度可能高于 64 位。

换句话说,将成员变量分配给局部变量可能(实际上在某些情况下会)足以产生不等式。

另请参阅:调试/发布模式中的浮点/双精度

于 2010-01-27T20:36:24.777 回答
1

同一个 C# 程序在同一台 PC 上可以带出不同的数值结果,一次在 debug 模式下编译没有优化,第二次在 release 模式下编译并启用优化。这是我的个人经历。当我们第一次为我们的一个程序设置一个自动回归测试套件时,我们没有考虑到这一点,并且完全惊讶于我们的许多测试没有任何明显的原因而失败。

于 2010-01-27T20:28:06.590 回答
1

假设您正在谈论应用多个操作,我认为您不会得到确切的数字。CPU 架构、编译器使用、优化设置将改变您的计算结果。

如果您指的是操作的确切顺序(在装配级别),我认为您仍然会得到变化。例如,英特尔芯片在内部使用扩展精度(80 位),其他 CPU 可能不是这种情况。(我不认为扩展精度是强制性的)

于 2010-01-27T20:13:13.633 回答
0

对于 64 位数据类型,我只知道使用 IEEE 754 的“双精度”/“二进制 64”(1985 年和 2008 年在常见情况下差别不大)。

注意:IEEE 854-1987 中定义的基数类型无论如何都被 IEEE 754-2008 取代。

于 2010-01-27T20:14:12.023 回答