从 C++11 开始,我们可以在编译时进行浮点数学运算。C++23 和 C++26 添加了constexpr一些函数,但不是全部。
constexpr一般来说,浮点数学很奇怪,因为结果并不完全准确。然而,constexpr代码应该始终提供一致的结果。C++ 如何解决这个问题?
constexpr浮点数学
是如何工作的?constexpr,而其他功能则不然(例如std::nearbyint)我正在执行这个问题的代码:为什么以下代码的输出不为零?
#include <stdio.h>
int main (void)
{
double A = 373737.0;
double B;
B = A*A*A + 0.37/A - A*A*A - 0.37/A;
printf("The value of B is %f.\n", B);
}
Run Code Online (Sandbox Code Playgroud)
每个主流 x86 编译器的每个优化设置都会给出输出-0.000001。当我使用当前的 clang 15.0.0 时,我-O0也得到了这一点。
但是,使用 14.0.0 版本以上的 clang 和-O1to进行编译-O3会给出输出-1.000001。为什么会发生这种情况?这是一个已知的错误?
Godbolt 为了您的方便:https ://godbolt.org/z/M5j3fGhWf
浮点表达式有时可以在处理硬件上收缩,例如使用融合乘法和加法作为单个硬件操作.
显然,使用这些不仅仅是一个实现细节,而是由编程语言规范控制.具体来说,C89标准不允许这样的收缩,而在C99中,只要定义了某些宏,就允许它们.请参阅此SO答案中的详细信息.
但是C++怎么样?浮点收缩是不允许的?某些标准允许吗?普遍允许吗?
如clang 14.0.0 浮点优化的答案所示,自版本 14 起,Clang 即使对于在编译时执行的常量计算也应用融合乘法加法 (FMA) 指令。
同时,我们可以观察到结果取决于表达式参数的形式恒定性:
#include <stdio.h>
int main() {
const float A = 2.1f;
const float B = 0.1f;
float C = 0.1f;
float V = A * B - A * B;
float W = A * C - A * C;
printf( "%g %g", V, W );
}
Run Code Online (Sandbox Code Playgroud)
在 Clang 中,程序打印0 1.49011e-10,在线演示:https://godbolt.org/z/a3fcYG7ob
从汇编代码可以看出, 和V都是W在编译时求值的。是否有一些规则规定只能W使用 FMA 指令进行评估?
添加-mno-fma用于禁用 FMA 指令的命令行选项不会改变结果中的任何内容。