请考虑以下程序:
$x=12345678901.234567000;
$y=($x-int($x))*1000000000;
printf("%f:%f\n",$x,$y);
以下是打印的内容:
12345678901.234568:234567642.211914
我期望的是:
12345678901.234567:234567000
这似乎是Perl中的某种舍入问题。
我该如何更改才能得到234567000
?
我做错了什么吗?
这是一个经常被问到的问题。
Why am I getting long decimals (eg, 19.9499999999999) instead of the numbers I should be getting (eg, 19.95)?
Internally, your computer represents floating-point numbers in binary. Digital (as in powers of two) computers cannot store all numbers exactly. Some real numbers lose precision in the process. This is a problem with how computers store numbers and affects all computer languages, not just Perl.
perlnumber shows the gory details of number representations and conversions. To limit the number of decimal places in your numbers, you can use the
printf
orsprintf
function. See the Floating Point Arithmetic for more details.printf "%.2f", 10/3; my $number = sprintf "%.2f", 10/3;
将"use bignum;"作为您程序的第一行。
其他答案解释了使用浮点算术时可以预期的结果——一些数字在末尾并不是真正的答案。这是为了使计算在合理的时间和空间内完成。如果您愿意花费无限的时间和空间来处理数字,那么您可以使用任意精度的数字和数学,这就是"use bignum"所实现的。它速度较慢,占用更多内存,但它的工作方式类似于您在小学学到的数学。
通常,在将程序转换为任意精度数学之前,最好了解更多关于浮点数学的工作原理。只有在非常奇怪的情况下才需要使用它。
printf "%s"
也可以通过强制大数转换为字符串来完成任务。虽然在这里没有特别的需要使用printf,但了解这一点也是值得的。 - hobbs你的作业(Google作业?):计算机如何表示浮点数?
计算机只能精确表示有限数量的数字,超出这个范围的部分只是从基数转换(二进制到十进制)中产生的噪声。这也是为什么你的$x
的最后一位看起来是8
。
$x - (int($x)
是 0.23456linenoise
,它也是一个浮点数。乘以1000000000,就会得到另一个浮点数,其中包含更多从基数不可约的性质中提取出的随机数字。
Perl不支持其内置浮点类型的任意精度算术运算。因此,您的初始变量$x
是一个近似值。您可以通过执行以下操作来查看:
$ perl -e 'printf "%.10f", 12345678901.234567000'
12345678901.2345676422
这个答案在我的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等地方,它也无法解决任何问题。
这与计算机执行的(有限的)浮点数计算的准确性有关。一般来说,当比较浮点数时,应该使用适当的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;
或者使用任意精度数学库。