为什么perl在这里有浮点错误?

时间:2018-07-08 15:54:22

标签: perl floating-point

我有此代码:

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”之类的操作时也不会发生这种情况。

1 个答案:

答案 0 :(得分:2)

  

为什么会这样?

请参见 What Every Programmer Should Know About Floating-Point Arithmetic

  

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

我猜您是在use bignum;内添加了sub range,但是由于bignum的作用域是有限的,所以这不会自动影响传递给该sub的变量。因此,无论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::BigRatMath::BigFloat等对象(包括通过bignum和相关的编译指示)会降低代码的速度,因此这可能是过分的。为了仅在以上sub中使用这些对象,我将使用numify将这些对象降级为常规标量,但这是可选的,具体取决于您的性能要求。

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