Jan*_*tke 34 c++ floating-point constexpr c++23 c++26
从 C++11 开始,我们可以在编译时进行浮点数学运算。C++23 和 C++26 添加了constexpr一些函数,但不是全部。
constexpr一般来说,浮点数学很奇怪,因为结果并不完全准确。然而,constexpr代码应该始终提供一致的结果。C++ 如何解决这个问题?
constexpr浮点数学
是如何工作的?constexpr,而其他功能则不然(例如std::nearbyint)Jan*_*tke 35
floatC++ 对和其他浮点类型的行为施加很少的限制。这可能会导致编译器之间以及同一编译器的运行时/编译时评估之间的结果可能不一致。这是 tl;dr 的内容:
| 运行时 | 在常量表达式中 | |
|---|---|---|
| 浮点错误,例如除以零 | UB,但编译器可能 通过 NaN 作为扩展支持静默错误 |
常量表达式中的 UB 会导致编译器错误 |
舍入运算,例如10.0 / 3.0 |
通过 浮点环境控制的舍入模式;结果可能会有所不同 |
舍入是实现定义的, 结果可能与运行时不同 |
语义通过-ffast-math其他编译器优化而改变 |
结果可能会变得不太精确或更加精确;IEEE-754 一致性被破坏 |
实践中没有效果;至多 实现定义的效果 |
| 调用数学函数 | 对错误和舍入的处理与使用and 进行算术的处理相同 +* |
有些constexpr是从 C++23 开始,有些 constexpr是从 C++26 开始,有些错误在编译时是不允许的 |
某些操作可能会失败,例如除以零。C++ 标准说:
如果 / 或 % 的第二个操作数为零,则行为未定义。
在常量表达式中,这一点是受到尊重的,因此不可能通过运算产生 NaN 或FE_DIVBYZERO在编译时引发。
浮点数也不例外。但是,当std::numeric_limits<float>::is_iec559()是 时true,大多数编译器都会将 IEEE-754 合规性作为扩展。例如,允许除以零,并根据操作数产生无穷大或 NaN。
C++ 始终允许编译时结果和运行时结果之间存在差异。例如,您可以评估:
double x = 10.0f / 3.0;
constexpr double y = 10.0 / 3.0;
assert(x == y); // might fail
Run Code Online (Sandbox Code Playgroud)
结果可能并不总是相同,因为浮点环境只能在运行时更改,因此可以更改舍入模式。
C++的做法是让浮点环境的效果实现定义。它没有为您提供可移植的方法来在常量表达式中控制它(从而舍入)。
如果 [
FENVC_ACCESS] 编译指示用于启用对浮点环境的控制,则本文档不会指定对常量表达式中的浮点计算的影响。
首先,编译器可能会渴望优化您的代码,即使它改变了其含义。例如,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);
Run Code Online (Sandbox Code Playgroud)
语义的变化甚至更多,比如-ffast-math允许编译器以不符合 IEEE-754 标准的方式重新排序和优化操作。例如:
float big() { return 1e20f;}
int main() {
std::cout << big() + 3.14f - big();
}
Run Code Online (Sandbox Code Playgroud)
对于 IEEE-754 浮点数,加法和减法不可交换。我们无法将其优化为:(big() - big()) + 3.14f。结果将是0,因为3.14f太小而无法big()在添加时进行任何更改,因为缺乏精度。然而,-ffast-math启用后,结果可能是3.14f。
所有操作的常量表达式都可能存在运行时差异,其中包括对数学函数的调用。编译时可能与运行时std::sqrt(2)不同。std::sqrt(2)然而,这个问题并不是数学函数所独有的。您可以将这些功能分为以下几类:
constexpr自 C++23 起)[P05333r9]有些函数完全独立于浮点环境,或者它们根本不会失败,例如:
std::ceil(四舍五入到下一个更大的数字)std::fmax(两个数字中的最大值)std::signbit(获取浮点数的符号位)此外,还有一些函数std::fma只组合了两个浮点运算。这些问题并不比编译时的问题+更大*。其行为与在 C 中调用这些数学函数相同(请参阅C23 标准,附录 F.8.4FE_INEXACT ),但是,如果引发、errno设置等除外,则它不是 C++ 中的常量表达式(请参阅[library. c]/3)。
constexpr自 C++26 起)[P1383r0]其他函数依赖于浮点环境,例如std::sqrt或std::sin。然而,这种依赖性被称为弱依赖性,因为它没有明确说明,并且它的存在只是因为浮点数学本质上是不精确的。
+在编译时允许and是任意的*,但不允许具有完全相同问题的数学函数。
constexpr还没有,将来可能)[P1383r0]认为添加数学特殊函数太过雄心勃勃,例如:constexpr
std::betastd::riemann_zetaconstexpr还没有,可能永远不会)某些函数(如std::nearbyint标准中明确声明使用当前舍入模式)。这是有问题的,因为您无法使用标准方法在编译时控制浮点环境。像这样的函数std::nearbyint不是constexpr,而且可能永远不会。
总之,标准委员会和编译器开发人员在处理constexpr数学时面临着许多挑战。为了取消对数学函数的一些限制,我们经过了数十年的讨论constexpr,但我们终于实现了。这些限制的范围从任意的(在 的情况下)std::fabs到必要的(在 的情况下)std::nearbyint。
未来我们可能会看到进一步的限制取消,至少对于数学特殊函数而言。
Jan Schultke 已经给出了很好的答案,我只想解决一些潜在的误解:
\nconstexpr\n\n从 C++11 开始,我们可以在编译时进行浮点数学运算。
\n
这不是真的。编译器能够在更长的时间内进行编译时数学计算,旧版本的 C++ 中没有任何东西可以阻止这一点。constexprGCC 和 Clang 会很乐意在不使用 . 的情况下进行编译时浮点除法-std=c++98 -O0。
另外,最好记住,唯一的要求constepxr是 \xe2\x80\x9cit 可以在编译时评估函数或变量的值。\xe2\x80\x9d 它仍然完全没问题让编译器发出在运行时进行数学运算的指令。
| 归档时间: |
|
| 查看次数: |
2532 次 |
| 最近记录: |