我对这个非常模棱两可的标题道歉。
我今天正在重构一些非常古老的(c89旧)C代码并且遇到了一个非常奇怪的舍入问题。
旧代码使用一堆#define
来声明特定计算中使用的某些值。在重构代码时,我打算将计算包装成更可重用的函数,该函数接受值作为参数,但是我在Ubuntu下遇到了一个相当奇怪的舍入问题(在Windows上不是这种情况)。
用示例解释可能更容易:
#include <stdio.h>
#define SOMEVAR 0.001
void test(double value) {
int calc1 = (int)(1.0 / SOMEVAR); // using the #define directly (so an in-place 0.001)
int calc2 = (int)(1.0 / value); // using the parameter value
printf("#define: %d\n", calc1); // prints 1000, as expected
printf("param: %d\n", calc2); // prints 999 on Ubuntu and 1000 on Windows
}
int main(int argc, char *argv[]) {
test(SOMEVAR);
}
使用以下命令在Ubuntu上编译
gcc -std=c99 -o test test.c
我知道在浮点运算方面存在精度损失,但这肯定是一个可以解决的问题吗?我真的想将计算封装到一个可重用的函数中,但是从#define
切换到函数参数时精度会降低,计算结果都是不正确的。
作为我的意思的一个例子,这里是代码中的一个点的摘录,这会产生巨大的差异:
#define DT 0.001
// -- snip
int steps = (int)(1.0 / DT); // evaluates to 1000
for(int i = 0; i < steps; ++i)
// do stuff
VS
void calculate(double dt) {
int steps = (int)(1.0 / dt); // evaluates to 999
for(int i = 0; i < steps; ++i)
// do stuff
}
正如您所看到的,功能化版本将比#define
版本少一次迭代,这意味着结果永远不会匹配。
还有其他人遇到过这个问题吗?有没有解决方案,或者我应该停止对抗#define
,吸收它并解决它?
编辑:当使用gcc
或g++
时会发生这种情况(我的重构版本将使用C ++编写,而不是c99 C,为简单起见,我在此示例中仅使用了C语言。)
答案 0 :(得分:3)
这看起来像是在这里继续折叠,如果我们查看第一组代码的godbolt output,我们可以看到第一个计算被归结为常数:
movl $1000, %esi #,
所以在这个例子中,编译器在翻译期间执行计算,因为两个值都是常量,并且它知道表达式实际上只是:
1.0 / 0.001
而在第二种情况下,因为两个值都不是常量,所以编译器在运行时评估:
divsd %xmm0, %xmm1 # value, D.1987
cvttsd2si %xmm1, %esi # D.1987, calc2
所以不幸的是,计算不是等价的,在某些情况下可能导致不同的结果,尽管我无法重现您在任何在线编译器上看到的结果。
如果您要重构C ++并且可以使用C ++ 11,那么您始终可以使用constexpr
来进行编译时评估:
constexpr double SOMEVAR = 0.001 ;
//....
constexpr int calc1 = (int)(1.0 / SOMEVAR );