在Perl中使用POSIX ceil()的意外结果

时间:2010-03-04 14:21:20

标签: perl floating-point posix ceil

我不能为我的生活弄清楚为什么以下产生它的结果。

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";

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

6 个答案:

答案 0 :(得分:8)

这是怎么回事:$n包含一个浮点值,因此不完全等于3,在我的计算机上它是3.00000000000000044409。 Perl非常聪明,可以在打印时将其舍入到3,但是当您明确使用浮点函数时,它将完全按照它所宣传的内容执行:ceil到下一个整数:{{1} }}

这是使用浮点数的现实,绝不是Perl特有的。你不应该依赖他们的确切价值。

答案 1 :(得分: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";

答案 2 :(得分:5)

强制性Goldberg参考:What Every Computer Scientist Should Know About Floating-Point Arithmetic

使用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:\Temp> g
3 (3)

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

答案 3 :(得分: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

答案 4 :(得分: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);

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

答案 5 :(得分:1)

其他答案已经解释了问题存在的原因,有两种方法可以让它消失。

如果可以,可以编译Perl以使用更高精度的类型。使用-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

声明单个任意精度数字