5

Perlに単純なforループがあります

for ($i=0; $i <= 360; $i += 0.01)
{
print "$i ";
}

このコードを実行すると、次の出力が得られるのはなぜですか。0.81に達すると、突然、小数点以下の桁数が増え始めます。この問題を回避するために単純に切り上げることができることは知っていますが、なぜそれが発生するのか疑問に思いました。0.01の増分は、まったく気が狂っているようには見えません。

 0.77
 0.78
 0.79
 0.8
 0.81
 0.820000000000001
 0.830000000000001
 0.840000000000001
 0.850000000000001
 0.860000000000001
 0.870000000000001
4

4 に答える 4

16

コンピューターはバイナリ表現を使用します。すべての10進浮動小数点数が2進表記で正確に表現されているわけではないため、エラーが発生する可能性があります(実際には丸めの違いです)。これは、金銭的価値に浮動小数点数を使用すべきではないのと同じ理由です。

混乱したレセプト

dailywtfから撮影した写真)

この問題を回避する最も洗練された方法は、計算に整数を使用し、それらを正しい小数点以下の桁数に分割し、sprintf印刷される小数点以下の桁数を制限するために使用することです。これにより、次のことが確実になります。

  • 印刷された結果を修正することは常にあります
  • 丸め誤差が累積しない

このコードを試してください:

#!/usr/bin/perl
for ($i=0; $i <= 360*100; $i += 1) {
  printf "%.2f \n", $i/100;
}
于 2013-02-06T23:55:36.650 に答える
7

基本的に、10進数0.01は2進浮動小数点で正確に表現されていないため、時間の経過とともに、0.01に最適な近似値を追加すると、希望する答えから外れます。

これは(バイナリ)浮動小数点演算の基本的なプロパティであり、Perlに固有のものではありません。すべてのコンピューター科学者が浮動小数点演算について知っておくべきことは標準のリファレンスであり、Google検索で非常に簡単に見つけることができます。

参照:Cコンパイラのバグ(浮動小数点演算)および間違いなく他の無数の質問。


Kernighan&Plaugerは、古くて古典的な本「The Elements of Programming Style」で、次のように述べています。

  • 賢明な古いプログラマーはかつて、「浮動小数点数は小さな砂の山のようなものです。1つ移動するたびに、小さな砂を失い、小さな汚れを増やします」と言いました。

彼らはまた言う:

  • 10*0.1はほとんど1.0ではありません

どちらのことわざも、浮動小数点演算は正確ではないことを指摘しています。

一部の最新のCPU(IBM PowerPC)には、IEEE 754:2008の10進浮動小数点演算が組み込まれていることに注意してください。Perlが正しいタイプを使用した場合(おそらくそうではありません)、計算は正確になります。

于 2013-02-06T23:50:40.603 に答える
4

ジョナサンの答えを示すために、Cで同じループ構造をコーディングすると、同じ結果が得られます。CとPerlはコンパイルが異なり、異なるマシンで実行される場合がありますが、基礎となる浮動小数点演算ルールにより、一貫した出力が得られるはずです。注:Perlは浮動小数点表現に倍精度浮動小数点を使用しますが、Cではコーダーは明示的にfloatまたはdoubleを選択します。

Cでループ:

    #include <stdio.h>

    int main() {
        double i;
        for(i=0;i<=1;i+=.01)  {
          printf("%.15f\n",i);
        } 
      }

出力:

    0.790000000000000
    0.800000000000000
    0.810000000000000
    0.820000000000001
    0.830000000000001
    0.840000000000001
    0.850000000000001

ポイントをさらに示すために、ループをCでコーディングしますが、単精度浮動小数点演算を使用して、出力の精度が低く、さらに不安定であることを確認します。

出力:

    0.000000000000000
    0.009999999776483 
    0.019999999552965
    0.029999999329448
    0.039999999105930
    0.050000000745058
    0.060000002384186
    0.070000000298023
    0.079999998211861
    0.089999996125698
    0.099999994039536
于 2013-02-07T00:02:26.203 に答える
2

1/10は、1/3が10進数で周期的であるのと同じように、2進数で周期的です。そのため、浮動小数点数に正確に格納することはできません。

>perl -E"say sprintf '%.17f', 0.1"
0.10000000000000001

整数で動作する

for (0*100..360*100) {
   my $i = $_/100;
   print "$i ";
}

または多くの丸めを行います

for (my $i=0; $i <= 360; $i = sprintf('%.2f', $i + 0.01)) {
   print "$i ";
}
于 2013-02-07T00:02:17.723 に答える