Datentypen Double Division ungenau

yeTh7

Mitglied
Hallo!

Scheinbar ist die Division von double Werten nicht ganz genau. Siehe:

System.out.println(0.84/0.07); // Output: 11.999999999999998

Ich vermute das hängt mit der Natur der Fließkommadarstellung zusammen. Dennoch schafft das jeder Taschenrechner auch. Gibt es einen Weg ein exaktes Resultat in Java zu erhalten?

Danke euch im Voraus!
 
Taschenrechner rechnen mit einer höheren Präzision als die, die sie für die Ausgabe benutzen. Intern erzeugt der Taschenrechner also natürlich auch Ungenauigkeiten, allerdings sieht man die nicht, da vorher für die Ausgabe gerundet wird.
Ein exaktes Resultat kannst du mit `java.math.BigDecimal` und dessen Operationen hinbekommen.
 
Also bei so Fragen würde ich empfehlen, doch einmal selbst in Google zu suchen. Da findet sich dann ganz schnell ganz viel an Möglichkeiten.

Und ganz nebenbei übt man so, selbst Lösungen zu finden.


Aber ja, es gibt ganz viele Möglichkeiten. Einfach mal ein paar aufgezeigt:
Java:
import java.text.DecimalFormat;
import java.text.NumberFormat;

public class Test {
    public static void main(String[] args) {
        System.out.printf("%.4f", 0.84/0.07);
        System.out.println();
        System.out.format("%.4f", 0.84/0.07);
        System.out.println();

        NumberFormat formatter = new DecimalFormat("#0.0000");
        System.out.println(formatter.format(0.84/0.07));

        System.out.println(String.format("%.4f", 0.84/0.07));
    }
}

Und siehe da: Alle liefern ein tolles Ergebnis:
Code:
12,0000
12,0000
12,0000
12,0000
 
Also bei so Fragen würde ich empfehlen, doch einmal selbst in Google zu suchen. Da findet sich dann ganz schnell ganz viel an Möglichkeiten.

Und ganz nebenbei übt man so, selbst Lösungen zu finden.

Hab schon selbst geschaut - meine Suchresultate waren aber entweder für meinen Fall unbrauchbar und unnötig komplex.

Für jemanden mit dem Wissen, ist es ein leichtes die Frage direkt zu beantworten. Recht herzlichen Dank! 🙂
 
Mein Hinweis mit dem Suchen war nicht böse gemeint. Bleib da immer dran! Mit der Zeit wirst Du auch immer schneller brauchbare Ergebnisse haben und wirst eine Ergebnisliste auch schneller nach möglicherweise hilfreichen Treffern durchsuchen können.

Daher würde ich Dir auch empfehlen, den Rat von Tobias aufzugreifen:
BigDecimal wird man nicht unbedingt brauchen - das habe ich etwas gezeigt. Aber da gibt es sehr interessante Methoden drin - die könnte man sich mal ansehen.

Ebenso könnte man sich die Dokumentation der Klassen, die ich verwendet habe, einmal ansehen (NumberFormat / DecimalFormat, String, ...)
 
Aber wenn man das auf 4 Nachkommastellen haben will, dann wird es halt doch etwas komplexer.
Da wird dann einiges deutlich komplexer. So Komma-Verschiebungen kann man machen, aber ob sich das wirklich immer lohnt?
 
Java:
double d1 = 0.84 / 0.07;
double d2 = Math.round(d1 * 10e5) / 10e5;
System.out.println(d2);
 
Genau das ist das Problem. Es ist eben nicht ganz so intuitiv und man muss mehr nachdenken. Sonst passieren so dumme Fehler.

Es ist eben 12 und nicht 0,12.

du Must also wirklich nachdenken um alle zahlen mit einem Faktor von 100 zu benutzen? Jetzt verstehe ich so langsam warum die heutige Software immer schlechter wird wenn ein Programmierer nich mal dazu in der Lage ist sowas Triviales voll automatisch im Kopf umzusetzen. Schreibt ihr für sowas Unit Tests?
 
du Must also wirklich nachdenken um alle zahlen mit einem Faktor von 100 zu benutzen?
Einer von euch beiden hat das falsche Ergebnis hingeschrieben - @JustNobody war es nicht.


Jetzt verstehe ich so langsam warum die heutige Software immer schlechter wird wenn ein Programmierer nich mal dazu in der Lage ist sowas Triviales voll automatisch im Kopf umzusetzen. Schreibt ihr für sowas Unit Tests?
So langsam verstehe ich, warum so viele Software so scheiße ist, wenn alle Programmierer meinen, sowas triviales im Kopf zu können und auf Unit-Tests zu verzichten.
 
du Must also wirklich nachdenken um alle zahlen mit einem Faktor von 100 zu benutzen? Jetzt verstehe ich so langsam warum die heutige Software immer schlechter wird wenn ein Programmierer nich mal dazu in der Lage ist sowas Triviales voll automatisch im Kopf umzusetzen. Schreibt ihr für sowas Unit Tests?
Ich würde für sowas Unit-Tests erwarten. Denn hier interagieren gleich zwei Faktoren miteinander:
* Konvertierung eines double/float in Int. Wie verhält sich das bei 1,999? Wird da 200 oder 199 raus? Und bei 1,991? Und bei 1,995? Erfolgt die Umwandlung nach der Multiplikation durch cast oder Rundung? Und durch welchen Runderungsmodus?
* Dann die Integer-Division. Was kommt bei 15/8 raus? 1 oder 2?


Also für trivial halte diese Methode nicht - Im Gegenteil, ich halte sie für eine gefährliche Bastellösung. Entweder ich rechne mit double und akzeptiere die ungenauigkeit durch die Fließkommaarithmetik und Runde ggf. am Ende. Oder ich nehme gleich BigDecimal, wenn ich es genau brauche.
 
du Must also wirklich nachdenken um alle zahlen mit einem Faktor von 100 zu benutzen? Jetzt verstehe ich so langsam warum die heutige Software immer schlechter wird wenn ein Programmierer nich mal dazu in der Lage ist sowas Triviales voll automatisch im Kopf umzusetzen. Schreibt ihr für sowas Unit Tests?

Einfache Mathematik erläutert.

Du hast a / b = c. Die Division sehen wir jetzt einfach mal als Bruch und erweitern ihn mit 100.
a / b = a * 100 / b * 100 = c

Das Ergebnis ist also gleich. Wenn Du a und b beide mit 100 multiplizierst, dann hast Du immer noch das gleiche Ergebnis. Man muss das Ergebnis also nicht durch 100 teilen!

Anders bei a + b = c. Hier können wir dann beide Seiten mit 100 multiplizieren. Dann hast du 100 * (a+b) = 100 c = 100a + 100b = 100c.

Also Du musst sehr gut aufpassen, was du wie machst. Und wie Du schön gezeigt hast: Da können sich mal eben Fehler einschleichen. Da mag man gerne argumentieren, dass ein "Guter" (Programmierer schreibe ich mal nicht .... evtl. Mathematiker? Aber egal was ... jemand, der gut ist auf jeden Fall) das doch auf Anhieb sieht.
==> Ja das ist richtig. Das habe ich ja vielleicht gezeigt. Ich habe Deinen Text gelesen und es sofort gesehen.

Aber dann sind wir wieder beim Thema Clean Code. Was soll das? Ein Guter Entwickler/Programmierer kann auch schlechten Code lesen!
==> Es sollen aber auch weniger gute Entwickler lesen können und jeder, der es liest, soll es in möglichst kurzer Zeit lesen können. Und das auch noch möglichst Fehlerfrei.

Nehmen wir einfach Deinen Fall: Immer wenn ich das Lese, muss ich kurz drüber nachdenken: Ist das richtig? Das kostet Zeit. Egal wie gut ich bin.
==> Klar, das wird gekapselt in eine Library, ich habe Unit Tests u.s.w. Da wird es dann praktkabel. Also es geht nicht um das generelle "mit Integern arbeiten". Das kann in vielen Szenarien Sinn machen ...
 
Ich würde für sowas Unit-Tests erwarten. Denn hier interagieren gleich zwei Faktoren miteinander:
* Konvertierung eines double/float in Int. Wie verhält sich das bei 1,999? Wird da 200 oder 199 raus? Und bei 1,991? Und bei 1,995? Erfolgt die Umwandlung nach der Multiplikation durch cast oder Rundung? Und durch welchen Runderungsmodus?
* Dann die Integer-Division. Was kommt bei 15/8 raus? 1 oder 2?

Das muss man ja definieren. Ich habe eine definierte Genauigkeit und mit der Rechne ich dann. Und dann wird da auch vieles klar definiert.

