2

浮動小数点数 (Java double) がたくさんありますが、そのほとんどは 1 に非常に近く、より大きな計算の一部としてそれらを乗算する必要があります。私はこれをたくさんする必要があります。

問題は、Java の double では次のような数値に問題がないことです。

0.0000000000000000000000000000000001 (1.0E-34)

次のようなものを表すことはできません。

1.0000000000000000000000000000000001

この結果、私は急速に精度を失います (制限は、Java の double では約 1.000000000000001 のようです)。

たとえば、1.0001 は 0.0001 として格納されますが、問題は、それらを再度乗算するには 1 を加算する必要があり、この時点で精度が失われることです。

これに対処するには、BigDecimals を使用して計算 (BigDecimal に変換し、1.0 を加算してから乗算) を実行し、その後 double に変換することができますが、これがパフォーマンスに与える影響について深刻な懸念があります。

BigDecimal を使用しないでこれを行う方法を誰でも見ることができますか?

明確にするために編集: これは、勾配降下最適化アルゴリズムを採用する大規模な共同フィルター用です。多くの場合、共同フィルターは非常に小さな数値 (商品の広告をクリックする確率など、1000 分の 1 または 10000 分の 1 など) を処理するため、精度が問題になります。

コラボレーティブ フィルターは、それ以上ではないにしても、数千万のデータ ポイントでトレーニングする必要があるため、速度が問題になります。

4

8 に答える 8

12

うん:だから

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

あなたの場合、xy非常に小さいので、はるかにx*y小さくなります - 小さすぎて計算の結果に影響を与えません。ですから、あなたに関する限り、

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

これは、1 を引いた数値を格納し、乗算する代わりに足すだけでよいことを意味します。結果が常に 1 よりはるかに小さい限り、それらは数学的に正確な結果に十分近いため、違いを気にする必要はありません。

EDIT:ちょうど気づいた:あなたはそれらのほとんどが1に非常に近いと言います。明らかに、この手法は1に近くない数、つまりxyが大きい場合には機能しません。ただし、一方が大きく、一方が小さい場合でも、機能する可能性があります。製品の大きさだけを気にしますx*y。(両方の数値が 1 に近くない場合は、通常の Javadouble乗算を使用できます...)

于 2009-04-04T23:03:55.450 に答える
11

おそらく、対数を使用できますか?

対数は、掛け算を足し算に簡便に還元します。

また、初期の精度損失を処理するために、関数 log1p (少なくとも C/C++ には存在します) があり、精度損失なしで log(1+x) を返します。(たとえば、log1p(1e-30) は 1e-30 を返します)

次に、expm1 を使用して、実際の結果の小数部分を取得できます。

于 2009-04-04T23:11:03.437 に答える
3

この種の状況はまさに BigDecimal の目的ではないでしょうか?

追加するために編集:

「最後から 2 番目の段落によると、パフォーマンス上の理由から、可能であれば BigDecimals を避けたいと思います。」- 正気

「時期尚早の最適化は諸悪の根源です」 - クヌース

あなたの問題に対して実際にオーダーメイドで作られた簡単な解決策があります。あなたはそれが十分に速くないのではないかと心配しているので、より速くなると思われる複雑なことをしたいと思っています。クヌースの名言は時々乱用されますが、これはまさに彼が警告していた状況です。簡単な方法で書きます。試して。プロファイリングします。遅すぎるかどうかを確認します。もしそうなら、それを速くする方法を考え始めてください。必要であることがわかるまで、このような複雑でバグが発生しやすいコードをすべて追加しないでください。

于 2009-04-04T23:04:17.900 に答える
1

David が指摘しているように、オフセットを追加するだけです。

(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;

結果 = f(x,y);

結果 = f(結果、z);

結果 = f(結果、w);

1+結果

ans =

      1.00004231009302

ご覧のとおり、これにより倍精度の結果に戻ります。実際、result の内部値は 4.23100930230249e-05 であるため、もう少し正確です。

于 2009-04-07T15:32:49.940 に答える
1

数値がどこから来て、どのように使用しているかによっては、浮動小数点数の代わりに有理数を使用したい場合があります。すべての場合に正しい答えではありませんが、それ正しい場合は他にありません。

合理性が合わない場合は、対数の答えを支持します。

編集に応じて編集します。

低い回答率を表す数値を扱っている場合は、科学者が行うことを行います。

  • 過不足で表す(1.0の部分を正規化)
  • それらをスケーリングします。「100万分の1」または適切なもので考えてください。

これにより、計算に妥当な数値を扱うことができます。

于 2009-04-04T23:54:06.767 に答える
1

Java ではなく、ハードウェアの限界をテストしていることに注意してください。Java は、CPU で 64 ビット浮動小数点を使用します。

BigDecimal の速度が十分でないと考える前に、BigDecimal のパフォーマンスをテストすることをお勧めします。BigDecimal を使用すると、1 秒あたり数万回の計算を行うことができます。

于 2009-04-05T08:24:28.723 に答える
0

本当に精度が必要な場合は、Double より遅くても、BigDecimal のようなものを使用する必要があります。

精度が本当に必要ない場合は、おそらくDavidの答えを使用できます。ただし、乗算を頻繁に使用する場合でも、時期尚早の最適化である可能性があるため、とにかく BIgDecimal を使用することをお勧めします

于 2009-04-04T23:11:53.123 に答える
0

「そのほとんどが 1 に非常に近い」と言うとき、正確にはいくつですか?

おそらく、すべての数値に 1 の暗黙的なオフセットを設定して、分数を操作することができます。

于 2009-04-05T00:02:36.543 に答える