3

在更新一些遗留 Perl 代码的过程中,我遇到了 C 样式for循环过早终止的情况。

问题

可以按如下方式重新创建这种情况(我在 Linux 和 Windows 上的结果相同):

$ perl
for($i = 0.8; $i <= 2.5; $i+=0.1){
    print $i, "\n";
}
__END__
0.8
0.9
1
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
2
2.1
2.2
2.3
2.4

2.5 明显不存在;它应该已经通过了$i <= 2.5条件。


修复

意识到这可能是由于浮点表示,如 中所述perldoc perlfaq4,我更新了条件:

$ perl
for($i = 0.8; sprintf( '%.1f', $i ) <= 2.5; $i += 0.1 ) {
    print $i, "\n";
}
__END__
0.8
0.9
1
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
2
2.1
2.2
2.3
2.4
2.5      <--- Gotcha !

服务恢复了(或者我是这么认为的)。


验证

我想证明 final$i确实大于 2.5,所以我将增量包装在一个do循环中:

$ perl
for($i = 0.8;  $i <= 2.5; do { $i += 0.1; print $i, "\n"; } ) {}
__END__
0.9
1
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
2
2.1
2.2
2.3
2.4
2.5      <--- Surprise!

测试结果似乎表明浮点表示不是这里的问题。

这里发生了什么?

4

4 回答 4

5

您很惊讶大于 2.5 的值打印为2.5? 不要惊讶,这是正常的浮点数。普通打印不会显示所有数字。试试这个:

printf "%.66g\n", $i

代替你的print陈述。在我的机器上打印的最终值是2.5000000000000013. 你的可能会很相似。

于 2012-08-06T06:16:32.800 回答
1

可能是float的原因,不只是2.5,可以这样使用:

for ($i = 0.8; $i < 2.6; $i += 0.1) {

或者你可以看到:

for ($i = 0.8; $i <= 2.5; $i += 0.1) {
    print $i, "\n";
}
print $i, "\n";
print $i <= 2.5 ? 'TRUE' : 'FALSE';

输出:

0.8
0.9
1
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
2
2.1
2.2
2.3
2.4
2.5
FALSE
于 2012-08-06T05:58:52.730 回答
1

i 的值在不同的时间增加。请注意,上次运行的第一个值是 0.9,而不是 0.8。

第一个问题是浮点数,尝试在条件中使用 2.5001。尝试使用 8 到 25 乘 1 的整数,<= 可以正常工作。

for($i = 8; $i <= 25; $i += 1 ) {
    print $i/10, "\n";
}

或者

  print $_/10, "\n" for 8..25;

或者

my $epsilon = .00000001;

for(my $i = 0.8; ($i - 2.5) <= $epsilon; $i += .1 ) {
    print $i, $/;
}
于 2012-08-06T05:59:05.823 回答
1

计算机使用二进制存储,而不是十进制!如果将十进制数 0.1 存储为二进制浮点数,它将产生一个无穷大的数字 ob 二进制数:

dezimal 0.1 = 二进制 0.000110011001100110011001100110011...

所以如果你不使用整数,你会得到舍入错误。dezimal 值 0.1 可能存储为数字 0.0001100110011001101,这是 dezimal 0.100006104

添加 25 乘以 0.100006104 得到 2.50015259,这大于 2.5,因此最后一个值超出范围。

于 2012-08-06T06:19:30.700 回答