当前位置: 首页 > 知识库问答 >
问题:

Perl-while循环的意外结果

闾丘鸣
2023-03-14

这是一个简单的程序,但我无法理解while loop在幕后所做的逻辑/工作。

问题:编写一个程序,打印从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.9后,当0.1添加到它时,它变成1.0,即,而(1.0

有人能解释一下为什么while循环会以这种意想不到的方式工作,即即使条件为false,也会打印1和1.1。


共有3个答案

翟奇
2023-03-14

考虑一下:

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获得的数字不完全是一,而是略小于一,这就是为什么检查比预期多成功一次的原因。

现在将此添加到脚本顶部的导入中:

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)

这是因为现在的计算是在高精度模式下进行的,浮点错误不再悄悄出现。

通常,人们会被浮点运算咬上几次,然后学会用整数或ε来完成重要的部分。谷歌,有很多关于如何处理浮点精度错误的资源。

公孙巴英
2023-03-14

在第二个版本中,在循环检查之前打印,即它甚至会打印已经超出循环条件的案例。

得到1.0和1.1的原因是,由于浮点精度的原因,1.0略高于添加大量0.1的结果。

所以循环实际看到的是

  • 0.9999

所以为了解决,使用第一个版本,但从0.1开始,对照0.95进行检查。

$num = 0.1;
while ( $num < 0.95 ) {
  print "$num \n";
  $num = $num + 0.1;
}
戚阳曜
2023-03-14

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总是尽可能正确。

 类似资料:
  • Perl 循环 while 语句在给定条件为 true 时,重复执行语句或语句组。循环主体执行之前会先测试条件。 语法 语法格式如下所示: while(condition) { statement(s); } 在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。 condition 可以是任意的表达式,当条件为 true 时执行循环。 当条件为 fals

  • Perl 循环 不像 for 和 while 循环,它们是在循环头部测试循环条件。在 Perl 语言中,do...while 循环是在循环的尾部检查它的条件。 do...while 循环与 while 循环类似,但是 do...while 循环会确保至少执行一次循环。 语法 语法格式如下所示: do { statement(s); }while( condition ); 请注意,条件表

  • 我有一个用户表和一个高尔夫比赛分数表。当用户参加比赛时,他使用表格在结果表中记录分数。我想显示一个结果表,显示用户的完整列表和比赛的分数。表中有八列分数——每门课一列。我正在努力使用php代码来显示结果分数。如果一名球员已经比赛,他的得分将正确显示,但如果表中的下一名球员没有比赛,则他的得分将显示为表中高于他的球员的得分。这将在列表中继续下去,直到获得真正的分数。我试图找到答案,但没有成功。这是我

  • 我正在尝试制作一个简单的程序,它使用扫描器进行输入,并有一个while循环,该循环继续输入,直到输入结束字符为止。我想让扫描器接受输入并将一个字符串添加到堆栈列表中。我试图弄清楚为什么这段代码在键入空格时不终止while循环。

  • 主要内容:循环控制语句,无限循环,实例有的时候,我们可能需要多次执行同一块代码。一般情况下,语句是按顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的流程图: 注意,数字 0, 字符串 '0' 、 "" , 空 list () , 和 undef 为 false ,其他值均为 true。 tru

  • 有的时候,我们可能需要多次执行同一块代码。一般情况下,语句是按顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的流程图: 注意,数字 0, 字符串 '0' 、 "" , 空 list () , 和 undef 为 false ,其他值均为 true。 true