
@rawpixel
学童でさえ、さまざまな数値システムの存在と、すべての有限小数が2進数システムの有限小数ではないという事実を認識しています。 この事実により、floatとdoubleの演算が正確ではないと考える人はほとんどいません。
Erlangについて話すと、他の多くの言語と同様に、floatのIEEE754標準を実装しますが、Erlangの標準のInteger型は任意精度の計算を使用して実装されます。 ただし、bigintだけでなく、必要な精度で有理数、複素数、浮動小数点数を操作できるようにしたいと考えています。
この記事では、浮動小数点数のコーディングの理論の最小限の概要と、新しい効果の最も顕著な例について説明します。 固定小数点表現への移行を通じて必要な操作精度を提供するソリューションは、Earlang / Elixirで開発された金融アプリケーションのニーズを満たすように設計されたEAPAライブラリ(Erlang Arbitrary Precision Arithmetic)として設計されています。
標準、標準、標準...
今日、バイナリ浮動小数点演算の主要な標準は、エンジニアリングおよびプログラミングで広く使用されているIEEE754です。 次の4つのプレゼンテーション形式を定義しています。
- 単精度32ビット
- 倍精度64ビット
- 単一拡張精度> = 43ビット(まれに使用)
- 倍精度拡張> = 79ビット(通常80ビットが使用されます)
および4つの丸めモード: - 丸め、最も近い整数に傾向があります。
- ゼロに向かう傾向。
- +∞に向かう傾向
- -∞への丸め
最新のマイクロプロセッサのほとんどは、IEEE754形式の実変数の表現をハードウェアで実装して製造されています。 プレゼンテーション形式は数値のサイズ制限を制限し、丸めモードは精度に影響します。 多くの場合、プログラマはハードウェアの動作を変更したり、プログラミング言語を実装したりすることはできません。 たとえば、公式のErlang実装では、64ビットマシンでは3ワード、32ビットでは4ワードで浮動小数点数が格納されます。
前述のように、IEEE754形式の数値は有限の集合であり、その上に実数の無限の集合がマッピングされるため、元の数値はIEEE754の形式でエラー付きで表示される場合があります。
有限集合に表示された数値の大部分には、安定した小さな相対誤差があります。 したがって、フロートの場合は11.920928955078125e-6%で、ダブルの場合は2.2204460492503130808472633361816e-14%です。 プログラマーの生活の中で、解決できる日常のタスクのほとんどは、このエラーを無視することを可能にしますが、単純なタスクであっても、絶対エラーの大きさは、フロートとダブルでそれぞれ10 31と10 292に達し、計算が困難になることに注意してください。
効果図
一般情報からビジネスまで。 Erlangの新しい効果を再現してみましょう。
以下の例はすべて、ctテストとして設計されています。
丸めと精度の低下
古典から始めましょう-2つの数字の追加:0.1 + 0.2 = ?:
t30000000000000004(_)-> ["0.30000000000000004"] = io_lib:format("~w", [0.1 + 0.2]).
追加の結果は、直感的に予想されるものとわずかに異なり、テストは正常に合格します。 適切な結果を達成してみましょう。 EAPAを使用してテストを書き換えます。
t30000000000000004_eapa(_)->
このテストも成功し、問題が解決されたことを示しています。
実験を続けて、1.0に非常に小さな値を追加します。
tiny(_)-> X = 1.0, Y = 0.0000000000000000000000001, 1.0 = X + Y.
ご覧のとおり、私たちの増加は見過ごされました。 ライブラリの機能の1つである自動スケーリングを同時に説明しながら、問題を修正しようとしています。
tiny_eapa(_)-> X1 = eapa_int:with_val(1, <<"1.0">>), X2 = eapa_int:with_val(25, <<"0.0000000000000000000000001">>), <<"1.0000000000000000000000001">> = eapa_int:to_float(eapa_int:add(X1, X2)).
ビットグリッドオーバーフロー
小さい数値に関連する問題に加えて、オーバーフローは明らかで重大な問題です。
float_overflow(_) -> 1.0 = 9007199254740991.0 - 9007199254740990.0, 1.0 = 9007199254740992.0 - 9007199254740991.0, 0.0 = 9007199254740993.0 - 9007199254740992.0, 2.0 = 9007199254740994.0 - 9007199254740993.0.
テストからわかるように、ある時点で、差は1.0に等しくなくなりますが、これは明らかに問題です。 EAPAはこの問題も解決します。
float_overflow_eapa(_)-> X11 = eapa_int:with_val(1, <<"9007199254740992.0">>), X21 = eapa_int:with_val(1, <<"9007199254740991.0">>), <<"1.0">> = eapa_int:to_float(1, eapa_int:sub(X11, X21)), X12 = eapa_int:with_val(1, <<"9007199254740993.0">>), X22 = eapa_int:with_val(1, <<"9007199254740992.0">>), <<"1.0">> = eapa_int:to_float(1, eapa_int:sub(X12, X22)), X13 = eapa_int:with_val(1, <<"9007199254740994.0">>), X23 = eapa_int:with_val(1, <<"9007199254740993.0">>), <<"1.0">> = eapa_int:to_float(1, eapa_int:sub(X13, X23)).
危険な削減
次のテストは、危険な削減の発生を示しています。 このプロセスには、結果の値が入力よりもはるかに小さい操作の計算精度の壊滅的な低下が伴います。 この場合、減算1の結果。
Erlangにこの問題が存在することを示します。
reduction(_)-> X = float(87654321098765432), Y = float(87654321098765431), 16.0 = XY.
予想された1.0ではなく16.0でした。 この状況を修正してみましょう。
reduction_eapa(_)-> X = eapa_int:with_val(1, <<"87654321098765432">>), Y = eapa_int:with_val(1, <<"87654321098765431">>), <<"1.0">> = eapa_int:to_float(eapa_int:sub(X, Y)).
Erlangの浮動小数点演算のその他の機能
負のゼロを無視することから始めましょう。
eq(_)-> true = list_to_float("0.0") =:= list_to_float("-0.0").
EAPAはこの動作を保持していると言いたいだけです。
eq_eapa(_)-> X = eapa_int:with_val(1, <<"0.0">>), Y = eapa_int:with_val(1, <<"-0.0">>), true = eapa_int:eq(X, Y).
有効だからです。 Erlangには、NaNと無限大の明確な構文と処理がありません。これにより、次のような多くの機能が発生します。
1> math:sqrt(list_to_float("-0.0")). 0.0
次のポイントは、大小の数を処理する機能です。 小さなもののために再現してみましょう:
2> list_to_float("0."++lists:duplicate(322, $0)++"1"). 1.0e-323 3> list_to_float("0."++lists:duplicate(323, $0)++"1"). 0.0
多数の場合:
4> list_to_float("1"++lists:duplicate(308, $0)++".0"). 1.0e308 5> list_to_float("1"++lists:duplicate(309, $0)++".0"). ** exception error: bad argument
少数の場合のいくつかの例を次に示します。
6> list_to_float("0."++lists:duplicate(322, $0)++"123456789"). 1.0e-323 7> list_to_float("0."++lists:duplicate(300, $0)++"123456789"). 1.23456789e-301
8> 0.123456789e-100 * 0.123456789e-100. 1.524157875019052e-202 9> 0.123456789e-200 * 0.123456789e-200. 0.0
上記の例は、Erlangプロジェクトの真実を裏付けています。IEEE754ではお金をカウントできません。
EAPA(Erlang Arbitrary-Precision Arithmetic)
EAPAは、Rustで記述されたNIF拡張です。 現時点では、EAPAリポジトリは、固定小数点数を扱うための最もシンプルで便利なeapa_intインターフェイスを提供します。 eapa_intの機能には次のものがあります。
- IEEE754コーディングの効果の欠如
- 多数サポート
- 小数点以下126桁までの構成可能な精度。 (現在の実装では)
- 自動スケーリング
- 数字に関するすべての基本操作のサポート
- プロパティベースを含む多かれ少なかれ完全なテスト。
eapa_int
インターフェース:
with_val/2
浮動小数点数の固定表現への変換with_val/2
、xmlで安全に使用できます。to_float/2
固定小数点数を指定された精度で浮動小数点数に変換します。to_float/1
固定小数点数を浮動小数点数に変換します。add/2
つの数値の合計sub/2
差mul/2
乗算divp/2
除算min/2
数字の最小値max/2
数字の最大値eq/2
数値の等価性を確認しますlt/2
数が少ないことを確認しますlte/2
同等以下のチェックgt/2
数値が大きいことを確認しますgte/2
同等以上のチェック
EAPAコードはリポジトリhttps://github.com/Vonmo/eapaにあります。
eapa_intをいつ使用する必要がありますか? たとえば、アプリケーションがお金で動作する場合、または92233720368547758079223372036854775807.92233720368547758079223372036854775807などの数値に対して便利かつ正確に計算操作を実行する必要がある場合、EAPAを安全に使用できます。
他のソリューションと同様に、EAPAは妥協案です。 実際のシステムで収集されたパフォーマンステストと統計では、ほとんどの操作が3〜30μsの範囲で実行されることが示されています。 EAPA固定小数点インターフェイスを選択するときは、この点も考慮する必要があります。
おわりに
もちろん、ErlangやElixirでこのような問題を解決することは必ずしも必要ではありませんが、問題が発生して適切なツールが見つからない場合は、解決策を考案する必要があります。
この記事は、一部の人々にとってこのライブラリが有用で時間の節約に役立つことを期待して、ツールと経験をコミュニティと共有する試みです。
アーランのお金についてどう思いますか?
PS有理数と複素数の処理、および整数、浮動小数点、複素数、Rationalタイプの任意精度へのネイティブアクセスについては、以下の資料で説明します。 切り替えないでください!
関連資料: