Behalten Sie die Präzision mit double in Java bei


Antworten:


151

Wie andere bereits erwähnt haben, möchten Sie wahrscheinlich die BigDecimalKlasse verwenden, wenn Sie eine genaue Darstellung von 11.4 wünschen.

Nun eine kleine Erklärung, warum dies geschieht:

Die floatund doubleprimitiven Typen in Java sind Gleitkommazahlen , wobei die Zahl als binäre Darstellung eines Bruchs und eines Exponenten gespeichert wird.

Insbesondere ist ein Gleitkommawert mit doppelter Genauigkeit wie der doubleTyp ein 64-Bit-Wert, wobei:

  • 1 Bit bezeichnet das Vorzeichen (positiv oder negativ).
  • 11 Bits für den Exponenten.
  • 52 Bits für die signifikanten Ziffern (der Bruchteil als Binärzahl).

Diese Teile werden kombiniert, um eine doubleDarstellung eines Wertes zu erzeugen .

(Quelle: Wikipedia: Doppelte Präzision )

Eine ausführliche Beschreibung des Umgangs mit Gleitkommawerten in Java finden Sie in Abschnitt 4.2.3: Gleitkommatypen, -formate und -werte der Java-Sprachspezifikation.

Die byte, char, int, longTypen sind Festkommazahlen, die genauen representions von Zahlen sind. Im Gegensatz zu Festkommazahlen können Gleitkommazahlen manchmal (sicher "meistens") keine genaue Darstellung einer Zahl zurückgeben. Dies ist der Grund, warum Sie 11.399999999999als Ergebnis von enden 5.6 + 5.8.

Wenn Sie einen genauen Wert benötigen, z. B. 1,5 oder 150,1005, sollten Sie einen der Festkommatypen verwenden, der die Zahl genau darstellen kann.

Wie bereits mehrfach erwähnt, verfügt Java über eine BigDecimalKlasse, die sehr große und sehr kleine Zahlen verarbeiten kann.

Aus der Java-API-Referenz für die BigDecimalKlasse:

Unveränderliche vorzeichenbehaftete Dezimalzahlen mit beliebiger Genauigkeit. Ein BigDecimal besteht aus einem nicht skalierten Ganzzahlwert mit beliebiger Genauigkeit und einer 32-Bit-Ganzzahlskala. Bei Null oder positiv ist die Skala die Anzahl der Stellen rechts vom Dezimalpunkt. Wenn negativ, wird der nicht skalierte Wert der Zahl mit zehn multipliziert mit der Potenz der Negation der Skala multipliziert. Der Wert der durch BigDecimal dargestellten Zahl ist daher (unscaledValue × 10 ^ -Skala).

Es gab viele Fragen zum Stapelüberlauf in Bezug auf Gleitkommazahlen und deren Genauigkeit. Hier ist eine Liste verwandter Fragen, die von Interesse sein können:

Wenn Sie wirklich auf die Details von Gleitkommazahlen eingehen möchten, schauen Sie sich an, was jeder Informatiker über Gleitkomma-Arithmetik wissen sollte .


3
Tatsächlich gibt es normalerweise 53 signifikante Bits, da die 1 vor dem "Dezimalpunkt" für alle außer denormalisierten Werte impliziert ist, was ein zusätzliches Maß an Genauigkeit ergibt. zB 3 wird als (1.) 1000 ... x 2 ^ 1 gespeichert, während 0,5 als (1.) 0000 ... x 2 ^ -1 gespeichert wird. Wenn der Wert denormalisiert ist (alle Exponentenbits sind Null), kann dies, und normalerweise werden weniger signifikante Stellen, z. B. 1 x 2 ^ -1030, als (0) 00000001 x 2 ^ -1022 gespeichert, so dass sieben signifikante Stellen maßstabsgetreu geopfert wurden.
Sarah Phillips

1
Es sollte beachtet werden, dass es zwar BigDecimalviel langsamer als doublein diesem Fall ist, aber nicht benötigt wird, da double 15 Dezimalstellen Genauigkeit hat, sondern nur gerundet werden muss.
Peter Lawrey

2
@PeterLawrey Es verfügt über 15 Dezimal - Stellen an Genauigkeit, wenn sie alle sind vor dem Punkt dezimal. Nach dem Dezimalpunkt kann alles passieren, da Dezimal- und Binärbrüche nicht miteinander vereinbar sind.
Marquis von Lorne

