Bewahren Sie Präzision mit Doppel in Java
-
11-07-2019 - |
Frage
public class doublePrecision {
public static void main(String[] args) {
double total = 0;
total += 5.6;
total += 5.8;
System.out.println(total);
}
}
Der obige Code druckt:
11.399999999999
Wie würde ich diese erhalten drucken nur (oder in der Lage sein, es zu benutzen als) 11.4?
Lösung
Wie andere erwähnt haben, werden Sie wahrscheinlich wollen die BigDecimal
Klasse, wenn Sie eine genaue Darstellung von 11.4 haben wollen.
Nun wird eine kleine Erklärung in warum dies geschieht:
Die float
und double
primitiven Typen in Java sind Gleitkomma Zahlen, wo die Nummer gespeichert ist als binäre Darstellung einer Fraktion und ein Exponenten.
Insbesondere wird ein doppelter Genauigkeit Gleitkommawert wie der double
Typ ist ein 64-Bit-Wert, wobei gilt:
- 1 Bit bezeichnet das Vorzeichen (positiv oder negativ).
- 11 Bit für den Exponenten.
- 52 Bits für die signifikanten Stellen (der Bruchteil als binäres).
Diese Teile werden kombiniert, um eine double
Darstellung eines Wertes zu erzeugen.
(Quelle: Wikipedia: doppelte Genauigkeit )
Für eine detaillierte Beschreibung, wie Fließkommawerte in Java behandelt werden, finden Sie in der Abschnitt 4.2.3. Gleitkommatypen, Formate und Werte der Java Language Specification
Die byte
, char
, int
, long
Typen sind Festkomma Zahlen, die sind genaue representions von Zahlen. Im Gegensatz zu Festkommazahlen, Zahlen Gleitkomma wird einige Male (sicher „die meiste Zeit“ anzunehmen) nicht in der Lage, eine genaue Darstellung einer Zahl zurück. Dies ist der Grund, warum Sie mit 11.399999999999
als Ergebnis 5.6 + 5.8
enden.
Wenn Sie einen Wert erfordert, die genau wie 1,5 oder 150,1005 ist, werden Sie eine der Festpunkttypen verwenden möchten, die in der Lage sein wird, die Nummer genau darzustellen.
Wie bereits mehrfach erwähnt, Java hat eine BigDecimal
Klasse, die sehr große Stückzahl und sehr kleine Zahlen behandelt.
Von der Java-API-Referenz für die BigDecimal
Klasse:
Immutable, beliebige Genauigkeit Dezimalzahl mit Vorzeichen Zahlen. Ein BigDecimal besteht aus einem beliebiger Genauigkeit integer unskalierten Wert und einen ganzzahligen Skalierungs 32-Bit. Wenn Null oder positiv ist, ist das Ausmaß der Anzahl der Stellen rechts von der Komma. Wenn negativ, die unskalierten Wert der Zahl ist, um zehn bis die volle Leistung der multiplizierten Negation der Skala. Der Wert von die Zahl durch die dargestellte BigDecimal ist daher (unscaledValue × 10 ^ -Skala).
Es hat sich auf die Frage der Gleitkommazahlen und seine Präzision im Zusammenhang viele Fragen auf Stack-Überlauf gewesen. Hier ist eine Liste von verwandten Fragen, die von Interesse sein können:
- Warum sehe ich eine doppelte Variable auf einen Wert wie 21,4 als 21,399999618530273 initialisiert?
- Wie wirklich große Zahlen in C ++ drucken
- Wie wird Gleitkomma gespeichert? Wann ist es wichtig?
- verwenden Float oder Decimal für Accounting Anwendung Dollar-Betrag?
Wenn Sie wirklich wollen, auf die Nitty Gritty Details des Schwebens runterPunktzahl, werfen Sie einen Blick auf Was jeder Informatiker über Gleitkommaarithmetik wissen sollte .
Andere Tipps
Bei der Eingabe eine doppelte Anzahl, beispielsweise 33.33333333333333
, der Wert, den Sie erhalten, ist eigentlich der nächste darstellbare Wert mit doppelter Genauigkeit, die genau ist:
33.3333333333333285963817615993320941925048828125
Die Aufteilung, dass durch 100 ergibt:
0.333333333333333285963817615993320941925048828125
, die auch nicht darstellbare als Zahl mit doppelter Genauigkeit ist, so es wieder auf den nächsten darstellbaren Wert gerundet wird, was genau ist:
0.3333333333333332593184650249895639717578887939453125
Wenn Sie diesen Wert aus drucken, wird sie abgerundet noch einmal auf 17 Dezimalstellen geben:
0.33333333333333326
Wenn Sie nur Werte als Fraktionen bearbeiten möchten, können Sie eine Fraction-Klasse erstellen, die einen Zähler und Nenner-Feld enthält.
Schreibmethoden für Addieren, Subtrahieren, Multiplizieren und sowie eine ToDouble Methode teilen. Auf diese Weise können Schwimmer während 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);
}
}
}
Beachten Sie, dass Sie das gleiche Problem haben würden, wenn Sie mit begrenzten Genauigkeit Dezimalarithmetik verwendet, und wollten mit 1/3 beschäftigen: 0,333333333 * 3 ist 0,999999999, nicht 1,00000000.
Leider, 5,6, 5,8 und 11,4 sind einfach nicht runde Zahlen in binär, weil sie Fünftel beteiligt. So ist der Schwimmer Darstellung von ihnen ist nicht exakt, wie 0.3333 ist nicht genau 1/3.
Wenn alle Zahlen, die Sie verwenden, sind nicht-wiederkehrenden Dezimalstellen, und Sie möchten genaue Ergebnisse, BigDecimal verwenden. Oder wie andere gesagt haben, wenn Ihre Werte wie Geld in dem Sinne, dass sie alle ein Vielfaches von 0,01 sind, oder 0,001, oder etwas, dann multiplizieren alles durch eine feste Leistung von 10 und Verwendung int oder lang (Addition und Subtraktion trivial. achten Sie auf Multiplikation)
Wenn Sie jedoch mit binärer für die Berechnung glücklich sind, aber Sie wollen die Dinge nur in einem etwas freundlicheren Format drucken, versuchen java.util.Formatter
oder String.format
. Im Format-String geben Sie eine Genauigkeit von weniger als die volle Präzision eines Doppel. Zu 10 signifikanten Zahlen, sagen sie, 11,399999999999 ist 11,4, so das Ergebnis fast so genau sein wird und mehr Menschen lesbaren in Fällen, in denen das binäre Ergebnis ist sehr nahe an einen Wert, der nur wenige Dezimalstellen.
Die Genauigkeit angeben, hängt ein wenig ab, wie viel Mathematik Sie mit Ihren Zahlen getan haben - in der Regel Je mehr Sie tun, desto mehr Fehler akkumulieren, aber einige Algorithmen akkumulieren es viel schneller als andere (sie heißen " unstabil Rundungsfehler stabil ‚in Bezug“ im Gegensatz zu‘). Wenn alles, was Sie tun, ist ein paar Werte addiert, dann würde ich vermuten, dass nur eine Dezimalstelle an Präzision fallen die Dinge aussortieren. Experiment.
Sie können in der Verwendung der Java-java.math.BigDecimal Klasse aussehen sollen, wenn Sie wirklich Präzisionsberechnungen benötigen. Hier ist ein guter Artikel von Oracle / Sun auf den Fall für BigDecimal . Während Sie nie erwähnt 1/3 als jemand darstellen kann, können Sie können die Macht haben, genau zu entscheiden, wie genau Sie das Ergebnis sein soll. setScale () ist dein Freund ..:)
Ok, da ich im Moment viel zu viel Zeit auf meinen Händen hier habe, ist ein Codebeispiel, das 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 meine neue Lieblingssprache zu stopfen, Groovy, hier ist ein ordentliches Beispiel für die gleiche Sache:
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
Ziemlich sicher, dass Sie, dass in ein dreizeilige Beispiel gemacht haben könnten. :)
Wenn Sie genaue Präzision möchten, verwenden BigDecimal. Andernfalls können Sie Ints mit 10 ^ multipliziert verwenden, was auch immer Präzision, die Sie wollen.
Wie andere bereits erwähnt haben, sind nicht alle Dezimalzahlen als binäre dargestellt werden, da dezimal auf Potenzen von 10 und binäre basiert basiert auf Zweierpotenzen.
Wenn Präzision Angelegenheiten, verwenden BigDecimal, aber wenn man nur freundlich ausgegeben werden soll:
System.out.printf("%.2f\n", total);
geben Sie:
11.40
Sie laufen gegen die Präzision Begrenzung vom Typ double.
java.math hat einige beliebige Genauigkeit arithmetische Einrichtungen.
Sie können nicht, weil 7.3 keine endliche Repräsentation binär haben. Die nächstgelegene Sie erhalten können, ist 2054767329987789/2 ** 48 = 7,3 + 1/1407374883553280.
Hier finden Sie aktuelle http://docs.python.org/tutorial/floatingpoint.html für eine weitere Erklärung. (Es ist auf der Python-Website, aber Java und C ++ haben das gleiche "Problem".)
Die Lösung hängt davon ab, was genau das Problem ist:
- Wenn es, dass Sie gerade nicht mag all diese Lärm Stellen sehen, dann Zeichenfolge Formatierung beheben. Sie nicht mehr als 15 signifikante Stellen angezeigt werden (oder 7 für float).
- Wenn es, dass die Ungenauigkeit Ihrer Zahlen ist Dinge wie "if" Aussagen zu brechen, dann sollten Sie schreiben, wenn (abs (x - 7,3)
. - Wenn Sie mit dem Geld arbeiten, dann, was Sie wahrscheinlich wirklich ist Fixpunkt dezimal wollen. Speichern Sie eine ganze Anzahl von Cent oder was auch immer die kleinste Einheit Ihrer Währung ist.
- (sehr unwahrscheinlich) Wenn Sie mehr als 53 Bits (15-16 signifikante Stellen) müssen Präzision, dann verwenden, um eine hochpräzise Gleitkommatyp, wie BigDecimal.
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
}
Verwenden java.math.BigDecimal
Doubles sind Binärbrüche intern, so dass sie manchmal nicht Dezimalbrüchen auf die genaue dezimal darstellen.
alles mit 100 multiplizieren und speichern sie in einem langen Cent.
Computer speichern Zahlen in binär und kann nicht wirklich solche Zahlen darstellen, wie 33,333333333 oder 100,0 genau. Dies ist eine der kniffligen Dinge über verdoppelt werden. Sie werden nur rund um die Antwort haben, bevor es zu einem Benutzer zeigt. Zum Glück in den meisten Anwendungen, die Sie nicht brauchen, dass viele Dezimalstellen sowieso.
Gleitkommazahlen unterscheiden sich von reellen Zahlen, dass für jede Gleitkommazahl gegeben gibt es eine nächsthöhere Gleitpunktzahl. Das gleiche wie ganze Zahlen. Es gibt keine ganze Zahl zwischen 1 und 2.
Es gibt keinen Weg 1/3 als Schwimmer zu vertreten. Es gibt einen Schwimmer darunter und es gibt einen Schwimmer darüber, und es gibt eine gewisse Distanz zwischen ihnen. Und ein Drittel ist in diesem Raum.
Apfloat für Java behauptet mit beliebiger Genauigkeit Gleitkommazahlen zu arbeiten, aber ich habe es nie benutzt. Wahrscheinlich einen Blick wert. http://www.apfloat.org/apfloat_java/
Eine ähnliche Frage wurde hier gefragt, bevor Java Gleitkomma hohe Präzision Bibliothek
Doubles sind Annäherungen der Dezimalzahlen in Ihrer Java-Quelle. Sie sind die Folge der fehlenden Übereinstimmung zwischen dem Doppel zu sehen (die ein binär codierter Wert ist) und Ihre Quelle (die Dezimal-codiert ist).
Java Herstellung der nächste binäre Annäherung. Sie können die java.text.DecimalFormat verwenden, um eine besser aussehende Dezimalwert angezeigt werden soll.
Verwenden Sie ein BigDecimal. Damit können Sie auch Rundungsregeln festlegen (wie ROUND_HALF_EVEN, die durch Rundung auf den auch Nachbar statistische Fehler minimieren, wenn beide den gleichen Abstand sind, das heißt beide 1,5 und 2,5 Runde 2).
Überprüfen Sie heraus BigDecimal, es behandelt Probleme Umgang mit Gleitpunktarithmetik ähnlich.
Der neue Aufruf wie folgt aussehen:
term[number].coefficient.add(co);
Mit setScale () die Anzahl der Nachkommastellen Genauigkeit einzustellen verwendet werden.
Warum nicht die Runde () -Methode von Math-Klasse 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
Wenn Sie keine andere Wahl haben andere als die doppelten Werte verwenden, kann den Code unten 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;
}
Do not waste your efford mit BigDecimal nicht. In 99,99999% Fällen Sie brauchen es nicht. java double Typ ist von cource annähernde, jedoch in fast allen Fällen ist es hinreichend präzise. Beachten Sie, dass Ihr haben einen Fehler bei der 14. signifikanten Stelle. Das ist wirklich vernachlässigbar!
Um schön Ausgang Verwendung zu erhalten:
System.out.printf("%.2f\n", total);