C中的Double等于0问题

时间:2011-02-01 03:19:06

标签: c floating-point logarithm

我正在实施一种算法来计算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;
}

如print语句所示,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.012346
(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...

5 个答案:

答案 0 :(得分:10)

浮点数比较完全相同,因此10^-100.0不同。

基本上,你应该比较一些可容忍的差异,比如10^-7根据你写出的小数位数,可以这样做:

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

答案 1 :(得分:2)

处理浮点数时不要使用精确的相等运算。虽然您的号码可能看起来像<{1}},但它可能类似于0

如果您在格式字符串中使用0.00000000000000000000001而不是%.50f,则会看到这一点。后者对小数位使用合理的默认值(在你的情况下为6),但前者明确表明你想要很多。

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

%f

显然,三角洲完全取决于您的需求。如果你说钱,10 -5 可能就足够了。如果你是一名物理学家,你应该选择一个较小的值。

当然,如果你是一名数学家,没有不准确的程度: - )

答案 2 :(得分:0)

仅仅因为数字显示为“0.000000”并不意味着它等于0.0。数字的十进制显示精度低于双精度存储的精度。

你的算法有可能达到非常接近0的程度,但是下一步移动的程度很小,以至于它变得与以前相同,因此它永远不会接近0(只是进入一个无限循环)。

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

while(fabs(tmp) >= 0.0001)

然后它会在合理接近0时停止。

答案 3 :(得分:0)

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

(并且,正如其他人所提到的,由于四舍五入问题,它实际上可能永远无法达到它。因此,将值与小限制进行比较比将等式与0.0进行比较更为稳健。)

答案 4 :(得分: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的每个递减值是否与总和有实际差异。它比从0开始计算tmp变得无关紧要的阈值更容易,并且可能更早地终止而不改变结果。

请注意,当您将相对较大的数字与相对较小的数字相加时,结果中的有效数字会限制精度。相比之下,如果你总结几个小的,然后将它添加到大的那个,那么你可能有足够的力量将大的那个稍微抬起来。在你的算法中,小tmp值无论如何都没有相互求和,所以除非每个实际上都影响求和,否则没有积累 - 因此上面的方法可以在不进一步降低精度的情况下工作。