代码之家  ›  专栏  ›  技术社区  ›  User1

如何消除Perl舍入错误

  •  3
  • User1  · 技术社区  · 14 年前

    考虑以下程序:

    $x=12345678901.234567000;
    $y=($x-int($x))*1000000000;
    printf("%f:%f\n",$x,$y);
    

    以下是印刷品:

    12345678901.234568:234567642.211914

    我期待着:

    12345678901.234567:234567000

    这似乎是Perl中的某种舍入问题。
    我怎么能把它换成 234567000 相反?
    我做错什么了吗?

    6 回复  |  直到 6 年前
        1
  •  5
  •   jrockway    14 年前

    “使用” bignum ;“程序的第一行。

    其他答案解释了使用浮点运算时的期望值——一些接近结尾的数字实际上不是答案的一部分。这是为了使计算能够在合理的时间和空间内进行。如果你愿意使用无限的时间和空间来处理数字,那么你可以使用任意精度的数字和数学,这就是“使用bignum”所能实现的。它的速度较慢,使用的内存也更多,但它的工作方式和你在小学学的数学一样。

    一般来说,在将程序转换为任意精度数学之前,最好了解有关浮点数学如何工作的更多信息。只有在非常奇怪的情况下才需要。

        2
  •  6
  •   Greg Bacon    14 年前

    这是一个常见问题。

    Why am I getting long decimals (eg, 19.9499999999999) instead of the numbers I should be getting (eg, 19.95)?

    在内部,您的计算机以二进制表示浮点数字。数字计算机(如二次乘方)不能准确地存储所有的数字。有些实数在这个过程中会失去精度。这是计算机如何存储数字和影响所有计算机语言的问题,而不仅仅是Perl。

    perlnumber 显示数字表示和转换的详细信息。 要限制数字中的小数位数,可以使用 printf sprintf 功能。见 Floating Point Arithmetic 了解更多详细信息。

    printf "%.2f", 10/3;
    my $number = sprintf "%.2f", 10/3;
    
        3
  •  3
  •   Schwern    14 年前

    浮点精度的整个问题已经得到了解决,但尽管如此,您仍然可以看到这个问题。 bignum . 为什么?罪魁祸首是 printf . 比格姆 是一个肤浅的语用。它只影响数字在变量和数学运算中的表示方式。尽管 比格姆 使Perl正确地进行数学运算, 普林特 仍然在C中实现。 %f 取你的精确数字,然后把它转回到一个不精确的浮点数。

    打印数字时只需 print 他们应该做得很好。您必须手动格式化它们。

    您可以做的另一件事是用重新编译Perl -Duse64bitint -Duselongdouble 这将强制Perl在内部使用64位整数和 long double 浮点数。这将使您获得更高的准确性、更一致性和几乎没有性能成本。( 比格姆 对于数学密集型代码来说,这有点像一个性能猪。不是100%准确 比格姆 但是它会影响诸如 普林特 . 但是,以这种方式重新编译Perl会使它与二进制文件不兼容,因此您必须重新编译所有扩展。如果这样做,我建议在其他位置安装一个新的Perl( /usr/local/perl/64bit 或者其他)而不是试图管理并行Perl安装共享同一个库。

        4
  •  2
  •   Svante    14 年前

    作业(谷歌作业?)对你来说:计算机如何表示浮点数?

    您只能有有限数量的精确数字,除此之外的一切都只是从基转换(二进制到十进制)的噪声。这也是为什么 $x 似乎是 8 .

    $x - (int($x) 0.23456linenoise ,也是一个浮点数。乘以1000000000,它给出另一个浮点数,从基数的不可公度中提取出更多的随机数字。

        5
  •  1
  •   Greg Hewgill    14 年前

    Perl不为其内置的浮点类型执行任意精度运算。所以你的初始变量 $x 是近似值。您可以通过执行以下操作看到这一点:

    $ perl -e 'printf "%.10f", 12345678901.234567000'
    12345678901.2345676422
    
        6
  •  0
  •   cnd    6 年前

    这个答案在我的X64平台上有效,通过 适应 误差的大小

    sub safe_eq {
      my($var1,$var2)=@_;
      return 1 if($var1==$var2);
      my $dust;
      if($var2==0) { $dust=abs($var1); }
      else { $dust= abs(($var1/$var2)-1); }
      return 0 if($dust>5.32907051820076e-15 ); # 5.32907051820075e-15
      return 1;
    }
    

    你可以在上面的基础上解决你的大部分问题。

    如果可以的话,尽量避免吃大麻。 惊人地 慢加上它不会解决任何问题,如果你必须存储你的数字任何地方,如数据库或JSON等。