函数参数中的舍入错误

时间:2014-09-25 02:41:26

标签: c linux ubuntu

我对这个非常模棱两可的标题道歉。

我今天正在重构一些非常古老的(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,吸收它并解决它?

编辑:当使用gccg++时会发生这种情况(我的重构版本将使用C ++编写,而不是c99 C,为简单起见,我在此示例中仅使用了C语言。)

1 个答案:

答案 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 );