constexpr 浮点数学的含义是什么?

What are the implications of constexpr floating-point math?

提问人:Jan Schultke 提问时间:7/4/2023 更新时间:7/5/2023 访问量:2288

问:

从 C++11 开始,我们能够在编译时进行浮点数学运算。C++23 和 C++26 添加到一些函数中,但不是全部。constexpr

constexpr浮点数学通常很奇怪,因为结果并不完全准确。但是,代码应该始终提供一致的结果。C++ 如何处理这个问题?constexpr

问题

  • 浮点数学是如何工作的?constexpr
    • 所有编译器的结果都一样吗?
    • 同一编译器的编译时和运行时之间的结果是否相同?
  • 为什么有些函数是 ,而另一些则不是 (比如constexprstd::nearbyint)
C++ 浮点 constexpr 23 C++26

评论

1赞 n. m. could be an AI 7/4/2023
答案:不,不,给它时间。
7赞 BoP 7/4/2023
对于必须采用不同浮点架构的交叉编译器来说,最大的负担一定是。constexpr
1赞 Fedor 7/5/2023
添加(或)如何更改结果的一些很好的例子: stackoverflow.com/q/75859098/7325599constconstexpr

答:

35赞 Jan Schultke 7/4/2023 #1

C++ 对浮点类型的行为施加了很少的限制。这可能会导致编译器之间以及同一编译器的运行时/编译时评估之间的结果可能不一致。这是 tl;博士:float

在运行时 在常量表达式中
浮点错误,例如除以零 UB,但编译器可能通过 NaN 作为扩展来支持
静默错误
常量表达式
中的 UB 会导致编译器错误
四舍五入操作,如 10.0 / 3.0 通过
浮点环境控制舍入模式;结果可能会有所不同
舍入是实现定义的,
结果可能与运行时不同
通过 -ffast-math
和其他编译器优化进行语义更改
结果可能会因此变得不那么精确或更精确
;IEEE-754 一致性被打破
在实践中没有效果;最多
实现定义的效果
对数学函数的调用 错误和舍入的处理
与使用 和 的算术相同
+*
有些从 C++23 开始,有些从 C++26 开始,

有些错误在编译时是不允许的
constexprconstexpr

浮点错误

某些操作可能会失败,例如除以零。C++标准说:

如果 / 或 % 的第二个操作数为零,则行为未定义。

- [expr.mul]/4

在常量表达式中,这是遵循的,因此不可能通过操作生成 NaN 或在编译时引发FE_DIVBYZERO

浮点数也不例外。但是,当 是 时,大多数编译器将具有 IEEE-754 合规性作为扩展。例如,允许除以零,并根据操作数产生无穷大或 NaN。std::numeric_limits<float>::is_iec559()true

舍入模式

C++ 始终允许编译时结果和运行时结果之间存在差异。 例如,您可以评估:

double x = 10.0f / 3.0;
constexpr double y = 10.0 / 3.0;
assert(x == y); // might fail

结果可能并不总是相同的,因为浮点环境只能在运行时更改,因此可以更改舍入模式。

C++ 的方法是定义浮点环境实现的效果。它没有提供在常量表达式中控制它(从而舍入)的可移植方法。

如果使用 [] 编译指示来启用对浮点环境的控制,则本文档不会在常量表达式中指定对浮点计算的影响。FENVC_ACCESS

- [cfenv.syn]/注 1

编译器优化

首先,编译器可能渴望优化您的代码,即使它改变了其含义。例如,GCC 将优化此调用:

// No call to sqrt thanks to constant folding.
// This ignores the fact that this is a runtime evaluation, and would normally be impacted
// by the floating point environment at runtime.
const float x = std::sqrt(2);

语义的变化甚至更大,标志允许编译器以不符合 IEEE-754 的方式重新排序和优化操作。例如:-ffast-math

float big() { return 1e20f;}

int main() {
    std::cout << big() + 3.14f - big();
}

对于 IEEE-754 浮点数,加法和减法不是可交换的。我们无法将其优化为:.结果将是 ,因为由于缺乏精度,在添加时太小而无法进行任何更改。但是,启用后,结果可以是 。(big() - big()) + 3.14f03.14fbig()-ffast-math3.14f

数学函数

所有操作的常量表达式都可能存在运行时差异,这包括对数学函数的调用。 在编译时可能与在运行时不同。但是,此问题并非数学函数所独有。您可以将这些函数分为以下几类:std::sqrt(2)std::sqrt(2)

无 FPENV 依赖性/非常弱的依赖性(自 C++23 以来)[P05333r9]constexpr

有些函数完全独立于浮点环境,或者它们根本不会失败,例如:

  • std::ceil(四舍五入到下一个较大的数字)
  • std::fmax(最多两个数字)
  • std::signbit(获取浮点数的符号位)

此外,还有一些函数只是结合了两个浮点运算。这些并不比编译时更成问题。该行为与在 C 中调用这些数学函数相同(参见 C23 标准,附录 F.8.4),但是,如果引发、设置了除之外的异常等,则它不是 C++ 中的常量表达式(参见 [library.c]/3)。std::fma+*FE_INEXACTerrno

弱 FPENV 依赖性(自 C++26 以来)[P1383r0]constexpr

其他函数依赖于浮点环境,例如 或 。然而,这种依赖性被称为依赖性,因为它没有明确说明,它的存在只是因为浮点数学本质上是不精确的。std::sqrtstd::sin

在编译时允许和允许是任意的,但不允许具有完全相同问题的数学函数。+*

数学特殊函数(目前还没有,将来可能)constexpr

[P1383r0] 认为它过于雄心勃勃,无法添加到数学特殊函数中,例如:constexpr

  • std::beta
  • std::riemann_zeta
  • 还有很多......

强烈的 FPENV 依赖性(还没有,可能永远不会)constexpr

一些函数,如在标准中明确声明使用当前的舍入模式。 这是有问题的,因为您不能在编译时使用标准方法控制浮点环境。 像这样的函数不是,而且可能永远不会是。std::nearbyintstd::nearbyintconstexpr

结论

总之,标准委员会和编译器开发人员在处理数学时面临着许多挑战。经过几十年的讨论,才取消了对数学函数的一些限制,但我们终于来到了这里。这些限制的范围从任意的 ,到 的 必要 。constexprconstexprstd::fabsstd::nearbyint

未来,我们可能会看到进一步的限制被取消,至少对于数学特殊函数

评论

4赞 Peter Cordes 7/5/2023
const或者有时会影响优化选择(以及编译时评估策略和结果),例如常数传播与 FP 收缩到 FMA 的顺序,如 Clang 融合乘加取决于表达式参数的恒定性和为什么如果数学移动到内联函数,C++ 舍入行为(对于编译时常量)会改变?constexpr
2赞 supercat 7/6/2023
是否应该在不以符合 IEC559 的方式处理的实现上让步,即通过让步 NaN?std::numeric_limits<float>::is_iec559()true1.0f/0.0f
0赞 Jan Schultke 7/6/2023
@supercat它不是故意的;如果实施符合 IEC 60559,则该值为 true。请参见 eel.is/c++draft/numeric.limits.members#53。但是,即使此值为 true,也可能是硬件无法完全符合要求地实现所有操作,或者数学库无法根据需要提供最准确的结果。例如,请参阅 gcc.gnu.org/onlinedocs/gcc/Floating-point-implementation.html;GCC 文档承认系统库可能不符合,这不符合编译器的要求,即使这是一个编译时常量。is_iec559
9赞 G. Sliepen 7/5/2023 #2

Jan Schultke已经给出了一个很好的答案,我只想解决一些潜在的误解:

编译时间与constexpr

从 C++11 开始,我们能够在编译时进行浮点数学运算。

那不是真的。编译器已经能够进行编译时数学运算的时间要长得多,而旧版本的 C++ 中没有任何内容可以阻止这一点。GCC 和 Clang 很乐意在没有 的情况下进行编译时浮点除法,即使使用 .constexpr-std=c++98 -O0

此外,最好记住,constepxr 的唯一要求是“可以在编译时评估函数或变量的值”。编译器在运行时发出指令来执行数学运算仍然完全没问题。

评论

6赞 Deduplicator 7/5/2023
这个问题可能应该扩展到 和类似的领域。constevalconstinit