浮動小数点の落とし穴を避けるために、プログラミング言語に何ができるでしょうか?


28

浮動小数点演算とその欠点の誤解は、プログラミングにおける驚きと混乱の主な原因です(「数値が正しく加算されない」に関するスタックオーバーフローの質問の数を考慮してください)。多くのプログラマーがその意味をまだ理解していないことを考慮すると、多くの微妙なバグを(特に金融ソフトウェアに)導入する可能性があります。精度があるもののために重要ではないとき、まだその速度を提供しながら、プログラミング言語は、概念に慣れていないもののためにその落とし穴を避けるために何ができる概念を理解できますか?


26
プログラミング言語が浮動小数点処理の落とし穴を避けるためにできることは、それを禁止することだけです。これには10進法の浮動小数点も含まれますが、これは一般に不正確ですが、金融アプリケーションが事前に適応されていることを除きます。
デビッドソーンリー

4
これが「数値分析」の目的です。精度の低下を最小限に抑える方法-別名浮動小数点の落とし穴。

回答:


47

あなたは「特に金融ソフトウェアのために」と言います、それは私のペットのおしっこの1つを持ち出します:お金はフロートではなく、intです。

確かに、フロートのように見えます。小数点があります。しかし、それはあなたが問題を混乱させるユニットに慣れているからです。お金は常に整数で提供されます。アメリカでは、セントです。(特定のコンテキストでは、millsなる可能性があると思いますが、今のところそれを無視してください。)

つまり、1.23ドルと言うと、実際は123セントです。常に、常に、常にそれらの用語であなたの数学をしてください、そしてあなたは大丈夫です。詳細については、以下を参照してください。

質問に直接答える場合、プログラミング言語には合理的なプリミティブとしてMoney型を含める必要があります。

更新

わかりました、3回ではなく2回だけ「常に」と言ったはずです。実際、お金は常に整数です。そうでないと思う人は、0.3セントを送って、あなたの銀行取引明細書に結果を見せてみてください。しかし、コメンターが指摘しているように、お金のような数値で浮動小数点演算を行う必要がある場合、まれな例外があります。例えば、特定の種類の価格や利息の計算。それでも、それらは例外のように扱われるべきです。お金は整数で出入りするので、システムがそれに近づくほど、賢明になります。


20
@JoelFan:プラットフォーム固有の実装の概念を間違えています。
-whatsisname

12
それほど単純ではありません。利息の計算は、特に小数セントを生成し、特定の方法に従ってある時点で丸める必要があります。
ケビンクライン

24
架空の-1、私は下票の担当者がいないからです:) ...これはあなたの財布の中のものには正しいかもしれませんが、十分の一セントまたはそれ以下の小数をうまく処理できる会計状況がたくさんあります。Decimalこれに対処するための唯一のまともなシステムであり、あなたのコメントは、「今のことを無視し、」どこでもプログラマのための運命の前触れです:P
detly

9
@kevin cline:計算には小数セントがありますが、それらを処理する規則があります。財務計算の目標は数学的な正確さではなく、電卓を持つ銀行家とまったく同じ結果を得ることです。
デビッドソーンリー

6
「整数」という言葉を「合理的」に置き換えることで、すべてが完璧になります
エミリオガラヴァリア

15

Decimalタイプのサポートを提供すると、多くの場合に役立ちます。多くの言語には10進数型がありますが、十分に活用されていません。

実数の表現を扱うときに生じる近似を理解することは重要です。10進型と浮動小数点型の両方を使用9 * (1/9) != 1するのは正しいステートメントです。定数の場合、オプティマイザーは計算が最適になるように最適化できます。

近似演算子を提供すると役立ちます。ただし、このような比較には問題があります。.9999兆ドルは1兆ドルにほぼ等しいことに注意してください。銀行口座に差額を預けていただけますか?


2
0.9999...兆ドルは実際には1兆ドルに正確に等しい。
ちょうど私の正しい意見

5
@JUST:はい。ただし、を保持するレジスタを持つコンピューターには遭遇していません0.99999...。それらはすべて、ある時点で切り捨てられ、不平等になります。 0.9999エンジニアリングには十分です。経済的な目的ではありません。
-BillThor

