简体   繁体   English

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

[英]Unexpected result using POSIX ceil() in Perl

I can't for the life of me figure out why the following produces the result it does. 我不能为我的生活弄清楚为什么以下产生它的结果。

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, I know — sorry. Sigil-tastic,我知道 - 抱歉。

I've solved this for my app as follows: 我已经为我的应用解决了这个问题,如下所示:

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

...but I'm bewildered as to why that's necessary here. ......但我很困惑为什么这里有必要。

What's happening is this: $n contains a floating point value, and thus is not exactly equal to 3 , on my computer it's 3.00000000000000044409 . 发生了什么: $n包含浮点值,因此不完全等于3 ,在我的计算机上它是3.00000000000000044409 Perl is smart enough to round that to 3 when printing it, but when you explicitly use a floating point function, it will do exactly what it advertises: ceil that to the next integral number: 4 . Perl非常聪明,可以在打印时将其舍入为3 ,但是当您明确使用浮点函数时,它将完全按照它所宣传的内容执行: ceil到下一个整数: 4

This is a reality of working with floating point numbers, and by no means Perl specific. 这是使用浮点数的现实,绝不是Perl特定的。 You shouldn't rely on their exact value. 你不应该依赖他们的确切价值。

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

Obligatory Goldberg reference: What Every Computer Scientist Should Know About Floating-Point Arithmetic . 强硬的戈德堡参考: 每个计算机科学家应该知道的关于浮点运算的内容

Using Perl, the ability to treat a string as a number in a numerical operation turns into an advantage because you can easily use sprintf to explicitly specify the amount of precision you want: 使用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";

Output: 输出:

C:\Temp> g
3 (3)

The problem comes about because, given the finite number of bits available to represent a number, there are only a large but finite number of numbers that can be represented in floating point. 问题出现的原因是,由于可用于表示数字的有限位数,因此只有大量但有限数量的数字可以浮点表示。 Given that there are uncountably many numbers on the real line, this will result in approximation and rounding errors in the presence of intermediate operations. 鉴于实线上有无数的数字,这将导致在存在中间操作时出现近似和舍入误差。

Not a Perl problem, as such 这不是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

Some numbers (like 6.65) have an exact representation in decimal, but cannot be represented exactly in the binary floating point that computers use (just like 1/3 has no exact decimal representation). 某些数字(如6.65)具有十进制的精确表示,但无法在计算机使用的二进制浮点中精确表示(就像1/3没有精确的十进制表示)。 As a result, floating point numbers are frequently slightly different than what you would expect. 因此,浮点数通常与您预期的略有不同。 The result of your calculation is not 3, but about 3.000000000000000444. 您的计算结果不是3,而是约3.000000000000000444。

The traditional way of handling this is to define some small number (called epsilon), and then consider two numbers equal if they differ by less than epsilon. 处理这种情况的传统方法是定义一些小数字(称为epsilon),然后如果它们相差小于epsilon则考虑两个数字相等。

Your solution of ceil("$n") works because Perl rounds a floating point number to around 14 decimal places when converting it to a string (thus converting 3.000000000000000444 back to 3). 你的ceil("$n")解决方案是有效的,因为Perl将浮点数舍入到14个小数位时将其转换为字符串(从而将3.000000000000000444转换回3)。 But a faster solution would be to subtract epsilon (since ceil will round up) before computing ceil : 但更快的解决方案是在计算ceil之前减去epsilon(因为ceil会向上舍入):

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

A floating point subtraction should be faster than converting to a string and back (which involves a lot of division). 浮点减法应该比转换为字符串和返回更快(这涉及大量的除法)。

The other answers have explained why the problem is there, there's two ways to make it go away. 其他答案已经解释了问题存在的原因,有两种方法可以让它消失。

If you can, you can compile Perl to use higher precision types. 如果可以,您可以编译Perl以使用更高精度的类型。 Configuring with -Duse64bitint -Duselongdouble will make Perl use 64 bit integers and long doubles. 使用-Duse64bitint -Duselongdouble进行配置将使Perl使用64位整数和长双精度。 These have high enough precision to make most floating point problems go away. 这些具有足够高的精度,使大多数浮点问题消失。

Another alternative is to use bignum which will turn on transparent arbitrary precision number support. 另一种方法是使用bignum ,它将打开透明的任意精确数字支持。 This is slower, but precise, and can be used lexically. 这个速度较慢,但​​很精确,可以在词汇上使用。

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

You can also declare individual arbitrary precision numbers using Math::BigFloat 您还可以使用Math :: BigFloat声明单个任意精度数字

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM