Perl for loop to haywire

时间:2013-02-06 23:41:01

标签: perl for-loop

我在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

4 个答案:

答案 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"中说:

  • 一位聪明的老程序员曾经说过“浮点数就像一堆沙子;每次移动一个,你就会失去一点沙子并获得一点污垢”。

他们还说:

  • 10 * 0.1几乎不是1.0

两种说法都指出浮点运算不精确。

请注意,某些现代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 ";
}