Und ganz klar: Wenn ich nur eine Genauigkeit von 2 Stellen haben will, dann kann die Genauigkeit von double ausreichen.

Aber wenn ich dann plötzlich ganz ganz viele Additionen / Subtraktionen habe und es genau sein muss, dann will ich das nicht mehr akzeptieren.
Beispiel Geld:
wenn ich Dir 1.000.000 Mal 0.01 Cent gebe, dann willst Du am Ende wirklich die 10.000 Euro haben. Keine 9,999,99 Euro oder so. Daher macht es hier z.B. Sinn, ganz klar Integer zu nutzen. Euro-Beträge z.B. in Cent zu speichen ist da eine gute Lösung.

Spätestens bei Buchhaltungs-Software fliegt einem das sonst schnell um die Ohren. Da brauche ich keine 1.000.000 Additionen. Da Multipliziere ich ja mit 0,19 bzw. teile durch 1,19 und so .... Ich schreibe eine Rechnung über 75,13 und da kommen 19% Umsatzsteuer drauf : 14,2747 Euro? Da muss dann meine Software natürlich ganz klar 14,27 Euro verbuchen. Schreibe ich zwei Rechnungen, dann soll das natürlich 28,54 und nicht 28,55 sein.

Also es geht nicht darum, die Idee komplett abzulehnen oder so. Man muss nur den Anwendungsfall genau sehen. Was brauche ich aus welchem Grund?

Und wenn ich der großer Number Cruncher bin, dann will ich Integer Operationen, alleine schon weil ich dann plötzlich ein vielfaches an Operationen erledigt bekomme. Wenn ich einen der Top Großrechner für 1 Tag belege oder nur für wenige Stunden macht ja evtl. doch einen Unterschied und könnte Bestandteil der Anforderungen sein 🙂
 
Also es geht nicht darum, die Idee komplett abzulehnen oder so. Man muss nur den Anwendungsfall genau sehen. Was brauche ich aus welchem Grund?

Und wenn ich der großer Number Cruncher bin, dann will ich Integer Operationen, alleine schon weil ich dann plötzlich ein vielfaches an Operationen erledigt bekomme. Wenn ich einen der Top Großrechner für 1 Tag belege oder nur für wenige Stunden macht ja evtl. doch einen Unterschied und könnte Bestandteil der Anforderungen sein 🙂
Ja, wobei ich dann aber weniger den Anwendungsfall sehe als "Ich habe Dezimalzahlen mit Nachkommastellen und bei Berechnungen wandle sie ich in Integer um" - sondern eher "Meine Daten werden generell in Integer verwaltet und wir bestimmte Exporte, wie Bildschirmdarstellungen werden sie in Dezimalzahlen mit Nachkommastellen umgewandelt."
 
Ja, wobei ich dann aber weniger den Anwendungsfall sehe als "Ich habe Dezimalzahlen mit Nachkommastellen und bei Berechnungen wandle sie ich in Integer um" - sondern eher "Meine Daten werden generell in Integer verwaltet und wir bestimmte Exporte, wie Bildschirmdarstellungen werden sie in Dezimalzahlen mit Nachkommastellen umgewandelt."
Ja diese Differenzierung erscheint mir auch logisch und richtig. Das hatte ich so noch nicht im Blickfeld.
 
Ich frage mich manchmal, warum Probleme, die Dinosaurier wie z.B. COBOL längst gelöst hatten, unter "modernen" Programmiersprachen wie Java wieder neu aufgelegt werden. Warum gibt es nicht einen primitiven BCD-Datentypen oder etwas vergleichbares?😕
 
Aus drei Gründen,
  1. Prozessoren/ALUs hantieren intern mit >=32-bit-Gleitkommazahlen,
  2. BCD-Code ist langsamer,
  3. wenn der Anwender eine höhere Genauigkeit braucht, kann er immernoch runden,
  4. nicht alles Neue ist gut.

Also, wie bereits mal angemerkt, ich würde BigDecimal empfehlen, selber runden empfehlen oder gar nur mit Ganzzahlen rechnen.
 
BigDecimal ist doch im Endeffekt die Lösung. Die bietet doch genau das, was BCD kann - eine genaue Darstellung einer Decimalzahl.
 
