it-swarm.com.de

So lösen Sie ein Java Rounding Double-Problem

Scheint, als würde die Subtraktion ein Problem auslösen und der resultierende Wert ist falsch.

double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;

78,75 = 787,5 · 10,0/100d

double netToCompany = targetPremium.doubleValue() - tempCommission;

708,75 = 787,5 - 78,75

double dCommission = request.getPremium().doubleValue() - netToCompany;

877,8499999999999 = 1586,6 - 708,75

Der resultierende erwartete Wert wäre 877,85.

Was ist zu tun, um die korrekte Berechnung sicherzustellen?

73
Patrick

Um die Genauigkeit der Gleitkommaarithmetik zu steuern, sollten Sie Java.math.BigDecimal verwenden. Lesen Sie Die Notwendigkeit von BigDecimal von John Zukowski für weitere Informationen.

In Ihrem Beispiel wäre die letzte Zeile mit BigDecimal wie folgt.

import Java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Daraus ergibt sich die folgende Ausgabe.

877.85 = 1586.6 - 708.75
93
Eric Weilnau

Wie in den vorherigen Antworten angegeben, ist dies eine Folge der Gleitkomma-Arithmetik.

Wie in einem vorherigen Poster vorgeschlagen, verwenden Sie bei numerischen Berechnungen Java.math.BigDecimal.

Es gibt jedoch ein Problem bei der Verwendung von BigDecimal. Wenn Sie vom doppelten Wert in einen BigDecimal konvertieren, haben Sie die Wahl, einen neuen BigDecimal(double) -Konstruktor oder die statische Factory-Methode BigDecimal.valueOf(double) zu verwenden. Verwenden Sie die statische Factory-Methode.

Der Doppelkonstruktor konvertiert die gesamte Genauigkeit des double in ein BigDecimal, während die statische Factory sie effektiv in ein String konvertiert und dieses dann in ein BigDecimal konvertiert. .

Dies ist relevant, wenn Sie auf subtile Rundungsfehler stoßen. Eine Zahl könnte als .585 angezeigt werden, aber intern ist ihr Wert '0.58499999999999996447286321199499070644378662109375'. Wenn Sie den Konstruktor BigDecimal verwenden, erhalten Sie eine Zahl, die NICHT 0,585 entspricht, während die statische Methode einen Wert von 0,585 ergibt.

 doppelter Wert = 0,585; 
 System.out.println (neuer BigDecimal (Wert)); 
 System.out.println (BigDecimal.valueOf (Wert)); 

auf meinem system gibt

 0.58499999999999996447286321199499070644378662109375 
 0.585 
65
Johann Zacharee

Ein anderes Beispiel:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

Verwenden Sie stattdessen BigDecimal.

BEARBEITEN:

Nur um darauf hinzuweisen, dass dies kein 'Java'-Rundungsproblem ist. Andere Sprachen zeigen ein ähnliches (wenn auch nicht notwendigerweise konsistentes) Verhalten. Java garantiert diesbezüglich zumindest ein konsistentes Verhalten.

10
toolkit

Ich würde das obige Beispiel wie folgt ändern:

import Java.math.BigDecimal;

BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Auf diese Weise vermeiden Sie die Gefahr, dass Sie anfänglich Zeichenfolgen verwenden. Eine andere Alternative:

import Java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Ich denke, diese Optionen sind besser als die Verwendung von Doppel. In Webapps beginnen Zahlen sowieso als Zeichenketten.

8
dog

Jedes Mal, wenn Sie mit Doppelwerten rechnen, kann dies passieren. Dieser Code würde Ihnen 877,85 geben:

doppelte Antwort = Math.round (dCommission * 100000)/100000.0;

7
Steve Klabnik

Speichern Sie die Anzahl der Cents und nicht die Dollars, und formatieren Sie sie bei der Ausgabe nur in Dollars. Auf diese Weise können Sie eine Ganzzahl verwenden, die nicht unter Genauigkeitsproblemen leidet.

4
tloach

Das ist eine lustige Angelegenheit.

Die Idee hinter Timons Antwort ist, dass Sie ein Epsilon angeben, das die kleinste Genauigkeit darstellt, die ein legaler Double haben kann. Wenn Sie in Ihrer Anwendung wissen, dass Sie niemals eine Genauigkeit unter 0,00000001 benötigen, reicht das, was er vorschlägt, aus, um ein genaueres Ergebnis zu erzielen, das der Wahrheit sehr nahe kommt. Nützlich in Anwendungen, in denen sie ihre maximale Genauigkeit im Voraus kennen (zum Beispiel zur Finanzierung von Währungspräzisionen usw.)

Das grundlegende Problem beim Versuch, es abzurunden, besteht jedoch darin, dass Sie, wenn Sie durch einen Faktor dividieren, um es neu zu skalieren, tatsächlich eine weitere Möglichkeit für Präzisionsprobleme einführen. Jede Manipulation von Doppeln kann zu Ungenauigkeiten mit unterschiedlicher Häufigkeit führen. Vor allem, wenn Sie versuchen, auf eine sehr wichtige Ziffer zu runden (Ihre Operanden sind also <0), wenn Sie beispielsweise Folgendes mit Timons-Code ausführen:

System.out.println(round((1515476.0) * 0.00001) / 0.00001);

Wird darin enden, dass 1499999.9999999998 wo das Ziel hier ist, mit den Einheiten von 500000 zu runden (d. h. wir wollen 1500000)

Der einzige Weg, um sicherzugehen, dass Sie die Ungenauigkeit beseitigt haben, besteht darin, ein BigDecimal zu verwenden, um die Skalierung zu verringern. z.B.

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());

Wenn Sie eine Mischung aus der Epsilon-Strategie und der BigDecimal-Strategie verwenden, können Sie Ihre Präzision genau steuern. Die Idee, das Epsilon zu sein, bringt Sie sehr nahe, und dann beseitigt das BigDecimal alle Ungenauigkeiten, die durch eine spätere Neuskalierung verursacht werden. Die Verwendung von BigDecimal verringert jedoch die erwartete Leistung Ihrer Anwendung.

Ich wurde darauf hingewiesen, dass der letzte Schritt der Verwendung von BigDecimal zum erneuten Skalieren in einigen Anwendungsfällen nicht immer erforderlich ist, wenn Sie feststellen können, dass es keinen Eingabewert gibt, den die endgültige Unterteilung erneut zu einem Fehler führen kann. Derzeit weiß ich nicht, wie ich das richtig bestimmen soll. Wenn also jemand weiß, wie, würde ich mich freuen, davon zu hören.

3
Doug

Siehe Antworten auf diese Frage . Im Wesentlichen ist das, was Sie sehen, eine natürliche Folge der Verwendung von Gleitkomma-Arithmetik.

Sie können eine beliebige Genauigkeit (signifikante Stellen Ihrer Eingaben?) Auswählen und Ihr Ergebnis darauf abrunden, wenn Sie dies möchten.

3
Adam Bellaire

Verwenden Sie noch besser JScience , da BigDecimal ziemlich eingeschränkt ist (z. B. keine sqrt-Funktion).

double dCommission = 1586.6 - 708.75;
System.out.println(dCommission);
> 877.8499999999999

Real dCommissionR = Real.valueOf(1586.6 - 708.75);
System.out.println(dCommissionR);
> 877.850000000000
2
Tomasz

Der bislang eleganteste und effizienteste Weg, dies in Java zu tun:

double newNum = Math.floor(num * 100 + 0.5) / 100;
2
Roman Kagan
double rounded = Math.rint(toround * 100) / 100;
1
Denis