如何修复此Perl代码以便1.1 + 2.2 == 3.3?

use*_*205 9 floating-point perl decimal

如何修复此代码以便1.1 + 2.2 == 3.3?这里发生了什么导致这种行为?我模糊地熟悉舍入问题和浮点数学,但我认为这仅适用于除法和乘法,并且在输出中可见.

[me@unixbox1:~/perltests]> cat testmathsimple.pl 
#!/usr/bin/perl

use strict;
use warnings;

check_math(1, 2, 3);
check_math(1.1, 2.2, 3.3);

sub check_math {
        my $one = shift;
        my $two = shift;
        my $three = shift;

        if ($one + $two == $three) {
                print "$one + $two == $three\n";
        } else {
                print "$one + $two != $three\n";
        }
}

[me@unixbox1:~/perltests]> perl testmathsimple.pl 
1 + 2 == 3
1.1 + 2.2 != 3.3
Run Code Online (Sandbox Code Playgroud)

编辑:

到目前为止,大多数答案都是"它是一个浮点问题,duh",并为它提供了解决方法.我已经怀疑是问题所在.我该如何演示?如何让Perl输出长形式的变量?将$ one + $ 2计算存储在临时变量中并打印它并不能证明问题.

编辑:

使用aschepler演示的sprintf技术,我现在能够"看到"问题.此外,根据mscha和rafl的建议,使用bignum可以解决比较不相等的问题.但是,sprintf输出仍然表示数字不正确.这对这个解决方案留下了一点疑问.

bignum是解决这个问题的好方法吗?是否有任何可能的副作用,我们在将其整合到一个更大的,现有的程序时应该注意什么?

Sin*_*nür 17

看看每个计算机科学家应该知道的关于浮点运算的内容.

这些都不是Perl特有的:有无数无数的实数,显然,所有这些都不能仅使用有限数量的位来表示.

要使用的具体"解决方案"取决于您的具体问题.你想跟踪货币金额吗?如果是这样,请使用bignum提供的任意精度数字(使用更多内存和更多CPU,获得更准确的结果).你在做数值分析吗?然后,决定要使用的精度,并使用sprintf(如下所示)并eq进行比较.

你可以随时使用:

use strict; use warnings;

check_summation(1, $_) for [1, 2, 3], [1.1, 2.2, 3.3];

sub check_summation {
    my $precision = shift;
    my ($x, $y, $expected) = @{ $_[0] };
    my $result = $x + $y;

    for my $n ( $x, $y, $expected, $result) {
        $n = sprintf('%.*f', $precision, $n);
    }

    if ( $expected eq $result ) {
        printf "%s + %s = %s\n", $x, $y, $expected;
    }
    else {
        printf "%s + %s != %s\n", $x, $y, $expected;
    }
    return;
}
Run Code Online (Sandbox Code Playgroud)

输出:

1.0 + 2.0 = 3.0
1.1 + 2.2 = 3.3


小智 6

"每个计算机科学家应该知道的浮点运算"

基本上,Perl正在处理浮点数,而你可能期望它使用定点.处理这种情况的最简单方法是修改代码,以便在任何地方使用整数,除非在最终的显示例程中.例如,如果您正在处理美元货币,请将所有美元金额存储在便士中.123美元和45美分成为"12345".这样,在加法和减法操作期间没有浮点模糊.

如果这不是一个选择,请考虑Matt Kane的评论.找到一个好的epsilon值,并在需要比较值时使用它.

我冒昧地猜测大多数任务并不真正需要浮点数,但我强烈建议仔细考虑它是否适合您的任务.


asc*_*ler 5

要查看浮点标量的精确值,请为 提供较大的精度sprintf

print sprintf("%.60f", 1.1), $/;
print sprintf("%.60f", 2.2), $/;
print sprintf("%.60f", 3.3), $/;
Run Code Online (Sandbox Code Playgroud)

我得到:

1.100000000000000088817841970012523233890533447265625000000000
2.200000000000000177635683940025046467781066894531250000000000
3.299999999999999822364316059974953532218933105468750000000000
Run Code Online (Sandbox Code Playgroud)

不幸的是,C99 的 %a 转换似乎不起作用。 perlvar提到一个过时的变量$#,它更改了打印数字的默认格式,但如果我给它一个 %f ,它就会中断,并且 %g 拒绝打印“非有效”数字。


msc*_*cha 5

修复浮点的一种快速方法是使用bignum.只需添加一条线

use bignum;
Run Code Online (Sandbox Code Playgroud)

到你的脚本的顶部.显然,存在性能影响,因此这对您来说可能不是一个好的解决方案.

更本地化的解决方案是在需要更高精度的地方明确使用Math :: BigFloat.