2
しかし、どのようなシステムがドル単位ではなく数兆ドルを基本単位として使用しましたか?
ブラッド

@Brad計算機で(1兆/ 3)* 3を計算してみてください。どのような価値がありますか?
-BillThor

8

私が大学に行ったとき、コンピューターサイエンスの最初の年(2年生)の講義で何をすべきかを教えられました(このコースは、ほとんどの科学コースの前提条件でもありました)

「浮動小数点数は近似値です。お金には整数型を使用してください。正確な計算にはFORTRANまたは他のBCD数を使用してください」と言ったのを思い出します。(そして、彼は、バイナリ浮動小数点で正確に表現することは不可能な0.2の古典的な例を使用して、近似を指摘しました)。これは、その週の実験室演習でも明らかになりました。

同じ講義:「浮動小数点の精度を高める必要がある場合は、用語を並べ替えます。大きな数値ではなく小さな数値を加算します。」それが私の心にとどまりました。

数年前、私は非常に正確で、なおかつ高速である必要のある球面形状をいくつか持っていました。PCでの80ビットダブルはそれをカットしなかったので、可換演算を実行する前に用語をソートするプログラムにいくつかのタイプを追加しました。問題が解決しました。

ギターの品質について文句を言う前に、演奏を学んでください。

4年前にJPLで働いていた同僚がいました。彼は、私たちがいくつかのことにFORTRANを使用したことに不信を表明しました。(オフラインで計算された非常に正確な数値シミュレーションが必要でした。)「FORTRANをすべてC ++に置き換えました」と彼は誇らしげに言いました。彼らがなぜ惑星を逃したのか疑問に思うことを止めました。


2
適切な仕事に適切なツールを+1。私は実際にFORTRANを使用していませんが。ありがたいことに、私は職場で私たちの金融システムに取り組んでもいません。
ジェームズコーリー

「浮動小数点の精度を上げる必要がある場合は、用語を並べ替えます。大きな数値ではなく、小さな数値を加算します。」これに関するサンプル?
mamcx 14

@mamcx精度が1桁の10進浮動小数点数を想像してください。すべての中間結果が丸められると、計算1.0 + 0.1 + ... + 0.1(10回繰り返される)が返さ1.0れます。それを他の方法でラウンドをやって、あなたがの中間結果を取得し0.20.3、...、1.0そして最後に2.0。これは極端な例ですが、現実的な浮動小数点数では、同様の問題が発生します。基本的な考え方は、サイズが似ている数字を追加すると、エラーが最小になるということです。合計が大きいため、小さい数字から始めてください。したがって、大きい数字への加算に適しています。
-maaartinus

ただし、FortranとC ++の浮動小数点はほとんど同じです。...どちらも、正確でオフラインになっている、と私はFortranがネイティブBCDの実数を持っていないかなり確信している
マーク・

8

警告:浮動小数点型のSystem.Doubleには、直接等価テストの精度がありません。

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

言語レベルでできることやすべきだとは思いません。


1
私は長い間フロートやダブルを使用していませんので、興味があります。それは実際の既存のコンパイラ警告ですか、それともあなたが見たいものですか?
カールビーレフェルト

1
@Karl-個人的には見たことがありませんし、必要ではありませんが、献身的で環境に優しい開発者にとっては役立つと思います。
ChaosPandion

1
バイナリの浮動小数点型は、Decimal等価性テストの場合よりも質的には良くも悪くもありません。違い1.0m/7.0m*7.0mとは、1.0m以下の差よりも何桁もあり1.0/7.0*7.0ますが、それはゼロではありません。
supercat

1
@パトリック-あなたが何を得ているのか分かりません。1つのケースに当てはまることと、すべてのケースに当てはまることとの間には大きな違いがあります。
ChaosPandion

1
@ChaosPandionこの投稿の例の問題は、等価比較ではなく、浮動小数点リテラルです。正確な値が1.0 / 10のフロートはありません。浮動小数点演算により、仮数内に収まる整数を使用して計算する場合、100%正確な結果が得られます。
パトリック

7

デフォルトでは、言語は非整数の数値に任意精度の有理数を使用する必要があります。

