3

我一生都无法弄清楚为什么以下会产生结果。

use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil($n);
print "$c ($n)\n";

Sigil-tastic,我知道——对不起。

我已经为我的应用解决了这个问题,如下所示:

use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil("$n");
print "$c ($n)\n";

...但我很困惑为什么这里有必要这样做。

4

6 回答 6

8

发生的事情是这样的:$n包含一个浮点值,因此不完全等于3,在我的计算机上它是3.00000000000000044409. Perl 足够聪明,可以3在打印时将其四舍五入,但是当您显式使用浮点函数时,它将完全按照它所宣传的那样做:ceil到下一个整数:4.

这是处理浮点数的现实,而绝不是 Perl 特有的。你不应该依赖它们的确切值。

于 2010-03-04T14:38:18.593 回答
6
use strict;
use warnings;
use POSIX;

my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;  # Should be exactly 3.

# But it's not.
print "Equals 3\n" if $n == 3;

# Check it more closely.
printf "%.18f\n", $n;

# So ceil() is doing the right thing after all.
my $c = ceil($n);
print "g=$g t=$t r=$r n=$n c=$c\n";
于 2010-03-04T14:38:00.213 回答
5

必读的 Goldberg 参考资料:每个计算机科学家都应该知道的关于浮点运算的知识

使用 Perl,在数值运算中将字符串视为数字的能力变成了一种优势,因为您可以轻松地使用它sprintf来明确指定所需的精度:

use strict; use warnings;

use POSIX qw( ceil );
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil( sprintf '%.6f', $n );
print "$c ($n)\n";

输出:

C:\温度> g
3 (3)

问题的出现是因为,鉴于可用于表示数字的位数有限,只有大量但有限的数字可以用浮点数表示。鉴于实线上有无数个数字,这将在存在中间操作的情况下导致近似和舍入误差。

于 2010-03-04T16:18:24.583 回答
4

不是 Perl 问题,因此

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
main()
{
  double n = (6.65 * 4.0 - 6.65) / 6.65;
  double c = ceil(n);
  printf("c is %g, n was %.18f\n", c, n);
}

c is 4, n was 3.000000000000000444
于 2010-03-04T15:03:13.690 回答
4

一些数字(如 6.65)具有精确的十进制表示,但不能精确地表示为计算机使用的二进制浮点数(就像 1/3 没有精确的十进制表示)。因此,浮点数通常与您预期的略有不同。您计算的结果不是 3,而是大约 3.000000000000000444。

处理这个问题的传统方法是定义一些小数(称为 epsilon),然后如果两个数的差小于 epsilon,则认为它们相等。

您的解决方案ceil("$n")有效,因为 Perl 在将浮点数转换为字符串时会将其四舍五入到小数点后 14 位(因此将 3.000000000000000444 转换回 3)。但更快的解决方案是ceil在计算之前减去 epsilon(因为会四舍五入)ceil

my $epsilon = 5e-15; # Or whatever small number you feel is appropriate
my $c = ceil($n - $epsilon);

浮点减法应该比转换为字符串并返回(涉及大量除法)更快。

于 2010-03-04T16:42:14.533 回答
2

其他答案已经解释了为什么会出现问题,有两种方法可以让它消失。

如果可以,您可以编译 Perl 以使用更高精度的类型。配置 with-Duse64bitint -Duselongdouble将使 Perl 使用 64 位整数和长双精度数。这些具有足够高的精度,可以消除大多数浮点问题。

另一种选择是使用bignum,它将打开透明的任意精度数字支持。这速度较慢,但​​精确,并且可以在词法上使用。

{
    use bignum;
    use POSIX;
    my $g = 6.65;
    my $t = $g * 4;
    my $r = $t - $g;
    my $n = $r / $g;
    my $c = ceil($n);
    print "$c ($n)\n";
}

您还可以使用Math::BigFloat声明单个任意精度数

于 2010-03-05T09:37:55.630 回答