@EJP Sie haben Recht, es hat ungefähr 15 signifikante Stellen Genauigkeit. Es kann 16 sein, aber es ist sicherer anzunehmen, dass es 15 oder vielleicht 14 ist.
Peter Lawrey

Die Korrektur von @PeterLawrey EJP war auf meine Frage zurückzuführen: stackoverflow.com/questions/36344758/… Könnten Sie bitte erläutern , warum es nicht genau 15 ist und in welchen Situationen es 16 oder 14 sein kann?
Shivam Sinha

103

Wenn Sie beispielsweise eine doppelte Zahl eingeben, 33.33333333333333ist der Wert, den Sie erhalten, tatsächlich der am nächsten darstellbare Wert mit doppelter Genauigkeit, und zwar genau:

33.3333333333333285963817615993320941925048828125

Wenn Sie das durch 100 teilen, erhalten Sie:

0.333333333333333285963817615993320941925048828125

Dies ist auch nicht als Zahl mit doppelter Genauigkeit darstellbar. Daher wird es erneut auf den nächsten darstellbaren Wert gerundet. Dies ist genau:

0.3333333333333332593184650249895639717578887939453125

Wenn Sie diesen Wert ausdrucken, wird er erneut auf 17 Dezimalstellen gerundet , was Folgendes ergibt:

0.33333333333333326

114
Für jeden, der dies in Zukunft liest und sich wundert, warum die Antwort nichts mit der Frage zu tun hat: Ein Moderator hat beschlossen, die Frage, die ich (und andere) beantwortet hatten, mit dieser etwas anderen Frage zusammenzuführen.
Stephen Canon

Woher kennen Sie den genauen Doppelwert?
Michael Yaworski

@ Mikeyaworski en.wikipedia.org/wiki/Double-precision_floating-point_format Siehe Beispiele mit doppelter Genauigkeit
Jaydee

23

Wenn Sie nur Werte als Brüche verarbeiten möchten, können Sie eine Bruchklasse erstellen, die ein Zähler- und Nennerfeld enthält.

Schreiben Sie Methoden zum Addieren, Subtrahieren, Multiplizieren und Dividieren sowie eine toDouble-Methode. Auf diese Weise können Sie Floats während der Berechnungen vermeiden.

EDIT: Schnelle Implementierung,

public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}

1
Sicher numeratorund denominatorsollte ints sein? Warum sollten Sie Gleitkommapräzision wünschen?
Samir Talwar

Schätze, es ist nicht wirklich notwendig, aber es vermeidet das Umwandeln in die toDouble-Funktion, damit der Code besser gelesen wird.
Viral Shah

5
ViralShah: Es führt möglicherweise auch Gleitkommafehler beim Umgang mit mathematischen Operationen ein. Da es bei dieser Übung darum geht, genau das zu vermeiden, erscheint es ratsam, sie zu ändern.
Samir Talwar

Aus den oben von Samir Talwar genannten Gründen für die Verwendung von Ints anstelle von Doubles bearbeitet.
Viral Shah

3
Diese Implementierung von Brüchen hat Probleme, da sie nicht auf die einfachste Form reduziert werden. 2/3 * 1/2 geben 2/6, wo Sie wirklich wollen, dass die Antwort 1/3 ist. Idealerweise möchten Sie im Konstruktor die gcd von Zähler und Divisor finden und beide dadurch dividieren.
Salix alba

15

Beachten Sie, dass Sie das gleiche Problem haben würden, wenn Sie eine Dezimalarithmetik mit begrenzter Genauigkeit verwenden und 1/3 behandeln möchten: 0,333333333 * 3 ist 0,999999999, nicht 1,00000000.

Leider sind 5.6, 5.8 und 11.4 keine runden Zahlen in Binärform, da es sich um Fünftel handelt. Die Float-Darstellung von ihnen ist also nicht genau, genauso wie 0,3333 nicht genau 1/3 ist.

Wenn alle von Ihnen verwendeten Zahlen einmalige Dezimalstellen sind und Sie genaue Ergebnisse wünschen, verwenden Sie BigDecimal. Oder wie andere gesagt haben, wenn Ihre Werte wie Geld in dem Sinne sind, dass sie alle ein Vielfaches von 0,01 oder 0,001 oder so sind, dann multiplizieren Sie alles mit einer festen Potenz von 10 und verwenden Sie int oder long (Addition und Subtraktion sind trivial: auf Multiplikation achten).

Wenn Sie jedoch mit Binärdaten für die Berechnung zufrieden sind, aber nur Dinge in einem etwas freundlicheren Format ausdrucken möchten, versuchen Sie es mit java.util.Formatteroder String.format. Geben Sie in der Formatzeichenfolge eine Genauigkeit an, die geringer ist als die volle Genauigkeit eines Doppels. Bei 10 signifikanten Zahlen ist beispielsweise 11.399999999999 11.4, sodass das Ergebnis in Fällen, in denen das binäre Ergebnis einem Wert sehr nahe kommt, der nur wenige Dezimalstellen erfordert, fast genauso genau und besser lesbar ist.

Die Genauigkeit der Angabe hängt ein wenig davon ab, wie viel Mathematik Sie mit Ihren Zahlen durchgeführt haben. Je mehr Sie tun, desto mehr Fehler werden im Allgemeinen akkumuliert, aber einige Algorithmen akkumulieren sie viel schneller als andere (sie werden als "instabil" bezeichnet) im Gegensatz zu "stabil" in Bezug auf Rundungsfehler). Wenn Sie nur ein paar Werte hinzufügen, wird das Löschen von nur einer Dezimalstelle die Genauigkeit verbessern. Experiment.


3
Nein, verwenden Sie nicht double mit Geldwerten! Sie brauchen Präzision mit Geld, verwenden Sie stattdessen BigDecimal. Ansonsten ist Ihre Antwort gut. Wenn Sie Präzision benötigen, verwenden Sie BigDecimal. Wenn Präzision nicht so wichtig ist, können Sie float oder double verwenden.
MetroidFan2002

1
Die Frage besagt oder impliziert nicht mehr, dass es sich um Geld handelt. Ich sage ausdrücklich, BigDecimal oder ganze Zahlen für Geld zu verwenden. Was ist das Problem?
Steve Jessop

1
Und gleich "nicht doppelt für Geld verwenden" ist "nicht BigDecimal oder doppelt für Drittel verwenden". Aber manchmal besteht ein Problem in der Teilung. In diesem Fall sind alle Basen, die nicht durch alle Primfaktoren aller Nenner teilbar sind, ungefähr gleich schlecht.
Steve Jessop

1
.9999 = 1, wenn Ihre Genauigkeit weniger als 4 signifikante Stellen beträgt
Brian Leahy

9

Vielleicht möchten Sie die Verwendung der Java-Klasse java.math.BigDecimal untersuchen, wenn Sie wirklich Präzisionsmathematik benötigen. Hier ist ein guter Artikel von Oracle / Sun zum Fall BigDecimal . Sie können zwar niemals 1/3 darstellen, wie jemand erwähnt hat, aber Sie können die Leistung genau zu entscheiden , wie genau Sie das Ergebnis sein soll. setScale () ist dein Freund .. :)

Ok, weil ich im Moment viel zu viel Zeit habe, ist hier ein Codebeispiel, das sich auf Ihre Frage bezieht:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

und um meine neue Lieblingssprache, Groovy, anzuschließen, hier ist ein besseres Beispiel für dasselbe:

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333

5

Ich bin mir ziemlich sicher, dass Sie das zu einem dreizeiligen Beispiel hätten machen können. :) :)

Wenn Sie genaue Genauigkeit wünschen, verwenden Sie BigDecimal. Andernfalls können Sie Ints multipliziert mit 10 ^ verwenden, was auch immer Sie wollen.


5

Wie andere angemerkt haben, können nicht alle Dezimalwerte als binär dargestellt werden, da die Dezimalzahl auf Potenzen von 10 und die Binärzahl auf Potenzen von zwei basiert.

Wenn Präzision wichtig ist, verwenden Sie BigDecimal, aber wenn Sie nur eine benutzerfreundliche Ausgabe wünschen:

System.out.printf("%.2f\n", total);

Werde dir geben:

11.40


5

Sie können nicht, weil 7.3 keine endliche Darstellung in Binärform hat. Der nächstgelegene ist 2054767329987789/2 ** 48 = 7,3 + 1/1407374883553280.

Schauen Sie sich http://docs.python.org/tutorial/floatingpoint.html anWeitere Informationen finden . (Es ist auf der Python-Website, aber Java und C ++ haben das gleiche "Problem".)

Die Lösung hängt davon ab, was genau Ihr Problem ist:

  • Wenn Sie all diese Rauschziffern einfach nicht sehen möchten, korrigieren Sie die Formatierung Ihrer Zeichenfolge. Zeigen Sie nicht mehr als 15 signifikante Stellen an (oder 7 für float).
  • Wenn die Ungenauigkeit Ihrer Zahlen Dinge wie "if" -Anweisungen zerstört, sollten Sie if (abs (x - 7.3) <TOLERANCE) anstelle von if (x == 7.3) schreiben.
  • Wenn Sie mit Geld arbeiten, möchten Sie wahrscheinlich wirklich einen dezimalen Fixpunkt. Speichern Sie eine ganzzahlige Anzahl von Cent oder die kleinste Einheit Ihrer Währung.
  • (SEHR UNGLAUBLICH) Wenn Sie mehr als 53 signifikante Bits (15-16 signifikante Stellen) Genauigkeit benötigen, verwenden Sie einen hochpräzisen Gleitkommatyp wie BigDecimal.

7.3 hat möglicherweise keine endliche Darstellung in Binärform, aber ich bekomme sicher -7.3, wenn ich dasselbe in C ++
ausprobiere

2
falscher Benutzername: Nein, das tust du nicht. Es wird nur so angezeigt. Verwenden Sie das Format "% .17g" (oder noch besser "% .51g"), um die tatsächliche Antwort zu sehen.
dan04

4
private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}

3

Verwenden Sie java.math.BigDecimal

Doubles sind intern binäre Brüche, daher können sie manchmal keine Dezimalbrüche auf die exakte Dezimalzahl darstellen.


1
-1 für die blinde Empfehlung von BigDecimal. Wenn Sie keine Dezimalarithmetik benötigen (dh wenn Sie mit Geld rechnen), hilft Ihnen BigDecimal nicht weiter. Es werden nicht alle Ihre Gleitkommafehler behoben: Sie müssen sich immer noch mit 1/3 * 3 = 0,999999999999999999999999999999 und sqrt (2) ** 2 = 1,9999999999999999999999999999999 befassen. Darüber hinaus bringt BigDecimal eine enorme Geschwindigkeitsstrafe mit sich. Schlimmer noch, da Java keine Operatorüberladung aufweist, müssen Sie Ihren gesamten Code neu schreiben.
Dan04

2
@ dan04 - Wenn Sie mit Geld rechnen, warum sollten Sie eine schwebende Darstellung verwenden, die den inhärenten Fehler kennt? Da es keinen Bruchteil von Cent gibt, können Sie Dezimalzahlen verwenden und Cent berechnen, anstatt einen ungefähren Dollarbetrag zu verwenden. Sie haben einen genauen Centbetrag. Wenn Sie wirklich den Bruchteil des Cent wollen, verwenden Sie aa long und berechnen Sie Tausende von Cent. Darüber hinaus erwähnte das OP keine irrationalen Zahlen, alles, worüber er sich Sorgen machte, war die Hinzufügung. Lesen Sie den Beitrag sorgfältig durch und verstehen Sie das Problem, bevor Sie antworten. Dies könnte Ihnen Verlegenheit ersparen.
Newtopian

3
@Newtopian: Ich muss mich nicht schämen. Die OP hat KEINE Erwähnung von Geld, und auch keine Anzeichen dafür , dass sein Problem jegliche inhärente decimalness hat.
dan04

@ dan04 - Nein, das OP hat nicht ... SIE haben es getan und blind aus dem Zusammenhang heraus eine Meinung abgegeben, was angesichts der geringen Menge an Details höchstwahrscheinlich eine vollkommen akzeptable Antwort war
Newtopian

2

Multiplizieren Sie alles mit 100 und lagern Sie es in Cent.


2
@Draemon - schau dir den Beitrag vor der letzten Bearbeitung an - all das Zeug von "purchaseTotal" und "calcGST" und "calcPST" sieht für mich nach Geld aus.
Paul Tomblin

