Heim  >  Artikel  >  Java  >  Ausführliche Erläuterung von Beispielen für Float- und Double-Precision-Verlust in Java

Ausführliche Erläuterung von Beispielen für Float- und Double-Precision-Verlust in Java

零下一度
零下一度Original
2017-06-28 11:38:431932Durchsuche

1.Der Wertebereich von int, float, long, double in Java

[java] view plain copy

public class TestOutOfBound {

public static void main ( String[] args) {

System.out.println(Integer.MAX_VALUE-(-Integer.MAX_VALUE)); //Speicherüberlauf

System.out . println(Integer.MAX_VALUE); //2 hoch 31 -1, 10 Ziffern, etwa 2 Milliarden, was für Geld möglicherweise nicht ausreicht

System.out.println(Integer.MIN_VALUE) ; //Negativ 2 hoch 31

System.out.println(Long.MAX_VALUE); //2 hoch 64 - 1,19 Ziffern, was sehr groß ist und für Geld verwendet werden kann . Oben

System.out.println(Long.MIN_VALUE); //Negativ 2 hoch 64

System.out.println(Float.MAX_VALUE); Potenzquadrat -1, 38 Ziffern, doppelt so lang. Dies wird hauptsächlich für einfache mathematisch präzise Operationen verwendet.

System.out.println(Float.MIN_VALUE); //2 hoch -149

System.out.println(Double.MAX_VALUE); //2 hoch 1024 Ziffern, was dem Zehnfachen der Anzahl der Gleitkommastellen entspricht. Wird hauptsächlich für komplexe Operationen und astronomische Operationen verwendet >

System.out.println(Double.MIN_VALUE); //2 hoch -1074

}

}

2.Float- und Double-Precision-Loss-Problem

Beispiel:

[java] view plain copy

Beispiel: double result = 1.0 - 0.9;

Dieses Ergebnis kennen wir natürlich alle: 0,09999999999999998

Warum tritt dieses Problem in Java und anderen Computersprachen auf? Frage:
Die Float- und Double-Typen sind hauptsächlich für wissenschaftliche Berechnungen und technische Berechnungen konzipiert. Sie führen binäre Gleitkomma-Arithmetik durch, die sorgfältig entwickelt wurde, um schnelle Näherungsberechnungen mit hoher Genauigkeit über einen weiten Zahlenbereich zu ermöglichen. Sie liefern jedoch keine völlig genauen Ergebnisse und sollten nicht für exakte Berechnungen verwendet werden. Die Typen „Float“ und „Double“ sind besonders für Geldoperationen ungeeignet, da es für einen Float oder Double unmöglich ist, 0,1 oder eine andere negative Potenz von 10 genau darzustellen (tatsächlich ist der Grund sehr einfach. Kann es in der Dezimalzahl genau dargestellt werden?). System? Was ist mit 1/3? Das gleiche Binärsystem kann 1/10 nicht genau darstellen.

Gleitkommaoperationen sind selten genau und es treten Fehler auf, solange sie den Bereich überschreiten, den die Genauigkeit darstellen kann. Der Fehler tritt häufig nicht aufgrund der Größe der Zahl auf, sondern aufgrund der Genauigkeit der Zahl. Daher liegen die erzielten Ergebnisse nahe an den gewünschten Ergebnissen, sind diesen jedoch nicht gleich. Seien Sie besonders vorsichtig, wenn Sie Float und Double für exakte Operationen verwenden.

Lassen Sie uns nun im Detail analysieren, warum Gleitkommaoperationen zu Präzisionsverlusten führen.



[java] view plain copy

Zuerst müssen wir die folgenden zwei Fragen klären:

(1) So konvertieren Sie dezimale Ganzzahlen in Binärzahlen

Der Algorithmus ist sehr einfach. Beispielsweise wird 11 als Binärzahl ausgedrückt:

11/2=5 mehr als 1 5/2=2 mehr als 1

2/2 = 1 übrig 0

1/2 = 0 余 1

0 Ende Die binäre Darstellung von 11 ist (von unten nach oben) : 1011

Eines ist hier zu erwähnen: Solange das Ergebnis nach der Division 0 ist, ist es vorbei. Denken Sie darüber nach, dass alle ganzen Zahlen durch 2 geteilt werden, was definitiv 0 ergibt. Mit anderen Worten: Wird der Algorithmus, der alle Ganzzahlen in Binärzahlen umwandelt, in einer Endlosschleife weiterlaufen? Absolut nicht, ganze Zahlen können binär immer genau dargestellt werden, Dezimalzahlen jedoch nicht unbedingt.

(2) So konvertieren Sie Dezimalzahlen in Binärzahlen

Der Algorithmus besteht darin, mit 2 zu multiplizieren, bis keine Dezimalstellen mehr vorhanden sind. Beispielsweise wird 0,9 als Binärzahl ausgedrückt

0,9*2=1,8 Nehmen Sie den ganzzahligen Teil 1

0,8 (Bruchteil von 1,8). )* 2=1.6 Holen Sie sich den ganzzahligen Teil 1

0.6*2=1.2 Holen Sie sich den ganzzahligen Teil 1

0.2*2=0.4 Take der ganzzahlige Teil 0

0,4*2=0,8 Nimm den ganzzahligen Teil 0

0,8*2=1,6 Nimm den ganzzahligen Teil 1

0,6*2 = 1,2 Nimm den ganzzahligen Teil 0

......... 0,9 binär angegeben (von oben nach unten) : 1100100100100 .....

Hinweis: Der obige Berechnungsprozess ist eine Schleife, das heißt, *2 eliminiert niemals den Dezimalteil, sodass der Algorithmus unendlich ist. Offensichtlich ist die binäre Darstellung von Dezimalzahlen manchmal nicht genau. Tatsächlich ist der Grund sehr einfach. Kann 1/3 im Dezimalsystem genau dargestellt werden? Ebenso kann das Binärsystem 1/10 nicht genau darstellen. Dies erklärt auch, warum die Gleitkommasubtraktion das Problem eines „unerschöpflichen“ Präzisionsverlusts aufweist.


3. Lösung 1:


Wenn es Ihnen nichts ausmacht, den Dezimalpunkt selbst zu notieren und den Wert zu ermitteln nicht groß, Dann können Sie Basistypen wie long und int verwenden. Die spezifische Verwendung von int oder long hängt von der Größe des betreffenden numerischen Bereichs ab. Der offensichtlichste Weg ist, dass Sie den Dezimalpunkt selbst verwalten müssen Cent anstelle von Yuan zur Währungsberechnung zu verwenden (beinhaltet nur das Hinzufügen von „Reduzieren“).

Zum Beispiel:

[java] view plain copy

int resultInt = 10 - 9;

double result = (double) resultInt / 100; //Am Ende steuern Sie den Dezimalpunkt selbst

4. Lösung 2:

Verwenden Sie BigDecmal und Sie müssen den String-Typ in den Konstruktionsparametern verwenden.

Eine Lösung finden Sie im Buch „Effective Java“. Das Buch weist auch darauf hin, dass Float und Double nur für wissenschaftliche Berechnungen oder technische Berechnungen verwendet werden können. Bei präzisen Berechnungen wie Geschäftsberechnungen müssen wir java.math.BigDecimal verwenden.

Die BigDecimal-Klasse verfügt über 4 Methoden. Wir kümmern uns nur um die Methoden, die für genaue Berechnungen von Gleitkommadaten nützlich sind, nämlich

BigDecimal(double value) // Doppelte Daten konvertieren In Daten vom Typ BigDecimal

Die Idee ist sehr einfach. Wir konvertieren zuerst die Daten vom Typ Double mit der Methode BigDecimal (Double Value) und können dann normal genaue Berechnungen durchführen. Nachdem die Berechnung abgeschlossen ist, können wir die Ergebnisse verarbeiten, z. B. die Ergebnisse runden, die nicht geteilt werden können. Abschließend wird das Ergebnis von BigDecimal-Daten zurück in Double-Daten konvertiert.

Diese Idee ist richtig, aber wenn Sie sich die detaillierte Beschreibung von BigDecimal in der API genauer ansehen, werden Sie wissen, dass wir Double nicht direkt verwenden können, sondern String zum Konstruieren verwenden müssen, wenn genaue Berechnungen erforderlich sind BigDecimal ! Daher beginnen wir, uns um eine andere Methode der BigDecimal-Klasse zu kümmern, nämlich die BigDecimal(String value)-Methode, die uns dabei helfen kann, präzise Berechnungen korrekt durchzuführen.

// BigDecimal(String value) kann String-Daten in BigDecimal-Daten konvertieren

Hier ist also das Problem. Stellen Sie sich vor: Wenn wir eine Additionsoperation für Gleitkommadaten durchführen möchten, müssen wir zuerst die beiden Gleitkommazahlen in String-Daten konvertieren und dann BigDecimal (String-Wert) verwenden, um ein BigDecimal zu erstellen , und dann Um die Add-Methode für einen von ihnen aufzurufen, übergeben Sie den anderen als Parameter und konvertieren Sie dann das Ergebnis der Operation (BigDecimal) in eine Gleitkommazahl. Wenn Sie dies jedes Mal tun müssen, wenn Sie Berechnungen mit Gleitkommadaten durchführen, können Sie dann einen so umständlichen Prozess ertragen? Zumindest kann ich es nicht. Der beste Weg besteht also darin, eine Klasse zu schreiben und diese umständlichen Konvertierungsprozesse in der Klasse abzuschließen. Auf diese Weise müssen wir nur diese Klasse aufrufen, wenn wir Gleitkommadatenberechnungen durchführen müssen. Einige Experten im Internet haben uns eine Werkzeugklasse Arith zur Verfügung gestellt, um diese Konvertierungsvorgänge durchzuführen. Es bietet die folgenden statischen Methoden, mit denen die Additions-, Subtraktions-, Multiplikations- und Divisionsoperationen von Gleitkommadaten abgeschlossen und die Ergebnisse gerundet werden können:

public static double add(double v1,double v2)
public static double sub (double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int-Skala)
öffentliche statische Doppelrunde (doppelte v, int-Skala)

Der Quellcode von Arith wird unten angehängt. Sie müssen ihn nur kompilieren und speichern, wenn Sie Floating durchführen möchten Punktberechnungen, in Ihrem Durch den Import der Arith-Klasse in das Quellprogramm können Sie die oben genannten statischen Methoden verwenden, um präzise Berechnungen von Gleitkommazahlen durchzuführen.

Anhang: Arith-Quellcode

[java] reine Kopie anzeigen

import java.math.BigDecimal;

/**

* Da die einfachen Java-Typen keine präzisen Operationen für Gleitkommazahlen ausführen können, bietet diese Dienstprogrammklasse präzise

* Operationen für Gleitkommazahlen, einschließlich Addition, Subtraktion, Multiplikation, Division und Rundung.

*/

public class Arith{

//Standardgenauigkeit der Divisionsoperation

private static final int DEF_DIV_SCALE = 10;

//Diese Klasse kann nicht instanziiert werden

private Arith(){

}

/**

* Bietet präzise Additionsoperationen.

* @param v1 Summand

* @param v2 Summand

* @return Summe zweier Parameter

*/ 

     public static double add(double v1,double v2){                                                                                                                                   BigDecimal b2 = new BigDecimal(Double.toString(v2));

return b1.add(b2) .doubleValue();

} */

public static double sub(double v1,double v2){

BigDecimal b1 = new BigDecimal(Double.toString(v1) );

BigDecimal b2 = new BigDecimal(Double.toString(v2));

return b1.subtract(b2).doubleValue();

}

/**

* Bietet präzise Subtraktionsoperationen.

* @param v1 Minuend

* @param v2 Minuend

* @return Die Differenz zwischen den beiden Parametern

*/

public static double mul(double v1,double v2){

BigDecimal b1 = new BigDecimal(Double.toString(v1));

BigDecimal b 2 = new BigDecimal(Double .toString(v2));

        return b1.multiply(b2).doubleValue();  

    }  

  

    /**

* Bietet (relativ) genaue Divisionsoperationen. Bei der Division erfolgt die Genauigkeit auf

* 10 Dezimalstellen nach dem Komma, und nachfolgende Zahlen werden gerundet.

* @param v1 Dividend

* @param v2 Divisor

* @return Quotient der beiden Parameter

*/  

    public static double div(double v1,double v2){  

        return div(v1,v2,DEF_DIV_SCALE);  

    }  

  

    /**

* Bietet (relativ) genaue Divisionsoperationen. Bei der unerschöpflichen Division wird die Genauigkeit durch den Skalenparameter

* angegeben und nachfolgende Zahlen werden gerundet.

* @param v1 Dividend

* @param v2 Divisor

* @param Skala bedeutet, dass die Genauigkeit auf mehrere Dezimalstellen genau sein muss.

* @return Der Quotient der beiden Parameter

*/  

    public static double div(double v1,double v2,int scale){  

        if(scale<0){  

            throw new IllegalArgumentException(  

                "Die Skala muss eine positive Ganzzahl oder eine Null sein");  

        }  

        BigDecimal b1 = new BigDecimal(Double.toString(v1));  

        BigDecimal b2 = new BigDecimal(Double.toString(v2));  

        return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();  

    }  

    /**

* Bietet eine präzise Verarbeitung der Dezimalstellenrundung.

* @param v Die Zahl, die gerundet werden muss

* @param scale Wie viele Dezimalstellen nach dem Dezimalpunkt beibehalten werden sollen

* @return Das Ergebnis nach dem Runden

*/  

    public static double round(double v,int scale){  

  

        if(scale<0){  

            throw new IllegalArgumentException(  

               "Die Skala muss eine positive Ganzzahl oder eine Null sein");  

        }  

        BigDecimal b = new BigDecimal(Double.toString(v));  

        BigDecimal one = new BigDecimal("1");  

        return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();  

    }  

};  


Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung von Beispielen für Float- und Double-Precision-Verlust in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn