1に近い数値の乗算を処理する方法
-
23-08-2019 - |
質問
たくさんの浮動小数点数(Java Double)があり、そのほとんどは1に非常に近いため、より大きな計算の一部としてそれらを掛ける必要があります。私はこれをする必要があります 多くの.
問題は、Java Doublesが次のような数に問題がないことです。
0.0000000000000000000000000000000001 (1.0E-34)
彼らは次のようなものを表現することはできません:
1.0000000000000000000000000000000001
その結果、私は急速に精度を失います(Javaのダブルの場合、制限は約1.000000000000001のようです)。
1つを差し引いて数値を保存することを検討したため、たとえば1.0001は0.0001として保存されますが、問題は再びそれらを掛けるには1を追加する必要があり、この時点で精度を失います。
これに対処するために、BigDecimalsを使用して計算を実行し(BigDecimalに変換し、1.0を追加してから乗算します)、その後2倍に戻しますが、これのパフォーマンスへの影響について深刻な懸念があります。
BigDecimalの使用を避けるこれを行う方法を誰かが見ることができますか?
明確にするために編集します: :これは、勾配降下最適化アルゴリズムを採用する大規模なコラボレーションフィルター用です。多くの場合、共同フィルターが非常に少ない数を扱っているため、精度は問題です(1000分の1、10000に1人である製品の広告をクリックする可能性など)。
コラボレーションフィルターは、それ以上ではないにしても、数千万人のデータポイントでトレーニングする必要があるため、速度が問題です。
解決
うん:なぜなら
(1 + x) * (1 + y) = 1 + x + y + x*y
あなたの場合、 x
と y
とても小さいので x*y
なるだろう 遠い 小さい - 計算の結果に影響を与えるには小さすぎます。あなたが懸念している限り、
(1 + x) * (1 + y) = 1 + x + y
これは、1つの差し引きで番号を保存できることを意味します。また、増殖する代わりに、追加するだけです。結果が常に1未満である限り、それらは数学的に正確な結果に十分近く、違いを気にしません。
編集: :ちょうど気づいた:あなたは言う 多くの そのうち1は1に非常に近いです。明らかに、この手法は1に近い数字では機能しません。つまり、 x
と y
大きい。しかし、1つが大きく、1つが小さい場合でも機能する可能性があります。あなたは製品の大きさだけを気にします x*y
. 。 (そして、両方の数値が1に近い場合は、通常のJavaを使用できます double
乗算...)
他のヒント
おそらくあなたは対数を使用することができますか?
対数は、乗算を便利に加算に減らします。
また、初期精度の損失を処理するために、関数log1p(少なくとも、C/C ++に存在します)があり、精度の損失なしにログ(1+x)を返します。 (例:log1p(1E-30)私のために1E-30を返します)
その後、Expm1を使用して、実際の結果の小数の部分を取得できます。
この種の状況はまさに大きな状況ではありませんか?
追加するために編集:
「2番目の段落によると、パフォーマンスの理由で可能であれば、大規模なことを避けたいと思います。」 - 正気
「時期尚早の最適化はすべての悪の根源です」 - クヌース
あなたの問題を注文するために実際に作られた簡単なソリューションがあります。あなたはそれが十分に速くないかもしれないと心配しているので、あなたはあなたが複雑なことをしたい 考える より速くなります。 Knuthの引用は時々使いすぎますが、これはまさに彼が警告していた状況です。簡単な方法で書いてください。試して。それをプロファイルします。遅すぎるかどうかを確認してください。もしそれが それから それをより速くする方法について考え始めてください。あなたがそれが必要であることがわかるまで、この追加の複雑なバグを起こしやすいコードをすべて追加しないでください。
数字がどこから来ているのか、どのようにそれらを使用しているかによって、フロートの代わりに合理的なものを使用することをお勧めします。すべての場合の正しい答えではありませんが、それが は 正しい答えは本当に他にありません。
合理的なものが適合しない場合、対数回答を支持します。
編集の編集:編集:
応答率が低いことを表す数値を扱っている場合は、科学者が行うことをしてください。
- それらを過剰 /赤字として表現します(1.0部品を正規化)
- それらをスケーリングします。 「100万人あたりの部品」または適切なものの観点から考えてください。
これにより、計算のために合理的な数字を扱うことができます。
Javaではなくハードウェアの限界をテストしていることは注目に値します。 Javaは、CPUの64ビットフローティングポイントを使用します。
BigDecimalのパフォーマンスをテストすることをお勧めします。 BigDecimalを使用して、1秒あたり数万の計算を行うことができます。
デビッドが指摘しているように、オフセットを追加するだけです。
(1 + x) *(1 + y)= 1 + x + y + x * y
ただし、最後の用語を中止することを選択するのは危険なようです。しないでください。たとえば、これを試してください:
x = 1e-8 y = 2e-6 z = 3e-7 w = 4e-5
とは何か(1+x)(1+y)(1+z)*(1+w)?二重の精度で、私は取得します:
(1+x)(1+y)(1+z)*(1+w)
ans =
1.00004231009302
ただし、単純な追加の近似を行うだけの場合はどうなりますか。
1+(x+y+z+w)
ans =
1.00004231
重要だった可能性のある低いオーダービットを失いました。これは、製品の1との違いのいくつかが少なくともSQRT(EPS)である場合にのみ問題です。ここで、EPSは作業中の精度です。
代わりにこれを試してください:
f = @(u、v)u + v + u*v;
result = f(x、y);
result = f(result、z);
result = f(result、w);
1+結果
ans =
1.00004231009302
ご覧のとおり、これにより、二重精度の結果に戻ります。実際、結果の内部値は4.23100930230249E-05であるため、もう少し正確です。
本当に精度が必要な場合は、ダブルよりも遅い場合でも、BigDecimalのようなものを使用する必要があります。
精度が本当に必要ない場合は、おそらくデビッドの答えに合わせて行くことができます。しかし、たとえあなたが乗算をよく使用していても、それはいくつかの時期尚早の最適化かもしれないので、とにかく大きなdecimalが行く方法かもしれません
「1つに非常に近い」と言うと、正確に何人ですか?
たぶん、あなたはあなたのすべての数に1つの暗黙のオフセットを持っていて、分数を操作するだけです。