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

シジル・タスティック、私は知っています—ごめんなさい。

私は次のように私のアプリのためにこれを解決しました:

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と正確には等しくありません。Perl はそれを印刷するときにに丸めるのに十分スマートですが、浮動小数点関数を明示的に使用すると、それが宣伝していることを正確に実行します:次の整数: .33.000000000000000444093ceil4

これは浮動小数点数を扱う場合の現実であり、決して 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 リファレンス: 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)

この問題が発生するのは、数値を表現するために使用できるビット数が有限である場合、浮動小数点で表現できる数値は大きくても有限数しかないためです。実線上には数え切れないほど多くの数値があるため、中間演算があると近似誤差と丸め誤差が発生します。

于 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 など) は 10 進数で正確に表現できますが、コンピューターが使用する 2 進浮動小数点では正確に表現できません (1/3 に正確な 10 進数表現がないのと同様)。その結果、浮動小数点数は予想とはわずかに異なることがよくあります。あなたの計算結果は 3 ではなく、約 3.00000000000000444 です。

これを処理する伝統的な方法は、小さな数 (イプシロンと呼ばれる) を定義し、2 つの数の差がイプシロン未満の場合は等しいと見なすことです。

ceil("$n")Perlは浮動小数点数を文字列に変換するときに小数点以下約14桁に丸めるため、ソリューションは機能します(したがって、3.00000000000000444を3に変換します)。しかし、より高速な解決策は、ceil計算する前にイプシロンを減算することです (切り上げられるため) 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

他の回答は、問題が存在する理由を説明しています。問題を解決するには2つの方法があります。

可能であれば、より精度の高い型を使用するように Perl をコンパイルできます。with を設定する-Duse64bitint -Duselongdoubleと、Perl は 64 ビット整数と long double を使用します。これらは、ほとんどの浮動小数点の問題を解消するのに十分な精度を備えています。

もう 1 つの方法は、透過的な任意精度数のサポートをオンにする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 に答える