对于这个模棱两可的标题我深表歉意。
我今天重构了一些非常旧(c89旧)的c代码,遇到了一个非常奇怪的舍入问题。
旧代码使用一组#define
s来声明特定计算中使用的某些值。在重构代码时,我打算将计算包装成更可重用的函数,接受值作为参数,但是在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
s切换到函数参数时精度的损失,所有的计算都将是不正确的。作为我意思的一个例子,下面是代码中一点的摘录,这一点非常重要:
#define DT 0.001
// -- snip
int steps = (int)(1.0 / DT); // evaluates to 1000
for(int i = 0; i < steps; ++i)
// do stuff
对
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
时(我的重构版本将用C++编写,而不是C99 C,我只是在这个例子中使用C来简化)。 最佳答案
这看起来像是在这里进行的常量折叠,如果我们查看第一组代码的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 );