这是一个简单的程序,但我无法理解场景后面的while循环所做的逻辑/工作。
问题:编写一个程序,打印从0到1的每个数字,在小数位后面有一个数字(即0.1,0.2等)。
所以这是我的代码:
$num = 0;
while ( $num < 1 ) {
print "$num \n";
$num = $num + 0.1;
}
如果我以这种方式写它,它将打印
$num = 0;
while ( $num < 1 ) {
$num = $num + 0.1;
print "$num \n";
}
输出:
0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1
理想情况下,1和1.1不应分别在两个代码示例中打印。在向其中加入0.1时印刷0.9后,它变为1.0,即while (1.0 < 1)
。因此,while循环中的条件为false,因此不应打印1和1.1。但这就是正在发生的事情。
有人可以解释为什么while循环以这种意想不到的方式工作,即即使条件为假也打印1和1.1。
答案 0 :(得分:4)
1/10是二进制的周期数,就像1/3是十进制周期一样。
____
1/10 = 0.00011 base 2
因此,它不能用浮点数精确表示。
$ perl -e'printf "%$.20e\n", 0.1;'
1.00000000000000005551e-01
这种不精确是造成问题的原因。
$ perl -e'my $i = 0; while ($i < 1) { printf "%1\$.3f %1\$.20e\n", $i; $i += 0.1; }'
0.000 0.00000000000000000000e+00
0.100 1.00000000000000005551e-01
0.200 2.00000000000000011102e-01
0.300 3.00000000000000044409e-01
0.400 4.00000000000000022204e-01
0.500 5.00000000000000000000e-01
0.600 5.99999999999999977796e-01
0.700 6.99999999999999955591e-01
0.800 7.99999999999999933387e-01
0.900 8.99999999999999911182e-01
1.000 9.99999999999999888978e-01
一般来说,可以通过检查数字是否等于某个容差中的另一个来解决这个问题。但在这种情况下,有一个更简单的解决方案。
$ perl -e'for my $j (0..9) { my $i = $j/10; printf "%1\$.3f %1\$.20e\n", $i; }'
0.000 0.00000000000000000000e+00
0.100 1.00000000000000005551e-01
0.200 2.00000000000000011102e-01
0.300 2.99999999999999988898e-01
0.400 4.00000000000000022204e-01
0.500 5.00000000000000000000e-01
0.600 5.99999999999999977796e-01
0.700 6.99999999999999955591e-01
0.800 8.00000000000000044409e-01
0.900 9.00000000000000022204e-01
上述解决方案不仅执行正确的迭代次数,而且不会累积错误,因此$i
总是尽可能正确。
答案 1 :(得分:3)
在第二个版本中,您在循环检查之前打印,即它甚至会打印已经超出循环条件的大小写。
由于浮点精度的原因,你得到1.0以及1.1的事实由1.0略高于添加大量0.1的结果。
所以循环实际看到的是
因此,为了解决问题,请使用第一个版本,但从0.1开始,然后检查0.95。
$num = 0.1;
while ( $num < 0.95 ) {
print "$num \n";
$num = $num + 0.1;
}
答案 2 :(得分:2)
考虑一下:
use strict;
use warnings;
use v5.10;
my $num = 0;
while ($num < 1) {
say $num, " (", $num-1, ")";
$num += 0.1;
}
输出:
0 (-1)
0.1 (-0.9)
0.2 (-0.8)
0.3 (-0.7)
0.4 (-0.6)
0.5 (-0.5)
0.6 (-0.4)
0.7 (-0.3)
0.8 (-0.2)
0.9 (-0.1)
1 (-1.11022302462516e-16)
正如您所看到的,由于浮点精度问题,重复添加0.1所获得的数字不是一个,而是略小于1,这就是为什么检查比预期成功多一次的原因。
现在将其添加到脚本顶部的导入中:
use bignum;
观察输出变化:
0 (-1)
0.1 (-0.9)
0.2 (-0.8)
0.3 (-0.7)
0.4 (-0.6)
0.5 (-0.5)
0.6 (-0.4)
0.7 (-0.3)
0.8 (-0.2)
0.9 (-0.1)
这是因为计算现在在high-precision mode中完成,并且浮点错误不再生效。
通常,人们会使用浮点算法咬几次,然后学会用整数或epsilons做重要的部分。 Google,有很多关于如何处理浮点精度错误的资源。