-4

在 C 和 C++ 中,浮点计算在默认情况下是不确定的,因为用户甚至没有选择真正的数据类型,对于 FP 子表达式的任何中间计算,编译器可以选择以更高的精度表示一个值(即作为另一个真正的数据类型)

[众所周知,一些编译器 (GCC) 会对任何自动变量执行此操作,而不仅仅是子表达式的(匿名)中间结果。]

编译器可以在某些函数中进行一些计算;它在某些情况下可以做到这一点,而对于完全相同的子表达式则不能。

它甚至可以内联一个函数,并在源代码中每次调用函数时使用不同的精度。这意味着任何可内联函数都可以有其语义调用依赖;只有单独编译,ABI 调用函数(根据 ABI 描述的约定调用并且本质上充当黑盒的函数)绝对保证只有一个浮点行为,在单独编译期间修复(这意味着没有发生全局优化)。

[请注意,这类似于字符串文字的定义方式:源代码中相同字符串文字的任何两次计算都可以引用相同或不同的字符数组。]

这意味着即使对于纯粹的应用函数,只有在f(x) == f(x)使用浮点运算(和字符串文字)(或者字符串文字的地址仅用于访问其元素)时才能保证基本相等。

因此,浮点运算具有非确定性语义,编译器为每个 FP 运算做出任意选择(这似乎比让编译器选择首先计算哪个子表达式 A 或 B 的小问题更不正常A+B)。

似乎使用中间浮点值进行任何计算的函数不能用于任何需要满足 axioms的函子的 STL 容器或算法,例如

  • 分类容器:set, map, multiset,multimap
  • 散列容器
  • 排序算法:sort,stable_sort
  • 在排序范围上运行的算法:lower_bound, set_union, set_intersection...

由于所有二元谓词和散列函数在公理被构想之前必须是确定性的,因此它们必须是纯粹的应用性数学函数,具有所有可能输入的定义值,而 C++ 非确定性浮点中间值绝不是这种情况?

换句话说,浮点运算是否默认仅基于标准几乎不可用,并且只能在具有某些(模糊)确定性隐含保证的现实世界实现中使用?

4

1 回答 1

1

在计算浮点算术表达式时,允许编译器使用更高精度的运算和临时值。因此,如果您这样做a1 = b+c; a2 = b+c;,那么是的,有可能a1彼此a2不相等(由于双舍入)。

不过,这与您提到的算法和容器无关;那些不对他们的价值观做任何算术。这些所依赖的“公理”最多是排序关系的公理。

您可以说的最糟糕的情况是,如果您以前存储b+c在 a 中set,那么操作mySet.find(b+c)可能会失败。所以,是的,不要那样做。但这是你通常不会做的事情,浮点算术产生的舍入误差已经使得很难期望派生量完全相等。

唯一开始担心扩展精度的时候是在计算一系列操作的理论 FP 误差范围时。当你这样做时,你就会知道要寻找什么。到那时,这个问题都不是问题。

于 2019-05-29T14:27:53.273 回答