コンピューターが無限に賢明で正確だとまだ考えているプログラマーや素人に、浮動小数点の不正確さをどのように説明しますか?
正確でありながらドライな説明よりもはるかに優れたアイデアを理解できると思われるお気に入りの例や逸話はありますか?
これはコンピューターサイエンスのクラスでどのように教えられていますか?
基本的に、人々が浮動小数点数でつまずく2つの大きな落とし穴があります。
規模の問題。各FP数値には、数値の全体的な「スケール」を決定する指数があるため、非常に小さな値または非常に大きな値を表すことができますが、そのために使用できる桁数は制限されています異なるスケールの2つの数値を追加すると、小さいスケールが大きいスケールに適合する方法がないため、小さい方が「食べられる」ことがあります。
PS> $a = 1; $b = 0.0000000000000000000000001
PS> Write-Host a=$a b=$b
a=1 b=1E-25
PS> $a + $b
1
この場合の例えとして、大きなスイミングプールと小さじ1杯の水を想像できます。どちらもサイズは非常に異なりますが、個々に大まかな大きさを簡単に把握できます。ただし、小さじ1杯をプールに注ぐと、おおよそ水で満たされたスイミングプールが残ります。
(これを学習している人々が指数表記法で問題を抱えている場合は、1
および100000000000000000000
などの値を使用することもできます。)
次に、2進数表現と10進数表現の問題があります。 0.1
のような数値は、限られた量の2進数では正確に表現できません。しかし、いくつかの言語はこれを隠しています:
PS> "{0:N50}" -f 0.1
0.10000000000000000000000000000000000000000000000000
ただし、数字を繰り返し加算することで、表現エラーを「増幅」できます。
PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum
9,99999999999998
しかし、これを適切に説明するニースの類推は考えられません。あなたが表現できる理由は基本的に同じ問題です 1/3 正確な値を取得するには、小数の最後で3を無期限に繰り返す必要があるため、おおよそ10進数でのみです。
同様に、2進小数は2分の1、4分の1、8分の1などを表すのに適していますが、10分の1などの場合、無限に繰り返される2進数のストリームが生成されます。
次に、別の問題がありますが、多くの人が大量の数値処理を行わない限り、ほとんどの人はそれにつまずきません。しかし、その後、それらはすでに問題について知っています。多くの浮動小数点数は単なる正確な値の近似であるため、これは、実数の特定の近似frを意味します無限に多くの実数が存在する可能性がありますr1、r2、...まったく同じ近似にマッピングされます。これらの数字は一定の間隔にあります。 rとしましょう分 rの可能な最小値であり、結果としてfおよびr最大 これが成り立つrの最大可能値は、間隔[r分、r最大]ここで、その間隔の任意の数値は、実際の数値rになります。
これで、その数値に対して計算(加算、減算、乗算など)を実行すると、精度が失われます。すべての数値は単なる概算であるため、実際にはintervalsで計算を実行しています。結果も間隔であり、近似誤差はますます大きくなり、間隔が広がります。その計算から単一の数値を取得できます。ただし、これは、possible結果の間隔からの1つのone数であり、元のオペランドの精度と、計算。
この種のことは 間隔演算 と呼ばれ、少なくとも私にとっては大学の数学コースの一部でした。
ベース10システムが正確に同じ問題に苦しんでいることを見せてください。
10進数で1/3を10進表記として表現するようにしてください。正確に行うことはできません。
そのため、「0.3333」と記述すると、多くのユースケースでかなり正確に表現できます。
しかし、それを端数に戻すと、「3333/10000」が得られます。これは、「1/3」と同じnotです。
1/2などの他の小数は、10進数の有限の10進数表現で簡単に表すことができます: "0.5"
現在、base-2とbase-10には本質的に同じ問題があります。両方とも正確に表現できない数値を持っています。
Base-10では、base-2で1/10を「0.1」として表す問題はありませんが、「0.000110011 ..」で始まる無限の表現が必要になります。
これは素人への外植にいかがですか。コンピューターが数値を表す1つの方法は、個別の単位をカウントすることです。これらはデジタルコンピューターです。整数部、小数部のないものについては、現代のデジタルコンピューターは2の累乗をカウントします:1、2、4、8. , Place value、binary digit、blah、blah、blah。分数については、デジタルコンピューターは2の逆累乗をカウントします:1/2、1/4、1/8、...問題は、これらの逆累乗の有限数の合計では多くの数値を表現できないことです。より多くの場所の値(より多くのビット)を使用すると、これらの「問題」番号の表現の精度が向上しますが、ビット数が限られているため正確に取得できません。一部の数値は、無限のビット数で表現できません。
スヌーズ...
容器内の水の量を測定したいのですが、フルカップ、ハーフカップ、クォーターカップの3つの測定カップしかありません。最後のフルカップを数えた後、カップの3分の1が残っているとしましょう。しかし、使用可能なカップの組み合わせを正確に満たしていないため、それを測定することはできません。ハーフカップは満たされず、クォーターカップからのオーバーフローは小さすぎて何も満たせません。したがって、エラーがあります-1/3と1/4の違い。このエラーは、他の測定からのエラーと組み合わせると悪化します。
Pythonの場合:
>>> 1.0 / 10
0.10000000000000001
一部の分数をバイナリで正確に表現できないことを説明します。一部の分数(1/3など)が基数10で正確に表現できないように。
Cの別の例
printf (" %.20f \n", 3.6);
信じられないほど与える
3.60000000000000008882
これが私の簡単な理解です。
問題:値0.45はフロートで正確に表すことができず、0.450000018に切り上げられます。何故ですか?
回答:45のint値は、バイナリ値101101で表されます。値0.45にするために、45 x 10 ^ -2(= 45/10 ^ 2)を取ることができれば正確です。しかし、それは不可能です。 10ではなくベース2を使用する必要があります。
したがって、10 ^ 2 = 100に最も近いのは128 = 2 ^ 7です。必要なビットの総数は、値45(101101)の場合は9:6 +値7(111)の場合は3ビットです。次に、値45 x 2 ^ -7 = 0.3515625。今、あなたは深刻な不正確な問題を抱えています。 0.3515625は0.45にほぼ近くありません。
この不正確さをどのように改善しますか?値45と7を別の値に変更できます。
460 x 2 ^ -10 = 0.44921875はどうでしょう。現在、460に9ビット、10に4ビットを使用しています。その後、少し近くなりますが、まだそれほど近くありません。ただし、初期の望ましい値が0.44921875の場合、近似なしで完全に一致します。
したがって、値の式はX = A x 2 ^ Bになります。ここで、AとBは正または負の整数値です。ただし、値AおよびBを表すビット数が制限されていることがわかっているため、数値が高いほど精度は高くなります。 floatの場合、合計数は32です。Doubleには64があり、Decimalには128があります。
9999999.4999999999をfloat
に変換してからdouble
に戻すと、かわいい数字の奇妙な部分が観察される場合があります。その値は明らかに9999999に近く、9999999.499999999は9999999に正しく丸められますが、結果は10000000として報告されます。