Frage

Ich habe eine Reihe schwimmender Punktzahlen (Java -Doppel), von denen die meisten sehr nahe bei 1 sind, und ich muss sie als Teil einer größeren Berechnung miteinander multiplizieren. Ich muss das tun viel.

Das Problem ist, dass Java -Doppel jedoch kein Problem mit einer Zahl wie:

0.0000000000000000000000000000000001 (1.0E-34)

Sie können so etwas nicht darstellen wie:

1.0000000000000000000000000000000001

Folglich verliere ich schnell Präzision (die Grenze scheint für Javas Doppel um 1.0000000000001 zu liegen).

Ich habe darüber nachgedacht, nur die Zahlen mit 1 abgezogen zu haben, also würde 1.0001 als 0,0001 gespeichert - aber das Problem ist, dass ich sie wieder miteinander multiplizieren muss, und zu diesem Zeitpunkt verliere ich Präzision.

Um dies zu beheben, könnte ich BigDecimals verwenden, um die Berechnung durchzuführen (in BigDecimal konvertieren, 1.0 hinzufügen, dann multiplizieren) und dann anschließend wieder in Doppelkonvertierungen zurückkehren, aber ich habe ernsthafte Bedenken hinsichtlich der Auswirkungen auf die Leistung.

Kann jemand einen Weg sehen, dies zu tun, der BigDecimal vermeidet?

Für Klarheit bearbeiten: Dies gilt für einen groß angelegten kollaborativen Filter, der einen Algorithmus zur Optimierung des Gradientenabstiegs verwendet. Genauigkeit ist ein Problem, da der kollaborative Filter häufig mit sehr kleinen Zahlen zu tun hat (z.

Geschwindigkeit ist ein Problem, da der kollaborative Filter auf zehn Millionen Datenpunkten geschult werden muss, wenn nicht sogar mehr.

War es hilfreich?

Lösung

Yep: weil

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

In Ihrem Fall, x und y sind sehr klein, also x*y wird sein weit kleiner - viel zu klein, um die Ergebnisse Ihrer Berechnung zu beeinflussen. Soweit Sie sich betrifft,

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

Dies bedeutet, dass Sie die Zahlen mit 1 subtrahiert und anstatt sich zu multiplizieren, fügen Sie sie einfach hinzu. Solange die Ergebnisse immer viel weniger als 1 sind, sind sie nahe genug an den mathematisch präzisen Ergebnissen, die Sie für den Unterschied nicht interessieren.

BEARBEITEN: Gerade bemerkt: du sagst die meisten von ihnen sind sehr nahe bei 1. Offensichtlich funktionieren diese Technik nicht für Zahlen, die nicht in der Nähe von 1 sind - das heißt, wenn x und y sind groß. Aber wenn man groß ist und einer klein ist, kann es immer noch funktionieren; Sie kümmern sich nur um die Größe des Produkts x*y. (Und wenn beide Zahlen nicht in der Nähe von 1 sind, können Sie einfach reguläre Java verwenden double Multiplikation...)

Andere Tipps

Vielleicht könnten Sie Logarithmen verwenden?

Logarithmen reduzieren die Multiplikation bequem auf Addition.

Um sich um den anfänglichen Präzisionsverlust zu kümmern, gibt es auch die Funktion log1p (zumindest in C/C ++), die log (1+x) ohne Präzisionsverlust zurückgibt. (EG LOG1P (1E-30) gibt 1E-30 für mich zurück)

Dann können Sie expm1 verwenden, um den Dezimalbestand des tatsächlichen Ergebniss zu erhalten.

Ist diese Art von Situation nicht genau das, wofür Bigdecimal ist?

Bearbeitet, um hinzuzufügen:

"Gemäß dem zweitletzten Absatz würde ich es aus Leistungsgründen, wenn möglich, BigDecimals zu vermeiden." - Vernunft

"Frühgeborene Optimierung ist die Wurzel des gesamten Bösen" - Knuth

Es gibt eine einfache Lösung, die praktisch für Ihr Problem bestellt wird. Sie sind besorgt, dass es möglicherweise nicht schnell genug ist, also möchten Sie etwas Kompliziertes tun, was Sie denken wird schneller sein. Das Knuth -Zitat wird manchmal überbeansprucht, aber genau dies ist die Situation, gegen die er warnte. Schreiben Sie es auf einfache Weise. Probier es aus. Profile es. Sehen Sie, ob es zu langsam ist. Wenn es so ist dann Überlegen Sie sich über Möglichkeiten, um es schneller zu machen. Fügen Sie nicht all diesen zusätzlichen komplexen, fehlanfälligen Code hinzu, wenn Sie wissen, dass dies erforderlich ist.

Abhängig davon, woher die Zahlen stammen und wie Sie sie verwenden, möchten Sie möglicherweise Rationals anstelle von Floats verwenden. Nicht die richtige Antwort für alle Fälle, aber wenn es ist Die richtige Antwort, es gibt wirklich keine andere.

Wenn Rationals nicht passen, würde ich die Antwort der Logarithmen unterstützen.

Bearbeiten Sie als Antwort auf Ihre Bearbeitung:

Wenn Sie mit Zahlen zu tun haben, die niedrige Antwortraten darstellen, tun Sie, was Wissenschaftler tun:

  • Stellen Sie sie als Überschuss / Defizit dar (normalisieren Sie den 1,0 -Teil)
  • Skalieren Sie sie. Denken Sie an "Parts pro Million" oder was auch immer angemessen ist.

Dadurch werden Sie mit angemessenen Zahlen für Berechnungen zu tun.

Es ist erwähnenswert, dass Sie die Grenzen Ihrer Hardware und nicht Java testen. Java verwendet den 64-Bit-Schwimmpunkt in Ihrer CPU.

Ich schlage vor, Sie testen die Leistung von BigDecimal, bevor Sie davon ausgehen, dass es für Sie nicht schnell genug ist. Mit BigDecimal können Sie immer noch Zehntausende von Berechnungen pro Sekunde durchführen.

Wie David betont, können Sie einfach die Aussätze hinzufügen.

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

Es erscheint jedoch riskant, die letzte Amtszeit auszugeben. Nicht. Versuchen Sie beispielsweise Folgendes:

x = 1e-8 y = 2e-6 z = 3E-7 W = 4E-5

Was ist (1+x)(1+y)(1+z)*(1+w)? In doppelter Präzision bekomme ich:

(1+x)(1+y)(1+z)*(1+w)

Ans =

      1.00004231009302

Sehen Sie jedoch, was passiert, wenn wir nur die einfache additive Näherung durchführen.

1+(x+y+z+w)

Ans =

            1.00004231

Wir haben die möglicherweise wichtigen Bits mit geringer Ordnung verloren. Dies ist nur ein Problem, wenn einige der Unterschiede von 1 im Produkt mindestens SQRT (EPS) sind, wobei EPS die Präzision ist, in der Sie arbeiten.

Versuchen Sie es stattdessen:

f = @(u, v) u + v + u*v;

Ergebnis = f (x, y);

Ergebnis = f (Ergebnis, z);

Ergebnis = f (Ergebnis, w);

1+Ergebnis

Ans =

      1.00004231009302

Wie Sie sehen können, bringt wir uns zum Doppelpräzisionsergebnis zurück. Tatsächlich ist es etwas genauer, da der interne Wert des Ergebniss 4.23100930230249E-05 beträgt.

Wenn Sie wirklich die Präzision benötigen, müssen Sie so etwas wie BigDecimal verwenden, auch wenn es langsamer als doppelt ist.

Wenn Sie die Präzision nicht wirklich brauchen, könnten Sie vielleicht mit Davids Antwort gehen. Aber selbst wenn Sie viel Multiplikationen verwenden, ist es möglicherweise eine vorzeitige Optimierung, daher könnte Bigdecimal trotzdem der richtige Weg sein

Wenn Sie sagen, "die meisten sind sehr nahe bei 1", wie viele genau?

Vielleicht könnten Sie in all Ihren Zahlen einen impliziten Versatz von 1 haben und einfach mit den Brüchen arbeiten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top