浮動小数点演算とその欠点の誤解は、プログラミングにおける驚きと混乱の主な原因です(「数値が正しく加算されない」に関するスタックオーバーフローの質問の数を考慮してください)。多くのプログラマーがその意味をまだ理解していないことを考慮すると、多くの微妙なバグを(特に金融ソフトウェアに)導入する可能性があります。精度があるもののために重要ではないとき、まだその速度を提供しながら、プログラミング言語は、概念に慣れていないもののためにその落とし穴を避けるために何ができるん概念を理解できますか?
浮動小数点演算とその欠点の誤解は、プログラミングにおける驚きと混乱の主な原因です(「数値が正しく加算されない」に関するスタックオーバーフローの質問の数を考慮してください)。多くのプログラマーがその意味をまだ理解していないことを考慮すると、多くの微妙なバグを(特に金融ソフトウェアに)導入する可能性があります。精度があるもののために重要ではないとき、まだその速度を提供しながら、プログラミング言語は、概念に慣れていないもののためにその落とし穴を避けるために何ができるん概念を理解できますか?
回答:
あなたは「特に金融ソフトウェアのために」と言います、それは私のペットのおしっこの1つを持ち出します:お金はフロートではなく、intです。
確かに、フロートのように見えます。小数点があります。しかし、それはあなたが問題を混乱させるユニットに慣れているからです。お金は常に整数で提供されます。アメリカでは、セントです。(特定のコンテキストでは、millsになる可能性があると思いますが、今のところそれを無視してください。)
つまり、1.23ドルと言うと、実際は123セントです。常に、常に、常にそれらの用語であなたの数学をしてください、そしてあなたは大丈夫です。詳細については、以下を参照してください。
質問に直接答える場合、プログラミング言語には合理的なプリミティブとしてMoney型を含める必要があります。
更新
わかりました、3回ではなく2回だけ「常に」と言ったはずです。実際、お金は常に整数です。そうでないと思う人は、0.3セントを送って、あなたの銀行取引明細書に結果を見せてみてください。しかし、コメンターが指摘しているように、お金のような数値で浮動小数点演算を行う必要がある場合、まれな例外があります。例えば、特定の種類の価格や利息の計算。それでも、それらは例外のように扱われるべきです。お金は整数で出入りするので、システムがそれに近づくほど、賢明になります。
Decimal
これに対処するための唯一のまともなシステムであり、あなたのコメントは、「今のことを無視し、」どこでもプログラマのための運命の前触れです:P
Decimalタイプのサポートを提供すると、多くの場合に役立ちます。多くの言語には10進数型がありますが、十分に活用されていません。
実数の表現を扱うときに生じる近似を理解することは重要です。10進型と浮動小数点型の両方を使用9 * (1/9) != 1
するのは正しいステートメントです。定数の場合、オプティマイザーは計算が最適になるように最適化できます。
近似演算子を提供すると役立ちます。ただし、このような比較には問題があります。.9999兆ドルは1兆ドルにほぼ等しいことに注意してください。銀行口座に差額を預けていただけますか?
0.9999...
兆ドルは実際には1兆ドルに正確に等しい。
0.99999...
。それらはすべて、ある時点で切り捨てられ、不平等になります。 0.9999
エンジニアリングには十分です。経済的な目的ではありません。
私が大学に行ったとき、コンピューターサイエンスの最初の年(2年生)の講義で何をすべきかを教えられました(このコースは、ほとんどの科学コースの前提条件でもありました)
「浮動小数点数は近似値です。お金には整数型を使用してください。正確な計算にはFORTRANまたは他のBCD数を使用してください」と言ったのを思い出します。(そして、彼は、バイナリ浮動小数点で正確に表現することは不可能な0.2の古典的な例を使用して、近似を指摘しました)。これは、その週の実験室演習でも明らかになりました。
同じ講義:「浮動小数点の精度を高める必要がある場合は、用語を並べ替えます。大きな数値ではなく小さな数値を加算します。」それが私の心にとどまりました。
数年前、私は非常に正確で、なおかつ高速である必要のある球面形状をいくつか持っていました。PCでの80ビットダブルはそれをカットしなかったので、可換演算を実行する前に用語をソートするプログラムにいくつかのタイプを追加しました。問題が解決しました。
ギターの品質について文句を言う前に、演奏を学んでください。
4年前にJPLで働いていた同僚がいました。彼は、私たちがいくつかのことにFORTRANを使用したことに不信を表明しました。(オフラインで計算された非常に正確な数値シミュレーションが必要でした。)「FORTRANをすべてC ++に置き換えました」と彼は誇らしげに言いました。彼らがなぜ惑星を逃したのか疑問に思うことを止めました。
1.0 + 0.1 + ... + 0.1
(10回繰り返される)が返さ1.0
れます。それを他の方法でラウンドをやって、あなたがの中間結果を取得し0.2
、0.3
、...、1.0
そして最後に2.0
。これは極端な例ですが、現実的な浮動小数点数では、同様の問題が発生します。基本的な考え方は、サイズが似ている数字を追加すると、エラーが最小になるということです。合計が大きいため、小さい数字から始めてください。したがって、大きい数字への加算に適しています。
警告:浮動小数点型のSystem.Doubleには、直接等価テストの精度がありません。
double x = CalculateX();
if (x == 0.1)
{
// ............
}
言語レベルでできることやすべきだとは思いません。
Decimal
等価性テストの場合よりも質的には良くも悪くもありません。違い1.0m/7.0m*7.0m
とは、1.0m
以下の差よりも何桁もあり1.0/7.0*7.0
ますが、それはゼロではありません。
デフォルトでは、言語は非整数の数値に任意精度の有理数を使用する必要があります。
最適化する必要がある人は、常にフロートを要求できます。これらをデフォルトとして使用することは、Cおよび他のシステムプログラミング言語では意味がありましたが、現在一般的なほとんどの言語では意味がありません。
double
。計算を100万分の1まで正確にする必要がある場合は、1秒を絶対に正確に計算するよりも、1秒あたり10億分の1以内にマイクロ秒を計算する方が適切です。
浮動小数点数に関する2つの最大の問題は次のとおりです。
最初のタイプの障害は、値と単位の情報を含む複合タイプを提供することによってのみ修正できます。たとえば、単位を組み込むlength
or area
値(メートルまたは平方メートル、またはフィートと平方フィート)。それ以外の場合は、常に1つのタイプの測定単位で作業し、回答を人間と共有する場合にのみ別の測定単位に変換することに熱心でなければなりません。
2番目のタイプの障害は、概念上の障害です。失敗は、人々がそれらを絶対数と考えるときに現れます。これは、等値演算、累積丸め誤差などに影響します。たとえば、あるシステムでは、特定の誤差範囲内で2つの測定値が同等であることは正しいかもしれません。つまり、+ /-.1より小さい差を気にしない場合、.999と1.001は1.0とほぼ同じです。ただし、すべてのシステムがそれほど寛容ではありません。
言語レベルの機能が必要な場合は、等価精度と呼びます。NUnit、JUnit、および同様に構築されたテストフレームワークでは、正しいと見なされる精度を制御できます。例えば:
Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));
たとえば、C#またはJavaが精度演算子を含むように変更された場合、次のようになります。
if(.999 == 1.001 within .1) { /* do something */ }
ただし、そのような機能を提供する場合は、+ /-側が同じでない場合に平等が良好である場合も考慮する必要があります。たとえば、+ 1 / -10は、2つの数値のうち1つが最初の数値よりも1以内または10以内にある場合、同等と見なします。このケースを処理するには、range
キーワードも追加する必要があります。
if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }
プログラミング言語は何ができますか?コンパイラ/インタプリタがプログラマの代わりに自分の人生を楽にするために行うことは、通常、パフォーマンス、明快さ、読みやすさに反するため、その質問に対する答えが1つあるかどうかはわかりません。私は、C ++の方法(必要な分だけ支払う)とPerlの方法(少なからず驚きの原理)の両方が有効だと思いますが、それはアプリケーションに依存します。
プログラマーは、言語で作業し、浮動小数点の処理方法を理解する必要があります。そうしないと、仮定を行い、いつかは想定された動作が仮定と一致しなくなるからです。
プログラマが知っておくべきことについての私の見解:
言語レベルでは何もすることがないことに同意します。プログラマーは、コンピューターは離散的で限定的であり、コンピューターに表される数学的概念の多くは近似にすぎないことを理解する必要があります。
浮動小数点を気にしないでください。ビットパターンの半分は負の数に使用され、整数演算の典型的な問題を回避するために2 ^ 64は実際には非常に小さいことを理解する必要があります。
x
== y
は、で計算x
を実行しても、で同じ計算を実行した場合と同じ結果が得られることを意味しないため、緩すぎるy
。
私が見たいと思うことの一つは、という認識だろうdouble
にするfloat
一方で、拡大変換とみなされるべきであるfloat
とdouble
(*)狭めています。それは直感に反するように思えるかもしれませんが、タイプが実際に何を意味するかを考慮してください。
一つは持っている場合double
、それが最高の数量「十分の一」の表現および変換を保持しfloat
、結果は値の正しい説明である「/ 134217728 13,421,773.5、プラスまたはマイナス1/268435456程度」であろう。
対照的に、float
「1/10」の量の最適な表現を保持し、それをdouble
に変換するaがある場合、結果は「13,421,773.5 / 134,217,728、プラスまたはマイナス1 / 72,057,594,037,927,936程度」になります-暗黙の精度のレベルこれは5,300万を超える要因で間違っています。
IEEE-744標準では、すべての浮動小数点数がその範囲の中心で正確な数値を正確に表すかのように浮動小数点演算を実行する必要がありますが、浮動小数点値が実際に正確な数値を表すことを意味するものではありません数量。むしろ、値がそれらの範囲の中心にあると想定される要件は、3つの事実から生じます。(1)オペランドが特定の正確な値を持っているかのように計算を実行する必要があります。(2)一貫した文書化された仮定は、矛盾した文書化されていない仮定よりも役立ちます。(3)一貫性のある仮定を行う場合、量がその範囲の中心を表すと仮定するよりも他の一貫性のある仮定が優れている傾向はありません。
ちなみに、約25年ほど前、誰かがCの数値パッケージを思いついたのを思い出します。このパッケージでは、それぞれが128ビットの浮動小数点のペアで構成される「範囲型」を使用しました。すべての計算は、各結果の可能な最小値と最大値を計算するような方法で行われます。大きな長い反復計算を実行し、[12.53401391134 12.53902812673]の値を見つけた場合、丸め誤差により多くの桁の精度が失われたが、結果は12.54として合理的に表現できると確信できます(そして、 t 12.9または53.2)。特に、複数の値を並行して操作できる数学ユニットに適していると思われるので、主流言語ではそのような型のサポートがまったくないことに驚いています。
(*)実際には、単精度の数値を操作する際に中間計算を保持するために倍精度値を使用すると便利な場合が多いため、そのようなすべての操作に型キャストを使用するのは面倒です。言語は、計算をdoubleとして実行し、singleとの間で自由にキャストできる「ファジーdouble」型を持つことで役立ちます。これは、型double
と戻り値のパラメータを取る関数double
にマークを付けて、代わりに「ファジーダブル」を受け入れて返すオーバーロードを自動的に生成できる場合に特に役立ちます。
より多くのプログラミング言語がデータベースからページを取得し、開発者が数値データ型の長さと精度を指定できるようにした場合、浮動小数点関連のエラーの可能性を大幅に減らすことができます。開発者が変数をFloat(2)として宣言することを許可した場合、精度の小数点以下2桁の浮動小数点数が必要であることを示すと、数学演算をより安全に実行できます。変数を内部で整数として表現し、値を公開する前に100で除算することでそうした場合、より高速な整数算術パスを使用して速度を向上させることができます。Float(2)は本質的にデータを小数点以下2桁に丸めるので、Float(2)のセマンティクスにより、開発者はデータを出力する前にデータを丸める必要性を回避できます。
もちろん、開発者がその精度を必要とする場合、開発者が最大精度の浮動小数点値を要求できるようにする必要があります。また、開発者が変数に十分な精度を持たない場合、中間の丸め演算により、同じ数学演算のわずかに異なる式が潜在的に異なる結果を生成するという問題が発生します。しかし、少なくともデータベースの世界では、それほど大したことではないようです。ほとんどの人は、中間結果に高い精度を必要とするような科学的な計算を行っていません。
Float(2)
あなたが提案するようなを呼び出さないでくださいFloat
。
上記のこれらは場合によっては適用可能ですが、実際には浮動小数点値を扱うための一般的なソリューションではありません。実際の解決策は、問題を理解し、その対処方法を学ぶことです。浮動小数点計算を使用している場合は、アルゴリズムが数値的に安定していることを常に確認する必要があります。問題に関連する数学/コンピューターサイエンスの巨大な分野があります。これは数値解析と呼ばれます。
他の回答が指摘しているように、金融ソフトウェアで浮動小数点の落とし穴を回避する唯一の本当の方法は、そこでそれを使用しないことです。これは実際に実行可能かもしれません- 金融数学専用の適切に設計されたライブラリを提供する場合。
浮動小数点推定値をインポートするように設計された関数は、そのように明確にラベル付けされ、その操作に適切なパラメーターを提供される必要があります。
Finance.importEstimate(float value, Finance roundingStep)
一般的に浮動小数点の落とし穴を避ける唯一の現実的な方法は教育です。プログラマーは、すべてのプログラマーが浮動小数点演算について知っておくべきことのようなものを読んで理解する必要があります。
しかし、役立つかもしれないいくつかのこと:
isNear()
関数を使用してください。ほとんどのプログラマーは、COBOLがそのように正しくなったことに驚くでしょう。COBOLの最初のバージョンでは、浮動小数点はなく、10進数のみでした。 ..浮動小数点は、本当に必要な場合にのみ使用されます。Cが登場したとき、何らかの理由で原始的な10進数型がなかったので、私の意見では、そこからすべての問題が始まりました。