如何消除Perl舍入错误

时间:2010-07-12 22:56:54

标签: perl rounding

考虑以下计划:

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

以下是印刷品的内容:

12345678901.234568:234567642.211914

我在期待:

12345678901.234567:234567000

这似乎是Perl中的某种舍入问题 如何更改它以取代234567000? 我做错了吗?

7 个答案:

答案 0 :(得分:6)

这是一个经常被问到的问题。

  

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

     

在内部,您的计算机代表二进制的浮点数。数字(如两个权力)计算机无法准确存储所有数字。一些实数在此过程中会失去精确度。这是计算机如何存储数字并影响所有计算机语言的问题,而不仅仅是Perl。

     

perlnumber显示了数字表示和转换的血腥细节。   要限制数字中的小数位数,可以使用printfsprintf功能。有关详细信息,请参阅Floating Point Arithmetic

printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;

答案 1 :(得分:5)

制作“使用bignum;”你的计划的第一行。

其他答案解释了使用浮点运算时会发生什么 - 到最后的一些数字实际上并不是答案的一部分。这是为了使计算能够在合理的时间和空间内完成。如果您愿意使用无限时间和空间来处理数字,那么您可以使用任意精度数和数学,这就是“使用bignum”所启用的。它速度较慢,使用的内存较多,但它的工作方式与您在小学时学到的数学相似。

通常,在将程序转换为任意精度数学之前,最好了解浮点数学的工作原理。它只在非常奇怪的情况下才需要。

答案 2 :(得分:3)

已经回答了浮点精度的整个问题,但是尽管bignum仍然存在问题。为什么?罪魁祸首是printfbignum是一个浅薄的实用主义。它只影响数字在变量和数学运算中的表示方式。即使bignum使Perl做正确的数学运算,printf仍然在C中实现。%f获取您的精确数字并将其右转回到不精确的浮点数。

仅使用print打印您的号码,他们应该可以。你必须手动格式化它们。

您可以做的另一件事是使用-Duse64bitint -Duselongdouble重新编译Perl,这会强制Perl在内部使用64位整数和long double浮点数。这将为您提供更高的准确性,更一致且几乎没有性能成本(bignum对于数学密集型代码来说有点费力)。它不像bignum那样100%准确,但会影响printf之类的内容。但是,以这种方式重新编译Perl会使二进制文件不兼容,因此您将不得不重新编译所有扩展。如果你这样做,我建议在不同的位置安装一个新的Perl(/usr/local/perl/64bit或其他东西),而不是试图管理共享同一个库的并行Perl安装。

答案 3 :(得分:2)

您的家庭作业(Googlework?):计算机如何表示浮点数?

您只能使用有限数量的精确数字,除此之外的所有内容只是基本转换的噪音(二进制到十进制)。这也是$x的最后一位数似乎为8的原因。

$x - (int($x)0.23456linenoise,也是一个浮点数。乘以1000000000,它给出了另一个浮点数,从基数的不可通约性中拉出了更多的随机数字。

答案 4 :(得分:1)

Perl不对其内置浮点类型执行任意精度算术。因此,您的初始变量$x是近似值。你可以这样做:

$ perl -e 'printf "%.10f", 12345678901.234567000'
12345678901.2345676422

答案 5 :(得分:0)

此答案适用于我的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;
}

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

如果可以的话,请避免使用bignum - 它非常强烈 - 如果您必须在数据库或JSON等任何地方存储您的号码,它将无法解决任何问题。

答案 6 :(得分:0)

这与计算机执行的浮点计算的(有限)准确性有关。通常,在比较浮点数时,应将其与合适的epsilon进行比较:

$value1 == $value2 or warn;

在大多数情况下无法正常工作。你应该做

use constant EPSILON => 1.0e-10;
abs($value1 - $value2) < EPSILON or warn;

EPSILON的选择应考虑到valueX的计算复杂性。大量的计算可能会导致EPSILON大得多。

其他人建议的另一种选择是

sprintf("%.5f", value1) eq sprintf("%.5f", value2) or warn;

或者使用任意精度的数学库。