C 函数参数中的舍入错误
我对这个模棱两可的标题表示歉意 今天我重构了一些非常旧的(c89旧的)C代码,遇到了一个非常奇怪的取整问题 旧代码使用了一组C 函数参数中的舍入错误,c,linux,ubuntu,C,Linux,Ubuntu,我对这个模棱两可的标题表示歉意 今天我重构了一些非常旧的(c89旧的)C代码,遇到了一个非常奇怪的取整问题 旧代码使用了一组#defines来声明特定计算中使用的一些值。在重构代码的过程中,我打算将计算包装成更可重用的函数,该函数接受值作为参数,但是在Ubuntu下我遇到了一个相当奇怪的舍入问题(在Windows上不是这样) 用一个例子来解释可能更容易: #include <stdio.h> #define SOMEVAR 0.001 void test(double val
#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);
}
我知道浮点运算会有精度损失,但这肯定是一个可以解决的问题吗?我真的很想将计算封装到一个可重用的函数中,但是由于从#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
#定义DT 0.001
//--剪断
整数步长=(整数)(1.0/DT);//估计为1000
对于(int i=0;i
vs
void计算(双dt){
int steps=(int)(1.0/dt);//计算结果为999
对于(int i=0;i
如您所见,函数化版本的迭代次数将比#define
版本少一次,这意味着结果永远不会匹配
还有谁遇到过这个问题吗?有解决办法吗?或者我应该停止与#define
斗争,接受它并解决它吗
<>编辑:这是在使用<代码> GCC < /C>或<代码> G++< /C> >(我的重构版本将用C++编写,而不是C99 C,我只是在这个例子中使用C来简化)。我们可以看到,第一次计算归结为一个常数:
movl $1000, %esi #,
因此,在本例中,编译器在转换过程中执行计算,因为这两个值都是常量,并且它知道表达式实际上只是:
1.0 / 0.001
而在第二种情况下,由于两个值都不是常量,编译器在运行时进行计算:
divsd %xmm0, %xmm1 # value, D.1987
cvttsd2si %xmm1, %esi # D.1987, calc2
因此,不幸的是,计算结果并不相等,在某些情况下可能会导致不同的结果,尽管我还不能重现您在任何在线编译器上看到的结果
<>如果你要重构C++,可以使用C++ 11,那么你可以总是使用<代码> CONTXPRPR <代码>来获得编译时间评估:
constexpr double SOMEVAR = 0.001 ;
//....
constexpr int calc1 = (int)(1.0 / SOMEVAR );
您特别询问C和C89;请不要用C++来双重标记这样的问题。它在C和C++两种情况下发生,我只说旧版本的代码是代码> C89<代码>,我的修订版本将在C++中,在Ubuntu下也有同样的舍入问题。为什么?C++和C++都是一个合法的问题,旧版本是用旧的C语言编写的,我的C++将用C++编写。如果有一个代码> C++ +<代码> -具体的解决方案,如果我只在标签中指定了代码>代码>代码>,我将永远不会得到它。@ JONAATEN LeFFLLE如果OP重构到C++,那么我认为添加C++标签是有意义的。这是否意味着我已经完蛋了,需要接受它并使用
#define
s进行编译时评估?@JasonLarke我无法重现结果,因此很难提出解决方案,因为我无法验证它们。您在每个平台上使用的编译器和版本是什么?gcc(Ubuntu/Linaro 4.6.3-1ubuntu5)4.6.3
g++(Ubuntu/Linaro 4.6.3-1ubuntu5)4.6.3
Ubuntu 12.04.3 LTS
(作为VirtualBox虚拟机运行)还有constepr
,是否有任何方法向编译器提示函数参数?i、 evoid test(constexpr double x)
?@JasonLarke您可以使用,但参数必须是常量表达式,以便在编译时对其求值。
divsd %xmm0, %xmm1 # value, D.1987
cvttsd2si %xmm1, %esi # D.1987, calc2
constexpr double SOMEVAR = 0.001 ;
//....
constexpr int calc1 = (int)(1.0 / SOMEVAR );