2011-02-01 67 views
10

我被实现算法中C.双等于0问题用C

double taylor_ln(int z) { 
    double sum = 0.0; 
    double tmp = 1.0; 

    int i = 1; 
    while(tmp != 0.0) { 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } 

    return sum * 2; 
} 

来计算自然对数。如图所示由打印语句,TMP确实等于0.0最终,然而,循环继续。什么可能导致这个?

我在Fedora 14 AMD64和与编译:

clang -lm -o taylor_ln taylor_ln.c 

例子:

$ ./taylor_ln 2 
(1.0/1) * (pow(((2 - 1.0)/(2 + 1.0)), 1)) = 0.333333 
(1.0/3) * (pow(((2 - 1.0)/(2 + 1.0)), 3)) = 0.
(1.0/5) * (pow(((2 - 1.0)/(2 + 1.0)), 5)) = 0.000823 
(1.0/7) * (pow(((2 - 1.0)/(2 + 1.0)), 7)) = 0.000065 
(1.0/9) * (pow(((2 - 1.0)/(2 + 1.0)), 9)) = 0.000006 
(1.0/11) * (pow(((2 - 1.0)/(2 + 1.0)), 11)) = 0.000001 
(1.0/13) * (pow(((2 - 1.0)/(2 + 1.0)), 13)) = 0.000000 
(1.0/15) * (pow(((2 - 1.0)/(2 + 1.0)), 15)) = 0.000000 
(1.0/17) * (pow(((2 - 1.0)/(2 + 1.0)), 17)) = 0.000000 
(1.0/19) * (pow(((2 - 1.0)/(2 + 1.0)), 19)) = 0.000000 
(1.0/21) * (pow(((2 - 1.0)/(2 + 1.0)), 21)) = 0.000000 
and so on... 
+0

哇,四个人在同一时间有相同的答案。 – mgiuca 2011-02-01 03:24:36

+0

看看:http://stackoverflow.com/questions/4664662/understanding-floating-point-problems/4664784。如果你不了解它们,浮点数可能非常棘手。 – 2011-02-01 03:25:50

回答

9

浮点比较确切足够小,所以10^-10是不一样的0.0

基本上,你应该比较对一些容许差别,说10^-7根据你写出来的小数位数,可以实现为:

while(fabs(tmp) > 10e-7) 
1

浮点数处理时,不要使用精确的相等操作。虽然你的号码可能看起来0,它可能是类似于0.00000000000000000000001

如果在格式化字符串中使用%.50f而不是%f,则会看到此内容。后者对小数位使用了一个合理的默认值(在你的情况下是6),但前者明确指出你需要很多。

为安全起见,使用增量,以检查它是否足够接近,例如:

if (fabs (val) < 0.0001) { 
    // close enough. 
} 

显然,增量完全取决于你的需求。如果你说钱,10 -5可能会很多。如果你是物理学家,你应该选择一个更小的值。

当然,如果你是一个数学家,没有任何不准确是:-)

0

仅仅因为一个号码显示为“0.000000 “并不意味着它等于0.0。数字的小数显示比双精度可以存储的精度低。

您的算法有可能达到非常接近0的点,但下一步的移动很小,以至于它与之前的相同,因此它永远不会接近0 (刚进入无限循环)。

通常,您不应该将浮点数与==!=进行比较。您应该始终检查它们是否在某个小范围内(通常称为epsilon)。例如:

while(fabs(tmp) >= 0.0001) 

然后,当它变得相当接近于0

0

print语句显示舍入值就会停止,这是不打印尽可能高的精度。所以你的循环还没有真正达到零。

(而且,正如其他人所说,由于四舍五入问题,它实际上可能永远也做不到。价值对一个小的限制比较,因此比平等0.0相比更加强劲。)的讨论

0

充足原因,但这里是一个替代的解决方案:

double taylor_ln(int z) 
{ 
    double sum = 0.0; 
    double tmp, old_sum; 
    int i = 1; 
    do 
    { 
     old_sum = sum; 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", 
       i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } while (sum != old_sum); 
    return sum * 2; 
} 

这种方法侧重于TMP的每个下降值是否能留下实实在在的区别总结。这比从tmp变得无关紧要的0计算出一些阈值要容易得多,并且可能在不改变结果的情况下提前终止。

请注意,当您将相对较大的数字与相对较小的数字相加时,结果中的有效数字会限制精度。相比之下,如果你总结几个小的那个,然后把它加到大的那个,那么你可能有足够的把那个大的一个碰到一个。在你的算法中,小的tmp值并不是相互累加的,所以没有累积,除非每个值实际上影响和 - 因此上述方法没有进一步降低精度。