【问题标题】:Unexpected result using POSIX ceil() in Perl在 Perl 中使用 POSIX ceil() 的意外结果
【发布时间】:2010-03-05 17:37:55
【问题描述】:

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

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

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

【问题讨论】:

  • 一般提示:通过包含“我希望结果是 X,但我得到 Y”这样的内容,这样的问题往往会得到很大的改善。这样一来,人们就不必输入并运行您的代码的两个版本才能给出有意义的答案。

标签: perl floating-point posix ceil


【解决方案1】:

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

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

【讨论】:

  • 非常感谢大家的回答。我很高兴这是与现实的普遍浮点偏差。但是,哇!
【解决方案2】:
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";

【讨论】:

    【解决方案3】:

    强制性戈德堡参考: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)

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

    【讨论】:

      【解决方案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】:

        不是 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
        

        【讨论】:

          【解决方案6】:

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

          如果可以,您可以编译 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 声明单个任意精度数

          【讨论】:

          • 即使是长双打也不能阻止这个问题。您仍然会得到一些比您预期的整数稍大的结果,ceil 会将其四舍五入到下一个整数。长双打只会使“稍大”的数量变小。
          猜你喜欢
          • 2018-02-14
          • 1970-01-01
          • 2020-09-03
          • 2012-09-27
          • 2019-01-09
          • 2022-10-14
          • 2021-12-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多