对于这个模棱两可的标题我深表歉意。
我今天重构了一些非常旧(c89旧)的c代码,遇到了一个非常奇怪的舍入问题。
旧代码使用一组#defines来声明特定计算中使用的某些值。在重构代码时,我打算将计算包装成更可重用的函数,接受值作为参数,但是在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

我知道在浮点运算方面有精度损失,但这肯定是一个可以解决的问题吗?我真的很想将计算封装到一个可重用的函数中,但是由于在从#defines切换到函数参数时精度的损失,所有的计算都将是不正确的。
作为我意思的一个例子,下面是代码中一点的摘录,这一点非常重要:
#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版本少迭代一次,这意味着结果永远不会匹配。
还有人遇到这个问题吗?有没有解决办法,或者我应该停止与“抄送”的斗争,吸取教训,努力解决它?
编辑:当使用#definegcc时(我的重构版本将用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 );

08-27 21:06
查看更多