在c++中是浮点加法可交换的

Is floating point addition commutative in C++?

本文关键字:可交换 浮点加 c++      更新时间:2023-10-16

对于浮点数,是否保证a + b1 b + a相同?

我相信IEEE754保证了这一点,但是c++标准并没有指定必须使用IEEE754。唯一相关的文本似乎来自[expr.add]#3:

二元+运算符的结果是操作数的和。

数学运算"sum"是可交换的。然而,数学运算"answers";也是关联的,而浮点加法肯定是不是关联的。所以,在我看来我们不能得出和的交换性在数学中的意思是这个引号指定c++中的交换性。


脚注1:
"Same"就像位相同,像memcmp而不是==,以区分+0和-0。IEEE754将+0.0 == -0.0视为真值,但对于带符号的零也有特定规则。+0 + -0-0 + +0在IEEE754中都产生+0,对于等量的对号值的加法也是如此。遵循IEEE语义的==将隐藏符号零的非交换性,如果这是标准的话。

同样,如果输入是NaN, a+b == b+a在IEEE754数学中为假。memcmp将说明两个NaN是否具有相同的位模式(包括有效载荷),尽管我们可以将NaN传播规则与有效数学运算的交换性分开考虑。

甚至不需要a + b == a + b。其中一个子表达式可能以更高的精度保存加法运算的结果,例如,当使用多个加法运算时,需要将其中一个子表达式临时存储在内存中,而另一个子表达式可以保存在寄存器中(具有更高的精度)。

如果不保证a + b == a + b,则不能保证a + b == b + a 。如果a + b不必每次都返回相同的值,并且值不同,那么其中一个必然不会等于b + a的一个特定求值

不,c++语言一般不会对硬件做出这样的要求。只定义了操作符的结合性。

各种疯狂的事情发生在浮点运算中。也许,在某些机器上,对一个不正常的数字加零会产生零。可以想象,在向内存中的异常寄存器中添加零值寄存器的情况下,机器可以避免更新内存。一个愚蠢的编译器可能总是把LHS放在内存中,而把RHS放在寄存器中。

请注意,如果你想要控制你得到的操作,那么使用非交换加法的机器需要特别定义表达式如何映射到指令。左边的是第一个机器操作数还是第二个?

这样的ABI规范,同时提到表达式和指令的构造,将是相当病态的。

c++标准非常明确保证IEEE 754。该库确实支持IEC 559(基本上只是IEC版本的IEEE 754标准),因此您可以检查底层实现是否使用IEEE 754/IEC 559(当它使用时,您可以依赖于它保证的内容,当然)。

在很大程度上,C和c++标准假定无论底层硬件如何工作,这些基本操作都将被实现。对于像IEEE 754这样常见的东西,他们会让你检测它是否存在,但仍然不需要它。

对于使用IEEE FP数学的c++实现,除NaN有效负载外,任何给定精度的加法都是可交换的。

Marc Glisse评论:

对于内置类型,gcc将交换+的操作数而不需要任何特别的注意事项。


  • 有限输入非零结果是简单情况,明显可交换。加法是"基本"的加法之一。FP数学运算,因此IEEE754要求结果"正确舍入"。(舍入误差<= 0.5 ulp),因此只有一个可能的数值结果,并且只有一个位模式表示它。

    非ieee FP数学可能允许更大的舍入误差(例如允许尾数的LSB中的off-by- 1,因此舍入误差<= 1 ulp)。它可以与最终结果不可交换,这取决于哪个操作数是哪个。我想大多数人会认为这是一个糟糕的设计,但c++可能不会禁止它。

  • 如果结果为零(具有相同大小但符号相反的有限输入),则在IEEE数学中始终为+0.0。(或-0.0在roundTowardNegative舍入模式)。该规则适用于+0 + (-0.0)+0.0的情况。参见 IEEE浮点标准中的(+0)+(-0)是什么?

    不同幅度的输入不能下流到零,除非FPU在flush-to-zero模式下工作时具有亚正常输入(亚正常输出向零四舍五入)。在这种情况下,你可以得到-0.0作为结果,如果确切的结果是负的。但它仍然是可交换的

  • 加法可以从-0 + -0产生-0.0,这是微不足道的交换,因为两个输入是相同的值。

  • -Inf +任何有限的都是-Inf。+∞+任何有限的都是+∞。+Inf + -Inf = NaN。这两个都不依赖于顺序。

  • NaN +任何东西或任何东西+ NaN是NaN。"payload"(尾数)取决于FPU。IIRC,保留之前NaN的有效载荷。

  • NaN + NaN生成NaN。如果我没记错的话,没有规定保留哪个NaN有效负载,或者是否可以发明一个新的有效负载。几乎没有人对NaN有效载荷做任何事情来跟踪它们的来源,所以这不是什么大问题。

c++中+的两个输入都将提升为匹配类型。如果两种输入类型不匹配,则具体到两种输入类型的宽度。所以没有不对称的类型


对于a+b == b+a本身,由于IEEE ==语义(不是因为+语义),对于nan可能为假,与a+b == a+b相同。

使用严格的FP数学(C语句之间没有额外的精度,例如gcc -ffloat-store,如果在x86上使用传统的x87数学),我认为相等性相当于!isunordered(a,b),它测试它们中的任何一个是否为NaN。

否则,可能编译器可以使用其中一个较早的代码而不使用另一个,并使用ab的更高精度值对其中一个进行计算。(严格的ISO c++要求,即使FLT_EVAL_METHOD==2(如x87),高精度临时值也只能存在于表达式中,而不是跨语句,但默认情况下gcc不尊重这一点。只能使用g++ -std=c++03或其他代替gnu++20的东西,或者专门为x87使用-ffloat-store)

在使用FLT_EVAL_METHOD == 0的c++实现上(表达式内的临时变量没有额外的精度),这种优化差异的来源将不会是一个因素。