首页 > 解决方案 > 为什么 perl 这里有浮点错误?

问题描述

我有这个代码:

sub range {
        my ($start, $end, $step) = @_;

        if($step == 0) {
                die("Step size cannot be 0!")
        }
        if($start > $end) {
                ($start, $end) = ($end, $start);
        }
        my @range = ();
        for (my $i = $start; $i <= $end; $i += $step) {
                push @range, $i;
        }

        return @range;
}

跑步时

my @range = range(-3, -2.7, 0.01);

我得到这个列表:

...
$VAR23 = '-2.78';
$VAR24 = '-2.77';
$VAR25 = '-2.76000000000001';
$VAR26 = '-2.75000000000001';
$VAR27 = '-2.74000000000001';
$VAR28 = '-2.73000000000001';
$VAR29 = '-2.72000000000001';
$VAR30 = '-2.71000000000001';
$VAR31 = '-2.70000000000001';

为什么会这样?

我在 4.9.0-7-amd64 #1 SMP Debian 4.9.107-1 机器上有 perl v5.24.1。添加 bignum 模块并不会改变计算出的变量错误的事实。

此外,在执行“-2.7 - 0.01”之类的操作时不会发生这种情况。

标签: perlfloating-point

解决方案


为什么会这样?

看看每个程序员应该知道的关于浮点运算的知识

添加 bignum 模块并不会改变计算出的变量错误的事实。

我猜use bignum;sub range添加bignumsub. 因此bignum,无论您传入的文字在哪里range定义并且它会起作用,都需要生效,或者,您可以升级sub自身的变量,如下所示:

use Math::BigRat;
sub range {
    my $start = Math::BigRat->new(shift);
    my $end   = Math::BigRat->new(shift);
    my $step  = Math::BigRat->new(shift);
    ...
    return map {$_->numify} @range;
}

但是,在任何地方使用Math::BigRat,Math::BigFloat等(包括通过bignum和相关的编译指示)对象会减慢代码速度,因此这可能是矫枉过正。为了使对象仅在sub上述范围内使用,我将对象降级为带有 的常规标量numify,但这取决于您的性能要求是可选的。

sprintf("%.2f",$i)根据您实际需要的精度,您还可以通过例如(仅作为示例:)四舍五入您的数字for (my $i = $start; $i <= $end; $i = 0+sprintf("%.2f",$i+$step) )。正如@ikegami 在评论中指出的那样,另一种可能性是使用整数并最后进行除法,如 eg map { $_/100 } -300 .. -270


推荐阅读