Konvertieren Sie Float in Double, ohne an Präzision zu verlieren

97

Ich habe einen primitiven Float und ich brauche als primitiven Double. Wenn ich den Schwimmer einfach verdopple, bekomme ich seltsame zusätzliche Präzision. Beispielsweise:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

Wenn ich jedoch anstelle des Castings den Float als String ausgeben und den String als Double analysieren möchte, erhalte ich das, was ich will:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

Gibt es einen besseren Weg als nach String und zurück zu gehen?

Steve Armstrong
quelle

Antworten:

124

Es ist nicht so, dass Sie tatsächlich zusätzliche Präzision erhalten - es ist so, dass der Schwimmer nicht genau die Zahl darstellt, die Sie ursprünglich angestrebt haben. Das Doppel wird , die den ursprünglichen Schwimmers genau; toStringzeigt die "zusätzlichen" Daten an, die bereits vorhanden waren.

Zum Beispiel (und diese Zahlen stimmen nicht, ich erfinde nur Dinge), nehmen wir an, Sie hatten:

float f = 0.1F;
double d = f;

Dann könnte der Wert von fgenau 0,100000234523 sein. dhat genau den gleichen Wert, aber wenn Sie ihn in eine Zeichenfolge konvertieren, "vertraut" er darauf, dass er mit einer höheren Genauigkeit genau ist, wird also nicht so früh abgerundet, und Sie sehen die "zusätzlichen Ziffern", die bereits vorhanden waren dort, aber vor dir versteckt.

Wenn Sie in einen String und zurück konvertieren, erhalten Sie einen doppelten Wert, der näher am String-Wert liegt als der ursprüngliche Float - aber das ist nur gut, wenn Sie wirklich glauben, dass der String-Wert das ist, was Sie wirklich wollten.

Sind Sie sicher, dass float / double die geeigneten Typen sind, die hier anstelle von verwendet werden sollen BigDecimal? Wenn Sie versuchen, Zahlen mit genauen Dezimalwerten (z. B. Geld) zu verwenden, BigDecimalist IMO besser geeignet.

Jon Skeet
quelle
40

Ich finde es einfacher, dieses Problem zu konvertieren, wenn ich in die binäre Darstellung konvertiere.

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

Sie können sehen, dass der Gleitkommawert durch Hinzufügen von Nullen am Ende auf das Doppelte erweitert wird. Die Doppeldarstellung von 0,27 ist jedoch "genauer", daher das Problem.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
Brecht Yperman
quelle
25

Dies ist auf den Vertrag von zurückzuführen Float.toString(float), der teilweise besagt:

Wie viele Ziffern müssen für den Bruchteil gedruckt werden […]? Es muss mindestens eine Ziffer vorhanden sein, um den Bruchteil darzustellen, und darüber hinaus so viele, aber nur so viele Ziffern, wie erforderlich sind, um den Argumentwert eindeutig von benachbarten Werten vom Typ float zu unterscheiden. Das heißt, angenommen, dass x der exakte mathematische Wert ist, der durch die von dieser Methode erzeugte Dezimaldarstellung für ein endliches Argument f ungleich Null dargestellt wird. Dann muss f der Gleitkommawert sein, der x am nächsten liegt; oder wenn zwei Gleitkommawerte gleich nahe an x ​​liegen, muss f einer von ihnen sein und das niedrigstwertige Bit des Signifikanten von f muss 0 sein.

erickson
quelle
13

Ich bin heute auf dieses Problem gestoßen und konnte RefDactor für BigDecimal nicht verwenden, da das Projekt wirklich riesig ist. Ich fand jedoch eine Lösung mit

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

Und das funktioniert.

Beachten Sie, dass der Aufruf von result.doubleValue () 5623.22998046875 zurückgibt

Der Aufruf von doubleResult.doubleValue () gibt jedoch korrekt 5623.23 zurück

Aber ich bin nicht ganz sicher, ob es eine richtige Lösung ist.

Andrew
quelle
1
Hat für mich gearbeitet. Ist auch sehr schnell. Ich bin mir nicht sicher, warum dies nicht als Antwort markiert ist.
Sameer
Dies ist die Methode, die ich verwende, und du brauchst den neuen Float-Teil nicht ... Erstelle einfach das FloatingDecimal mit dem Grundelement. Es wird automatisch verpackt ... und funktioniert auch schnell ... Es gibt einen Nachteil: FloatingDecimal ist unveränderlich, daher müssen Sie für jeden einzelnen Float einen erstellen ... Stellen Sie sich einen Rechencode von O (1e10) vor! !! Natürlich hat BigDecimal den gleichen Nachteil ...
Mostafa Zeinali
6
Der Nachteil dieser Lösung ist, dass sun.misc.FloatingDecimal die interne Klasse von JVM ist und die Signatur des Konstruktors in Java 1.8 geändert wurde. Niemand sollte interne Klassen in realen Anwendungen verwenden.
Igor Bljahhin
7

Verwenden Sie ein BigDecimalanstelle von float/ double. Es gibt viele Zahlen, die nicht als binäres Gleitkomma dargestellt werden können (z. B. 0.1). Sie müssen das Ergebnis also entweder immer auf eine bekannte Genauigkeit runden oder verwenden BigDecimal.

Weitere Informationen finden Sie unter http://en.wikipedia.org/wiki/Floating_point .

Aaron Digulla
quelle
Nehmen wir an, Sie haben 10 Mio. Float-Handelspreise in Ihrem Cache. Ziehen Sie es immer noch in Betracht, BigDecimal zu verwenden und einzigartige Objekte zu instanziieren, sagen wir ~ 6 Mio. Objekte (um Fliegengewicht / unveränderlich zu reduzieren). Oder steckt noch mehr dahinter?
Sendi_t
1
@Sendi_t Bitte verwenden Sie keine Kommentare, um komplexe Fragen zu stellen :-)
Aaron Digulla
Vielen Dank fürs Schauen! .. Wir verwenden jetzt Floats, da dies vor einem Jahrzehnt vereinbart wurde. Ich habe versucht, Ihren Standpunkt zu dieser Situation bei der Nachverfolgung zu sehen. Vielen Dank!
Sendi_t
@Sendi_t Missverständnis. Ich kann Ihre Frage nicht mit 512 Zeichen beantworten. Bitte stellen Sie eine richtige Frage und senden Sie mir einen Link.
Aaron Digulla
1
@GKFX Es gibt keine "Einheitsgröße", wenn es um den Umgang mit Dezimalstellen auf Computern geht. Aber da die meisten Leute es nicht verstehen, weise ich sie darauf hin, BigDecimalda dies die meisten häufigen Fehler auffängt. Wenn die Dinge zu langsam sind, müssen sie mehr lernen und Wege finden, um ihre Probleme zu optimieren. Vorzeitige Optimierung ist die Wurzel allen Übels - DE Knuth.
Aaron Digulla
6

Ich habe folgende Lösung gefunden:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Wenn Sie float und double anstelle von Float und Double verwenden, verwenden Sie Folgendes:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}
GSD.Aaz
quelle
1

Schwimmer sind von Natur aus ungenau und haben immer ordentliche Rundungsprobleme. Wenn Präzision wichtig ist, können Sie Ihre Anwendung so umgestalten, dass sie Decimal oder BigDecimal verwendet.

Ja, Floats sind aufgrund der Unterstützung des Prozessors rechnerisch schneller als Dezimalstellen. Wollen Sie jedoch schnell oder genau?

Nicht ich
quelle
1
Dezimalarithmetik ist ebenfalls ungenau. (zB 1/3 * 3 == 0,999999999999999999999999999999) Es ist natürlich besser, exakte Dezimalgrößen wie Geld darzustellen , aber für physikalische Messungen hat es keinen Vorteil.
Dan04
2
Aber 1 == 0,999999999999999999999999999999 :)
Emmanuel Bourg
0

Weitere Informationen finden Sie unter Punkt 48 - Vermeiden Sie Float und Double, wenn genaue Werte erforderlich sind, von Effective Java 2nd Edition von Joshua Bloch. Dieses Buch ist vollgepackt mit guten Sachen und auf jeden Fall einen Blick wert.

mR_fr0g
quelle
0

Funktioniert das?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
AesmaDiv
quelle
0

Eine einfache Lösung, die gut funktioniert, besteht darin, das Double aus der String-Darstellung des Floats zu analysieren:

double val = Double.valueOf(String.valueOf(yourFloat));

Nicht super effizient, aber es funktioniert!

Alessandro Roaro
quelle