C中的Double等于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; } 

如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... 

浮点比较是精确的,因此10^-100.0

基本上,你应该比较一些可容忍的差异,比如10^-7基于你写出的小数位数,可以实现如下:

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

处理浮点数时,请勿使用精确的相等运算。 尽管您的数字可能看起来0 ,但它可能类似于0.00000000000000000000001

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

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

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

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

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

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

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

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

 while(fabs(tmp) >= 0.0001) 

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

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

(并且,正如其他人所提到的,由于舍入问题,它实际上可能永远不会达到它。因此,将值与小限制进行比较比比较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的每个递减值是否对总和产生实际差异。 这比从t 0变得无关紧要的一些阈值更容易,并且可能更早地终止而不改变结果。

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