constexpr
,但并非全部都有。
constexpr
的浮点数计算在一般情况下是奇怪的,因为结果并不完全准确。然而,constexpr
代码应该始终提供一致的结果。C++如何处理这个问题?问题:
-
constexpr
的浮点数计算是如何工作的?
- 对于所有编译器来说,结果是否相同?
- 对于同一编译器的编译时和运行时,结果是否相同?- 为什么有些函数是
constexpr
,而其他函数(例如std::nearbyint
)不是?constexpr
,但并非全部都有。
constexpr
的浮点数计算在一般情况下是奇怪的,因为结果并不完全准确。然而,constexpr
代码应该始终提供一致的结果。C++如何处理这个问题?constexpr
的浮点数计算是如何工作的?
- 对于所有编译器来说,结果是否相同?
- 对于同一编译器的编译时和运行时,结果是否相同?constexpr
,而其他函数(例如std::nearbyint
)不是?在运行时 | 在常量表达式中 | |
---|---|---|
浮点数错误,如除以零 | 未定义行为(UB),但编译器可能通过NaN支持 静默错误作为扩展 |
在常量表达式中是UB, 会导致编译器错误 |
舍入操作,如10.0 / 3.0 |
舍入模式由浮点环境控制;结果可能不同 | 舍入是实现定义的, 结果可能与运行时不同 |
通过-ffast-math 和其他编译器优化的语义变化 |
结果可能变得不太精确或更精确;破坏了IEEE-754规范 | 实际上没有影响;最多是实现定义的效果 |
调用数学函数 | 错误处理和舍入与+ 和* 的算术相同 |
自C++23起有一些constexpr ,自C++26起有一些 constexpr ,在编译时禁止某些错误 |
某些操作可能会失败,例如除以零。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
// 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);
-ffast-math
这样的标志而发生更大的变化,它允许编译器以一种不符合IEEE-754标准的方式重新排序和优化操作。例如:float big() { return 1e20f;}
int main() {
std::cout << big() + 3.14f - big();
}
对于IEEE-754浮点数,加法和减法不满足交换律。我们不能将其优化为:(big() - big()) + 3.14f
。结果将为0
,因为3.14f
的精度太小,不足以改变big()
的值。然而,启用-ffast-math
选项后,结果可以是3.14f
。
所有操作(包括对数学函数的调用)在运行时与常量表达式可能存在运行时差异。编译时的std::sqrt(2)
可能与运行时的std::sqrt(2)
不同。然而,这个问题并不局限于数学函数。您可以将这些函数分为以下几类:
constexpr
)[P05333r9][P1383r0]认为在数学特殊函数中添加constexpr
太过雄心勃勃,例如:
std::beta
std::riemann_zeta
constexpr
,可能永远不会)一些函数如std::nearbyint
明确规定使用标准中的当前舍入模式。
这是有问题的,因为您无法使用标准方法在编译时控制浮点环境。
像std::nearbyint
这样的函数不是constexpr
,可能永远也不会成为。
constexpr
数学时,标准委员会和编译器开发人员面临许多挑战。几十年的讨论才解除了某些对constexpr
数学函数的限制,但我们终于取得了成果。这些限制的范围从std::fabs
的任意性到std::nearbyint
的必要性都有所不同。const
或constexpr
有时会影响优化选择(以及编译时评估策略和结果),例如常量传播的顺序与FP收缩到FMA,就像Clang融合乘加取决于表达式参数的常数性和C++舍入行为为何(对于编译时常量)在将数学移至内联函数时发生变化?中所述。 - Peter Cordesconst
或constexpr
有时会影响优化选择(以及编译时评估策略和结果),例如常量传播的顺序与FP缩减到FMA,就像Clang融合乘加取决于表达式参数的常数性和C++舍入行为为何(对于编译时常量)在将数学移至内联函数时发生变化?中所述。 - Peter Cordesstd::numeric_limits<float>::is_iec559()
在不符合IEC559标准的实现中是否应该返回true
,即通过返回NaN来处理1.0f/0.0f
? - supercatstd::numeric_limits<float>::is_iec559()
在不符合IEC559标准的实现中是否应该返回true
,即对1.0f/0.0f
的处理结果为NaN? - supercatJan Schultke已经给出了一个很好的答案,我只想解释一些可能的误解:
constexpr
不同自从C++11以来,我们就能够在编译时进行浮点数运算。
这是不正确的。编译器早在很久之前就能够进行编译时数学运算,而且旧版本的C++中没有任何阻止这种能力的东西。即使在-std=c++98 -O0
的情况下,GCC和Clang也可以愉快地进行编译时浮点数除法。
另外,需要记住的是constepxr
的唯一要求是“在编译时能够评估函数或变量的值是可能的”。编译器仍然可以发出指令在运行时进行数学运算,这完全没问题。
consteval
、constinit
和类似的领域。 - Deduplicatorconsteval
、constinit
和类似的领域。 - Deduplicatorconsteval
、constinit
和类似的领域。 - undefined
constexpr
处理。 - BoPconstexpr
化。 - BoPconstexpr
处理。 - undefinedconst
(或constexpr
)如何改变结果:https://stackoverflow.com/q/75859098/7325599 - Fedor