考虑以下计划:
$x=12345678901.234567000;
$y=($x-int($x))*1000000000;
printf("%f:%f\n",$x,$y);
以下是印刷品的内容:
12345678901.234568:234567642.211914
我在期待:
12345678901.234567:234567000
这似乎是Perl中的某种舍入问题
如何更改它以取代234567000
?
我做错了吗?
答案 0 :(得分:6)
这是一个经常被问到的问题。
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;
答案 1 :(得分:5)
制作“使用bignum;”你的计划的第一行。
其他答案解释了使用浮点运算时会发生什么 - 到最后的一些数字实际上并不是答案的一部分。这是为了使计算能够在合理的时间和空间内完成。如果您愿意使用无限时间和空间来处理数字,那么您可以使用任意精度数和数学,这就是“使用bignum”所启用的。它速度较慢,使用的内存较多,但它的工作方式与您在小学时学到的数学相似。
通常,在将程序转换为任意精度数学之前,最好了解浮点数学的工作原理。它只在非常奇怪的情况下才需要。
答案 2 :(得分:3)
已经回答了浮点精度的整个问题,但是尽管bignum
仍然存在问题。为什么?罪魁祸首是printf
。 bignum
是一个浅薄的实用主义。它只影响数字在变量和数学运算中的表示方式。即使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;
或者使用任意精度的数学库。