【发布时间】:2020-03-03 02:34:36
【问题描述】:
问题
在使用自动化 CI 测试时,我发现了一些代码,如果 gcc 的优化设置为 -O2,则会中断。如果双精度值在任一方向越过阈值,代码应增加计数器。
解决方法
转到 -O1 或使用 -ffloat-store 选项可以解决此问题。
示例
这是一个显示相同问题的小示例。
只要*pNextState * 1e-6 的序列超过阈值 0.03,update() 函数就应该返回 true。
我使用引用调用,因为这些值是完整代码中大型结构的一部分。
使用< 和>= 背后的想法是,如果一个序列恰好命中该值,则函数这次应该返回1,下一个循环返回0。
test.h:
extern int update(double * pState, double * pNextState);
test.c:
#include "test.h"
int update(double * pState, double * pNextState_scaled) {
static double threshold = 0.03;
double oldState = *pState;
*pState = *pNextState_scaled * 1e-6;
return oldState < threshold && *pState >= threshold;
}
main.c:
#include <stdio.h>
#include <stdlib.h>
#include "test.h"
int main(void) {
double state = 0.01;
double nextState1 = 20000.0;
double nextState2 = 30000.0;
double nextState3 = 40000.0;
printf("%d\n", update(&state, &nextState1));
printf("%d\n", update(&state, &nextState2));
printf("%d\n", update(&state, &nextState3));
return EXIT_SUCCESS;
}
使用至少 -O2 的 gcc 输出是:
0
0
0
将 gcc 与 -O1、-O0 或 -ffloat-store 一起使用会产生所需的输出
0
1
0
据我了解,如果编译器优化堆栈上的局部变量 oldstate 并与精度更高(80 位)的浮点寄存器中的中间结果进行比较,并且值 *pState 是比阈值小一点。
如果用于比较的值以 64 位精度存储,则逻辑不会错过阈值。由于乘以 1e-6,结果可能存储在浮点寄存器中。
你会认为这是一个 gcc 错误吗? clang 没有显示问题。
我在 Intel Core i5、Windows 和 msys2 上使用 gcc 版本 9.2.0。
更新
我很清楚浮点比较并不精确,我认为以下结果是有效的:
0
0
1
想法是,如果(*pState >= threshold) == false 在一个周期内,那么在随后的调用(*pState < threshold) 中将相同的值 (oldstate = *pState) 与相同的阈值进行比较必须为真。
【问题讨论】:
-
不相关,但为什么要传递
pNextState_scaled参数的指针? -
FWIW,x86 的 gcc 4.8.5 在所有情况下都会产生预期的结果。
-
我认为这是gcc bug 323,其中 gcc 使用超额精度是标准未批准的地方。另请参阅gcc faq on the subject。
-
@AProgrammer 即使存在编译器错误,代码仍然过度依赖精确的浮点相等,这完全是在自找麻烦
-
@SteveFriedl,在某些情况下浮点相等是合理的,但不是很好,尽管引入了额外的 fudge 因素使其不合理,尤其是当它与这里的不等式结合时。
标签: c gcc floating-point compiler-optimization