我在Perl中有一个简单的for循环
for ($i=0; $i <= 360; $i += 0.01)
{
print "$i ";
}
为什么当我运行此代码时,我得到以下输出,一旦达到0.81,它突然开始添加更多小数位的加载?我知道我可以简单地围绕以避免这个问题,但我想知道它为什么会发生。 0.01的增量似乎并不疯狂。
0.77
0.78
0.79
0.8
0.81
0.820000000000001
0.830000000000001
0.840000000000001
0.850000000000001
0.860000000000001
0.870000000000001
答案 0 :(得分:15)
计算机使用二进制表示。并非所有十进制浮点数都具有二进制表示法的精确表示,因此可能会发生一些错误(实际上是一个舍入差异)。这与why you shouldn't use floating point numbers for monetary values:
的原因相同messed up recepit http://img.thedailywtf.com/images/200902/errord/DSC00669.JPG
(图片来自dailywtf)
解决此问题的最优雅方法是使用整数进行计算,将它们分成正确的小数位数,并使用sprintf
来限制打印的小数位数。这将确保:
试试这段代码:
#!/usr/bin/perl
for ($i=0; $i <= 360*100; $i += 1) {
printf "%.2f \n", $i/100;
}
答案 1 :(得分:7)
基本上,因为十进制数0.01在二进制浮点中没有精确表示,所以随着时间的推移,将最佳近似值加到0.01会偏离您想要的答案。
这是(二进制)浮点运算的基本属性,而不是Perl特有的。 What Every Computer Scientist Should Know About Floating-Point Arithmetic是标准参考,您可以通过Google搜索轻松找到它。
另见:C compiler bug (floating point arithmetic)毫无疑问是其他无数问题。
Kernighan&amp;普劳格在他们古老但经典的书"The Elements of Programming Style"中说:
他们还说:
两种说法都指出浮点运算不精确。
请注意,某些现代CPU(IBM PowerPC)内置了IEEE 754:2008十进制浮点运算。如果Perl使用了正确的类型(可能没有),那么你的计算就是精确的。
答案 2 :(得分:4)
为了演示Jonathan的答案,如果您在C中编写相同的循环结构,您将得到相同的结果。尽管C和Perl可以以不同方式编译并在不同的机器上运行,但是底层浮点算术规则应该导致一致的输出。注意:Perl对其浮点表示使用双精度浮点,而在C中,编码器显式选择float或double。
在C中循环:
#include <stdio.h>
int main() {
double i;
for(i=0;i<=1;i+=.01) {
printf("%.15f\n",i);
}
}
输出:
0.790000000000000
0.800000000000000
0.810000000000000
0.820000000000001
0.830000000000001
0.840000000000001
0.850000000000001
为了进一步说明这一点,用C编写循环,但现在使用单精度浮点运算,看输出不精确,甚至更不稳定。
输出:
0.000000000000000
0.009999999776483
0.019999999552965
0.029999999329448
0.039999999105930
0.050000000745058
0.060000002384186
0.070000000298023
0.079999998211861
0.089999996125698
0.099999994039536
答案 3 :(得分:2)
1/10是二进制的周期性,就像1/3是十进制的周期性。因此,它无法准确地存储在浮点数中。
>perl -E"say sprintf '%.17f', 0.1"
0.10000000000000001
使用整数
for (0*100..360*100) {
my $i = $_/100;
print "$i ";
}
或者进行大量的舍入
for (my $i=0; $i <= 360; $i = sprintf('%.2f', $i + 0.01)) {
print "$i ";
}