【问题标题】:Is floating point addition commutative in C++?C ++中的浮点加法是否可交换?
【发布时间】:2024-01-23 09:40:01
【问题描述】:

对于浮点值,是否保证a + b1b + a相同?

我相信这在 IEEE754 中得到了保证,但是 C++ 标准并未指定必须使用 IEEE754。唯一相关的文字似乎来自 [expr.add]#3:

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

数学运算“sum”是可交换的。然而,数学运算“sum”也是关联的,而浮点加法绝对是not关联的。所以,在我看来,我们不能得出结论,数学中“sum”的交换性意味着这句话指定了 C++ 中的交换性。


脚注 1:
“相同”与按位相同,例如 memcmp 而不是 ==,以区分 +0 和 -0。 IEEE754 将+0.0 == -0.0 视为真,但也有符号零的特定规则。 +0 + -0-0 + +0 在 IEEE754 中都产生 +0,对于添加具有相等幅度的相反符号值也是如此。遵循 IEEE 语义的== 将隐藏有符号零的非交换性(如果这是标准)。

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

【问题讨论】:

  • std::strtod("nan")+0.0 == 0.0+std::strtod("nan") 为假。但我怀疑这就是你的意思。
  • 为什么要限制自己使用浮点数?不用多想,我不认为 C++ 要求整数加法是可交换的(例如溢出结果可能是不可交换的)。
  • @StephenCanon:这似乎取决于您对未定义行为的看法。对于不引出 UB 的程序,整数加法是可交换的,并且程序是否引出 UB 不能取决于整数加法的可交换性。
  • 询问a+b == b+a 是否与询问是否可交换不一样,因为== 与memcmp 不同。在类似于 IEEE754 的系统中,最可能的非交换性(如某些软件-FP 仿真)可能是有符号零或不同的NaN 有效载荷。 (例如,0 + -0 可能总是使用左侧操作数的符号,而在 IEEE754 中总是使用 +0。)但它很可能仍然实现 -0 == +0 IEEE 语义。 (此外,如果 a 或 b 为 NaN,则 a+b == b+a 对于 IEEE754 为 false。同样,memcmp 实际上会检查交换性)。

标签: c++ floating-point language-lawyer commutativity


【解决方案1】:

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

如果a + b == a + b 不保证,a + b == b + a 不能保证。如果a + b 不必每次都返回相同的值,并且值不同,则其中之一必然将不等于b + a 的特定评估。

【讨论】:

  • @MattMcNabb 这取决于编译器想要遵循的规则。 C 和 C++ 标准非常宽松,但由于它们非常宽松,因此实现可能会实施更严格的规则(可能符合其他标准)并且仍然符合 C++ 标准。这种更严格的规则确实不允许许多浮点优化。 C++ 标准在 [expr.prim.general] p11 中声明:“浮动操作数的值和浮动表达式的结果可以用比类型要求更高的精度和范围来表示;类型不会因此而改变。”
  • @MattMcNabb 这实际上意味着,例如,如果ab 的类型为double,并且a + blong double 中完全可以表示double 需要四舍五入,而 CPU 只有一个精度为 long double 的浮点类型,那么你可能会得到 (long double) a + (long double) b 的结果。 C 和 C++ 标准都有意不要求编译器在每个浮点运算之后发出轮指令。
  • 有 GCC 的 bug 323,关于一个假定 a + b == a + b 的程序,其中 GCC 开发人员已将其关闭为不是错误。
  • a+b == a+b 无法保证更糟糕的是,IEEE-754 要求a 的某些值甚至不能满足a==a
  • @KamiKaze:我指的是 NaN。虽然可以通过使用初始传递将数据划分为 NaN 和其他所有内容,然后对不包含任何 NaN 值的部分进行排序,来编写行为合理的 NaN 安全浮点排序,但这是额外的一层通过定义一个始终如一地工作的关系运算符可以避免复杂性。
【解决方案2】:

不,C++ 语言一般不会对硬件提出这样的要求。只定义了运算符的关联性。

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

但请注意,如果您要控制获得的操作,则具有非交换加法的机器需要专门定义表达式如何映射到指令。左边是第一个机器操作数还是第二个机器操作数?

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

【讨论】:

  • 运算符结合性不同于乘法结合性的数学性质。前者表示double x = a * b * c; 将以左关联方式评估为(a * b) * c。在数学中,关联性意味着(a * b) * c == a * (b * c)。浮点加法/乘法被评估为左关联,但绝对不是数学关联的(即右关联评估可能会产生不同的结果)。
  • @TemplateRex 嗯,我不明白这与问题或我的答案有何关系。这个讨论完全是关于用于编程浮点计算的符号,而不是被近似的数学运算。
  • 我只是指出关联性有两个含义,从上下文中并不清楚您指的是哪一个。
【解决方案3】:

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

在大多数情况下,C 和 C++ 标准假定这些基本操作将被实现,但底层硬件工作。对于像 IEEE 754 这样常见的东西,它们会让您检测它是否存在,但仍然不需要它。

【讨论】:

  • 我已经接受了 hvd 的回答(至少现在是这样)——假设它是正确的,这表明即使在 IEEE754 实现上,如果结果不能完全表示,那么我们甚至没有身份,更不用说交换性了。
最近更新 更多