如何修复此Perl代码,使1.1+;2.2 == 3.3?

如何修复此Perl代码,使1.1+;2.2 == 3.3?,perl,floating-point,decimal,Perl,Floating Point,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 {

如何修复此代码,使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
编辑:

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

编辑:

使用aschepler演示的sprintf技术,我现在能够“看到”问题。此外,按照mscha和rafl的建议,使用bignum修复了比较不相等的问题。然而,sprintf输出仍然表明这些数字不“正确”。这让人们对这个解决方案产生了些许怀疑


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

使用
sprintf
将变量转换为格式化字符串,然后比较结果字符串

# equal( $x, $y, $d );
# compare the equality of $x and $y with precision of $d digits below the decimal point.
sub equal {
    my ($x, $y, $d) = @_;
    return sprintf("%.${d}g", $x) eq sprintf("%.${d}g", $y);   
}
出现这种问题是因为分数(0.1、0.2等)没有完美的定点表示。因此,值
1.1
2.2
实际上分别存储为
1.100000000000…1
2.2000000…1
(我不确定它是变大还是变小。在我的示例中,我假设它们变大)。当你把它们加在一起时,它变成了
3.300000000…3
,比
3.3
大,后者被转换成
3.300000…1

abs($3-($1+$2))


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

要使用的具体“解决方案”取决于您的具体问题。您是否正在尝试跟踪货币金额?如果是这样,请使用提供的任意精度数字(使用更多内存和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;
}
输出:

1.0 + 2.0 = 3.0 1.1 + 2.2 = 3.3 1.0 + 2.0 = 3.0 1.1+2.2=3.3

基本上,Perl处理的是浮点数,而您可能希望它使用定点。处理这种情况的最简单方法是修改代码,以便在任何地方都使用整型整数,可能在最终显示例程中除外。例如,如果您正在处理美元货币,请将所有美元金额存储在便士中。123美元45美分变为“12345”。这样,在加法和减法运算期间就不会出现浮点歧义

如果这不是一个选择,考虑一下。找到一个好的ε值,并在需要比较值时使用它

然而,我大胆猜测,大多数任务并不真正需要浮点运算,我强烈建议您仔细考虑它是否适合您的任务。

来源:

为什么我的数字,比如0.1+0.2加起来不等于0.3,然后 相反,我得到了一个奇怪的结果,比如 300000000000000004?

因为在内部,计算机使用 格式(二进制浮点)为 不能准确地表示一个数字 比如0.1、0.2或0.3

当代码被编译或 已解释,您的“0.1”已 四舍五入到该表中最接近的数字 格式,这将导致 舍入误差甚至在 计算发生了

如何避免此问题?

这取决于什么样的环境 你正在做的计算

  • 如果你真的需要把结果加起来,特别是当你 与钱一起工作:使用特殊的工具
  • 如果你只是不想看到那些额外的小数位:简单地说 将结果四舍五入为固定格式 输入时的小数位数 展示它
  • 如果没有可用的十进制数据类型,另一种方法是工作 使用整数,例如domoney 计算完全以美分为单位。但是 这是更多的工作,并有一些 缺点

Youz还可以使用a来确定两个数字是否足够接近,从而可以使用精确的数学来假设它们是相同的。

要查看浮点标量的精确值,请为
sprintf
指定一个较大的精度:

print sprintf("%.60f", 1.1), $/;
print sprintf("%.60f", 2.2), $/;
print sprintf("%.60f", 3.3), $/;
我得到:

1.100000000000000088817841970012523233890533447265625000000000
2.200000000000000177635683940025046467781066894531250000000000
3.299999999999999822364316059974953532218933105468750000000000

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

一个快速修复浮点的方法是使用。只需添加一行

use bignum;
到脚本的顶部。 显然,这会影响性能,因此这可能不是一个好的解决方案

一个更本地化的解决方案是在需要更高精度的地方显式使用。

允许您使用有理数(分数)而不是小数,类似这样的内容(“:constants”被导入以自动将字符串(如“11/10”)转换为Number::Fraction对象):

其中打印:

1 + 2 == 3
11/10 + 11/5 == 33/10

或者,按照
math::Big*
-模块系列(
math::BigInt
math::BigFloat
math::BigRat
bignum
BigRat
,…)提供的更高精度的数学运算,不会链接到未经许可的书籍副本。抱歉。我不知道
1 + 2 == 3
11/10 + 11/5 == 33/10