我正在实施一种算法来计算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...
答案 0 :(得分:10)
浮点数比较完全相同,因此10^-10
与0.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值无论如何都没有相互求和,所以除非每个实际上都影响求和,否则没有积累 - 因此上面的方法可以在不进一步降低精度的情况下工作。