質問

たくさんの浮動小数点数(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

あなたの場合、 xy とても小さいので x*y なるだろう 遠い 小さい - 計算の結果に影響を与えるには小さすぎます。あなたが懸念している限り、

(1 + x) * (1 + y) = 1 + x + y

これは、1つの差し引きで番号を保存できることを意味します。また、増殖する代わりに、追加するだけです。結果が常に1未満である限り、それらは数学的に正確な結果に十分近く、違いを気にしません。

編集: :ちょうど気づいた:あなたは言う 多くの そのうち1は1に非常に近いです。明らかに、この手法は1に近い数字では機能しません。つまり、 xy 大きい。しかし、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つの暗黙のオフセットを持っていて、分数を操作するだけです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top