最適化する必要がある人は、常にフロートを要求できます。これらをデフォルトとして使用することは、Cおよび他のシステムプログラミング言語では意味がありましたが、現在一般的なほとんどの言語では意味がありません。


1
その場合、不合理な数をどのように扱いますか?
-dsimcha

3
あなたはそれをfloatsと同じ方法で行います:近似。
ワコ

1
私はこれが非常に理にかなっていると言わなければなりません、正確な数を必要とするほとんどの人は非合理的ではなく有理数を必要とします
jk。

1
任意精度の有理数を使用した計算は、ハードウェアでサポートされるを使用した計算よりも桁違いに遅くなることがあります(おそらく桁違いに遅くなります)double。計算を100万分の1まで正確にする必要がある場合は、1秒を絶対に正確に計算するよりも、1秒あたり10億分の1以内にマイクロ秒を計算する方が適切です。
supercat

5
@supercat:あなたが提案しているのは、時期尚早な最適化の単なるポスターです。現在の状況では、大多数のプログラマーは高速数学をまったく必要とせず、その後、理解しにくい浮動小数点(誤)動作に噛まれるため、高速数学を必要とする比較的少数のプログラマーは、単一の追加文字を入力します。これは70年代に理にかなっており、今では単なるナンセンスです。デフォルトは安全なはずです。速く必要とする人はそれを求めるべきです。
ワコ

4

浮動小数点数に関する2つの最大の問題は次のとおりです。

  • 計算に適用された一貫性のない単位(これは整数演算にも同じように影響することに注意してください)
  • FP数は近似値であり、丸めをインテリジェントに処理する方法を理解していない。

最初のタイプの障害は、値と単位の情報を含む複合タイプを提供することによってのみ修正できます。たとえば、単位を組み込むlengthor 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 */ }

2
順番を入れ替えます。概念的な問題は広まっています。比較すると、単位変換の問題は比較的軽微です。
-S.ロット

私は精度演算子の概念が好きですが、あなたがそれについてさらに言及するように、間違いなくよく考えられる必要があります。個人的には、それを独自の完全な構文構造として見たいと思います。
ChaosPandion

ライブラリで非常に簡単に行うこともできます。
マイケルK

1
@ dan04:「すべての計算が1パーセント以内に正確」などの観点から考えていました。計測単位の処理であるタールピットを見たことがあります。
TMN

1
約25年前、数量の最大値と最小値を表す一対の浮動小数点数で構成されるタイプを特徴とする数値パッケージを見ました。数値が計算を通過すると、最大値と最小値の差が大きくなります。事実上、これにより、計算値に実際の精度がどれだけ存在するかを知る手段が提供されました。
supercat

3

プログラミング言語は何ができますか?コンパイラ/インタプリタがプログラマの代わりに自分の人生を楽にするために行うことは、通常、パフォーマンス、明快さ、読みやすさに反するため、その質問に対する答えが1つあるかどうかはわかりません。私は、C ++の方法(必要な分だけ支払う)とPerlの方法(少なからず驚きの原理)の両方が有効だと思いますが、それはアプリケーションに依存します。

プログラマーは、言語で作業し、浮動小数点の処理方法を理解する必要があります。そうしないと、仮定を行い、いつかは想定された動作が仮定と一致しなくなるからです。

プログラマが知っておくべきことについての私の見解:

  • システムおよび言語で使用可能な浮動小数点型
  • 必要なタイプ
  • コードで必要なタイプの意図を表現する方法
  • 自動型昇格を正しく活用して、正確性を維持しながら明快さと効率のバランスをとる方法

3

[浮動小数点]の落とし穴を避けるためにプログラミング言語でできること...?

賢明なデフォルトを使用します。たとえば、デクマールの組み込みサポート。

Groovyはこれを非常にうまく行いますが、多少の努力を払えば、浮動小数点の不正確さを導入するコードを書くことができます。


3

言語レベルでは何もすることがないことに同意します。プログラマーは、コンピューターは離散的で限定的であり、コンピューターに表される数学的概念の多くは近似にすぎないことを理解する必要があります。

浮動小数点を気にしないでください。ビットパターンの半分は負の数に使用され、整数演算の典型的な問題を回避するために2 ^ 64は実際には非常に小さいことを理解する必要があります。


反対、ほとんどの言語は現在、バイナリ浮動小数点型のサポートを過剰に提供し(floatの==でさえ定義されているのはなぜですか?)、有理数または10進数のサポートが不十分です
jk。

@jk:計算の結果が他の計算の結果と等しくなることが保証されない場合でも、同じ値が2つの変数に割り当てられる場合、等値比較は依然として有用です(ただし、一般的に実装される等値規則はおそらくx== yは、で計算xを実行しても、で同じ計算を実行した場合と同じ結果が得られることを意味しないため、緩すぎるy
-supercat

@supercatあなたはまだ比較が必要ですが、言語では各浮動小数点比較の許容値を指定する必要がありますが、許容値= 0を選択することで平等に戻ることができますが、少なくともそれを強制する必要があります選択
jk。

3

言語ができることの1つは、NAN値との直接比較以外の浮動小数点型からの等価比較を削除することです。

同等性テストは、2つの値とデルタを取る関数呼び出しとして、または他の値とデルタを取るEqualsToメソッドを型に許可するC#などの言語の場合にのみ存在します。


3

Lispファミリーの合理的な数のトリックを誰も指摘していないのは奇妙だと思う。

真剣に、sbclを開いて、 (+ 1 3)これを実行して*( 3 2)ください。そして、4を取得します。もしあなたが6を取得したら、今度(/ 5 3)は5/3または5/3を取得してみてください。

状況によっては多少助けになるはずですよね。


可能であれば、結果を1/3として表現する必要があるのか​​、それとも正確な小数になる可能性があるのか​​を知ることができますか?
mamcx 14

良い提案
ピーターポルフィ

3

私が見たいと思うことの一つは、という認識だろうdoubleにするfloat一方で、拡大変換とみなされるべきであるfloatdouble(*)狭めています。それは直感に反するように思えるかもしれませんが、タイプが実際に何を意味するかを考慮してください。

  • 0.1fは「13,421,773.5 / 134,217,728、プラスまたはマイナス1 / 268,435,456程度」を意味します。
  • 0.1は、実際には3,602,879,701,896,397 / 36,028,797,018,963,968を意味し、プラス/マイナス1 / 72,057,594,037,927,936程度」

一つは持っている場合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にマークを付けて、代わりに「ファジーダブル」を受け入れて返すオーバーロードを自動的に生成できる場合に特に役立ちます。


2

より多くのプログラミング言語がデータベースからページを取得し、開発者が数値データ型の長さと精度を指定できるようにした場合、浮動小数点関連のエラーの可能性を大幅に減らすことができます。開発者が変数をFloat(2)として宣言することを許可した場合、精度の小数点以下2桁の浮動小数点数が必要であることを示すと、数学演算をより安全に実行できます。変数を内部で整数として表現し、値を公開する前に100で除算することでそうした場合、より高速な整数算術パスを使用して速度を向上させることができます。Float(2)は本質的にデータを小数点以下2桁に丸めるので、Float(2)のセマンティクスにより、開発者はデータを出力する前にデータを丸める必要性を回避できます。

もちろん、開発者がその精度を必要とする場合、開発者が最大精度の浮動小数点値を要求できるようにする必要があります。また、開発者が変数に十分な精度を持たない場合、中間の丸め演算により、同じ数学演算のわずかに異なる式が潜在的に異なる結果を生成するという問題が発生します。しかし、少なくともデータベースの世界では、それほど大したことではないようです。ほとんどの人は、中間結果に高い精度を必要とするような科学的な計算を行っていません。


長さと精度を指定しても、役に立つことはほとんどありません。固定小数点10を使用すると、金融処理に役立ちます。これにより、人々が浮動小数点から受ける驚きの多くがなくなります。
デビッドソーンリー

@David-何かが足りないかもしれませんが、固定小数点10のデータ型は、ここで提案しているものとどう違うのですか?私の例のFloat(2)は、固定の2桁の10進数を持ち、自動的に最も近い100分の1に丸めます。より複雑な計算では、開発者がより多くの10進数を割り当てる必要があります。
ジャスティン洞窟

1
提唱しているのは、プログラマー指定の精度を持つ固定小数点の10進データ型です。プログラマーが指定した精度はほとんど意味がなく、COBOLプログラムで実行するために使用したようなエラーにつながると言っています。(たとえば、変数の精度を変更する場合、値が通過する変数を見逃すのは本当に簡単です。別の変数では、中間結果のサイズについて考えるよりもはるかに多くのことを考える必要があります。)
David Thornley

4
ここに浮かぶものはなく、「小数点」ではないので、Float(2)あなたが提案するようなを呼び出さないでくださいFloat
パエロエベルマン

1
  • 言語には10進型のサポートがあります。もちろん、これは実際に問題を解決するわけではありませんが、例えばexactの正確かつ有限な表現はまだありません。
  • 一部のDBおよびフレームワークはMoneyタイプをサポートしています。これは基本的にセントの数を整数として格納しています。
  • 有理数をサポートするライブラリがいくつかあります。それは⅓の問題を解決しますが、例えば√2の問題を解決しません。

上記のこれらは場合によっては適用可能ですが、実際には浮動小数点値を扱うための一般的なソリューションではありません。実際の解決策は、問題を理解し、その対処方法を学ぶことです。浮動小数点計算を使用している場合は、アルゴリズムが数値的に安定していることを常に確認する必要があります。問題に関連する数学/コンピューターサイエンスの巨大な分野があります。これは数値解析と呼ばれます。


1

他の回答が指摘しているように、金融ソフトウェアで浮動小数点の落とし穴を回避する唯一の本当の方法は、そこでそれを使用しないことです。これは実際に実行可能かもしれません- 金融数学専用の適切に設計されたライブラリを提供する場合。

浮動小数点推定値をインポートするように設計された関数は、そのように明確にラベル付けされ、その操作に適切なパラメーターを提供される必要があります。

Finance.importEstimate(float value, Finance roundingStep)

一般的に浮動小数点の落とし穴を避ける唯一の現実的な方法は教育です。プログラマーは、すべてのプログラマーが浮動小数点演算について知っておくべきことのようなものを読んで理解する必要があります。

しかし、役立つかもしれないいくつかのこと:

  • 「浮動小数点の正確な等価性テストはなぜ合法なのですか?」
  • 代わりに、isNear()関数を使用してください。
  • 浮動小数点アキュムレータオブジェクトを提供し、その使用を奨励します(浮動小数点値のシーケンスをすべて通常の浮動小数点変数に単純に追加するよりも安定して追加します)。

-1

ほとんどのプログラマーは、COBOLがそのように正しくなったことに驚くでしょう。COBOLの最初のバージョンでは、浮動小数点はなく、10進数のみでした。 ..浮動小数点は、本当に必要な場合にのみ使用されます。Cが登場したとき、何らかの理由で原始的な10進数型がなかったので、私の意見では、そこからすべての問題が始まりました。


1
Cには10進数型はありませんでした。これはプリミティブ型ではないため、ハードウェアの10進数命令を備えたコンピューターはほとんどありません。BASICとPascalには、金属にぴったりと適合するように設計されていないため、なぜそれがなかったのかと疑問に思われるかもしれません。COBOLとPL / Iは、そのようなことを経験した唯一の言語です。
デビッドソーンリー

3
@JoelFan:⅓をCOBOLでどのように書くのですか?10進数は問題を解決しません。10進数は2進数と同じくらい不正確です。
vartec11年

2
10進数は、ドルとセントを正確に表す問題を解決します。これは、「ビジネス指向」言語に役立ちます。しかし、そうでなければ、10進数は役に立ちません。同じ種類のエラー(たとえば、1/3 * 3 = 0.99999999)がありますが、はるかに遅いです。これが、会計専用に設計されていない言語のデフォルトではない理由です。
dan04

1
また、10年以上前にCに先行していたFORTRANにも、標準の10進数サポートがありません。
dan04

1
@JoelFan:四半期ごとの価値があり、月ごとの価値が必要な場合は、何を掛ける必要があると思いますか?いいえ、0.33ではなく、⅓です。
バルテック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.