2

Computer speichern Zahlen in Binärform und können Zahlen wie 33.333333333 oder 100.0 nicht genau darstellen. Dies ist eines der kniffligen Dinge bei der Verwendung von Doppel. Sie müssen die Antwort nur abrunden, bevor Sie sie einem Benutzer anzeigen. Glücklicherweise benötigen Sie in den meisten Anwendungen sowieso nicht so viele Dezimalstellen.


Ich mache einige Quotenberechnungen, für die ich die höchstmögliche Präzision bevorzugen würde. Aber ich verstehe, dass es Einschränkungen gibt
Aly

2

Gleitkommazahlen unterscheiden sich von reellen Zahlen darin, dass es für jede gegebene Gleitkommazahl eine nächsthöhere Gleitkommazahl gibt. Gleich wie ganze Zahlen. Es gibt keine ganze Zahl zwischen 1 und 2.

Es gibt keine Möglichkeit, 1/3 als Float darzustellen. Es gibt einen Schwimmer darunter und einen Schwimmer darüber, und es gibt einen gewissen Abstand zwischen ihnen. Und 1/3 ist in diesem Raum.

Apfloat für Java behauptet, mit Gleitkommazahlen mit beliebiger Genauigkeit zu arbeiten, aber ich habe es nie verwendet. Wahrscheinlich einen Blick wert. http://www.apfloat.org/apfloat_java/

Eine ähnliche Frage wurde hier vor der hochpräzisen Java-Gleitkomma-Bibliothek gestellt


1

Doubles sind Annäherungen an die Dezimalzahlen in Ihrer Java-Quelle. Sie sehen die Konsequenz der Nichtübereinstimmung zwischen dem Double (das ein binär codierter Wert ist) und Ihrer Quelle (die dezimal codiert ist).

Java erzeugt die nächste binäre Näherung. Mit java.text.DecimalFormat können Sie einen besser aussehenden Dezimalwert anzeigen.


1

Verwenden Sie ein BigDecimal. Sie können sogar Rundungsregeln angeben (z. B. ROUND_HALF_EVEN, mit denen statistische Fehler minimiert werden, indem auf den geraden Nachbarn gerundet wird, wenn beide den gleichen Abstand haben, dh sowohl 1,5 als auch 2,5 auf 2 runden).


1

Kurze Antwort: Verwenden Sie immer BigDecimal und stellen Sie sicher, dass Sie den Konstruktor mit dem String- Argument verwenden, nicht das doppelte.

Zurück zu Ihrem Beispiel: Der folgende Code gibt 11.4 aus, wie Sie möchten.

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}

0

Schauen Sie sich BigDecimal an, es behandelt Probleme, die mit einer solchen Gleitkomma-Arithmetik zu tun haben.

Der neue Anruf würde folgendermaßen aussehen:

term[number].coefficient.add(co);

Verwenden Sie setScale (), um die Anzahl der zu verwendenden Dezimalstellen festzulegen.


0

Warum nicht die round () -Methode aus dem Mathematikunterricht verwenden?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4

0

Wenn Sie keine andere Wahl haben, als doppelte Werte zu verwenden, können Sie den folgenden Code verwenden.

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}

-1

Verschwenden Sie Ihren Efford nicht mit BigDecimal. In 99,99999% der Fälle benötigen Sie es nicht. java Doppeltyp ist von cource annähernde , jedoch in fast allen Fällen ist es hinreichend präzise. Beachten Sie, dass bei der 14. signifikanten Stelle ein Fehler vorliegt.Das ist wirklich vernachlässigbar!

Um eine schöne Ausgabe zu erhalten, verwenden Sie:

System.out.printf("%.2f\n", total);

2
Ich denke, er ist besorgt über die Ausgabe, nicht über die numerische Genauigkeit. und BigDecimal wäre keine Hilfe, wenn Sie z. durch drei teilen. Es kann sogar noch schlimmer werden ...
Maciek D.

Sie sollten niemals niemals Gleitkomma für Geld verwenden. Ich habe gesehen, dass ein Auftragnehmer, der trotz dieser Anweisung gegen diese Regel verstoßen hat, einer umfassenden Überarbeitung unterzogen wurde.
Marquis von Lorne
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.