BigDecimal ist doch im Endeffekt die Lösung. Die bietet doch genau das, was BCD kann - eine genaue Darstellung einer Decimalzahl.
Stimmt schon. Aber ich hätte eben lieber einen primitiven Datentypen, auf den man die Operatoren der Grundrechenarten direkt anwenden kann. Durch Methodenaufrufe wird es ja schon bei sehr einfachen Berechnungen ziemlich hässlich.
 
Außerdem müsste es für die anderen Ganzzahldatentypen dann auch einen zusätzlichen, genauigkeitserhöhenden Datentypen geben, den es aber bereits gibt. Zudem hält sich Java an die Konventionen, was ich für begrüßenswert erachte. 😉
 
Stimmt schon. Aber ich hätte eben lieber einen primitiven Datentypen, auf den man die Operatoren der Grundrechenarten direkt anwenden kann. Durch Methodenaufrufe wird es ja schon bei sehr einfachen Berechnungen ziemlich hässlich.
Ja, das wäre schön. Vielleicht kommt sowas ja irgendwann. Mittlerweile hat Java ja doch was Fahrt aufgenommen, was neue Sprachfeatures angeht.
 
das ist einfach nur syntaktischer Zucker
Klar, braucht nicht jeder. Aber auch wenn es "nur" syntaktischer Zucker ist, würde ich statt so etwas:
Java:
    public static void main(String[] args) {
        BigDecimal ekPreis = new BigDecimal(43.99);
        BigDecimal margeInProzent = new BigDecimal(10);
        BigDecimal mwstInProzent = new BigDecimal(19);
        BigDecimal vkPreis = ekPreis.multiply(margeInProzent.divide(new BigDecimal(100)).add(BigDecimal.ONE)).multiply(mwstInProzent.divide(new BigDecimal(100)).add(BigDecimal.ONE));
        System.out.println(vkPreis);
    }
schon lieber so etwas schreiben können:
Code:
    public static void main(String[] args) {
        decimal(7,2) ekPreis = 43.99;
        decimal(5,2) margeInProzent = 10;
        decimal(3,1) mwstInProzent = 19;
        decimal(7,2) vkPreis = ekPreis * (1+margeInProzent/100) * (1+mwstInProzent/100);
        System.out.println(vkPreis);
    }
Im übrigen müsste man mit dem Argument "syntaktischer Zucker" auch integer, double, etc. abschaffen. Das hätte immerhin den Vorteil, dass man die Unterscheidung zwischen primitiven Datentypen und Klassen fallen lassen könnte. Die ist ja manchmal auch etwas unnatürlich.
den man selten braucht
Das mag für technisch-wissenschaftliche Anwendungen ja gelten, aber für kaufmännische Aufgaben benötigt man das dauernd und Gleitkommazahlen praktisch nie.
Außerdem müsste es für die anderen Ganzzahldatentypen dann auch einen zusätzlichen, genauigkeitserhöhenden Datentypen geben, den es aber bereits gibt.
Wieso das denn?
Zudem hält sich Java an die Konventionen, was ich für begrüßenswert erachte.
Welche Konventionen verletzt man denn, wenn man einen Festkommadatentyp anbietet?
 
In Java vermutlich eine Mischung aus fehlender Hardwareunterstützung, wenig Nachfrage und das nicht-verschwenden wollen von Op-Codes.
Das kann gut sein, wobei für mich das OpCode-Argument am schwersten wiegt. Hardwareunterstützung erscheint mir nicht so wesentlich, weil sich das ja komplett mittels Ganzzahl-Arithmetik abbilden lassen sollte. Über mangelnde Nachfrage würde ich mich auch wundern, aber vielleicht reichen Programmierer, die sich mit so banalem kaufmännischem Zeug beschäftigen, einfach keine Anforderungen ein.😉
 
Wir haben bei uns in der Firma einen Wrapper für BigDecimal, um den einfacher zu verwenden. Der hat die Eigenschaft, dass:
* 1.00 equals 1.0 ist
* Es gibt ein explizites NULL Object, mit dem man rechnen kann (Das erspart einen Unmengen an null-Checks). NULL + irgendwas ist halt wieder NULL etc.

Damit geht es eigentlich ganz gut und der zusätzliche Nutzen, wenn ich Operatoren nutzen könnte, wäre gering. Meist sind es nur einfache Rechenopererationen pro Methode, so dass es noch lesbar bleibt.
 

Neue Themen


Zurück
Oben