web-dev-qa-db-ger.com

Warum ändert die Konvertierung von float zu double den Wert?

Ich habe versucht, den Grund herauszufinden, aber ich konnte nicht. Kann mir jemand helfen?

Schauen Sie sich das folgende Beispiel an.

    float f;

    f = 125.32f;
    System.out.println("value of f = " + f);
    double d = (double) 125.32f; 
    System.out.println("value of d = " + d);

Dies ist die Ausgabe:

wert von f = 125,32

wert von d = 125,31999969482422

13
arthursfreire
  1. Wenn Sie einen floatin einen doublekonvertieren, gehen keine Informationen verloren. Jeder floatkann genau als doubledargestellt werden.
  2. Andererseits ist keine von System.out.println gedruckte Dezimaldarstellung der genaue Wert für die Zahl. Eine exakte Dezimaldarstellung kann bis zu 760 Dezimalstellen erfordern. Stattdessen gibt System.out.println genau die Anzahl der Dezimalstellen aus, die es ermöglichen, die Dezimaldarstellung in den ursprünglichen floatoder doublezu parsen. Es gibt mehr doublename__s, so dass beim Drucken von System.out.println mehr Ziffern gedruckt werden müssen, bevor die Darstellung eindeutig wird.
11
Pascal Cuoq

Der Wert von floatändert sich nicht, wenn er in doublekonvertiert wird. Es gibt einen Unterschied in den angezeigten Zahlen, da mehr Ziffern erforderlich sind, um einen doublename__-Wert von seinen Nachbarn zu unterscheiden, der von der Java-Dokumentation benötigt wird. Dies ist die Dokumentation für toStringname__, auf die (über mehrere Links) aus der Dokumentation für printlnverwiesen wird.

Der genaue Wert für 125.32f lautet 125.31999969482421875. Die zwei benachbarten floatname__-Werte sind 125.3199920654296875 und 125.32000732421875. Beachten Sie, dass 125.32 näher an 125.31999969482421875 liegt als an einem der Nachbarn. Daher hat Java mit der Anzeige von „125.32“ genügend Ziffern angezeigt, so dass die Konvertierung von der Dezimalzahl in floatden Wert von floatwiedergibt, der an printlnübergeben wird.

Die beiden benachbarten doublename__-Werte von 125.31999969482421875 lauten 125.3199996948242045391452847979962825775146484375 und 125.31999969482423296032606024853515625. Beachten Sie, dass 125.32 näher am letzten Nachbarn liegt als am ursprünglichen Wert. Daher enthält das Drucken von „125.32“ nicht genügend Ziffern, um den ursprünglichen Wert zu unterscheiden. Java muss mehr Ziffern drucken, um sicherzustellen, dass bei der Konvertierung von der angezeigten Zahl in doubleder Wert von doublean printlnübergeben wird.

10

Die Konvertierung von float zu double ist eine erweiterte Konvertierung , wie von der JLS angegeben . Eine sich erweiternde Umwandlung ist definiert als ein injektives Abbilden einer kleineren Menge in ihre Obermenge. Daher ändert sich die dargestellte Zahl nach einer Konvertierung von float in double nicht .

Weitere Informationen zu Ihrer aktualisierten Frage

In Ihrem Update haben Sie ein Beispiel hinzugefügt, das zeigen soll, dass sich die Nummer geändert hat. Es wird jedoch nur angezeigt, dass sich die Zeichenfolgendarstellung der Zahl geändert hat. Dies ist in der Tat auf die zusätzliche Genauigkeit zurückzuführen, die durch die Konvertierung in double erzielt wurde. Beachten Sie, dass Ihre erste Ausgabe nur eine Rundung der zweiten Ausgabe ist. Wie von Double.toString angegeben,

Es muss mindestens eine Ziffer geben, um den Bruchteil darzustellen, und darüber hinaus müssen so viele, aber nur so viele Stellen vorhanden sein, wie zur eindeutigen Unterscheidung des Argumentwerts von benachbarten Werten des Typs double erforderlich sind.

Da die benachbarten Werte im Typ double viel näher liegen als in float, sind mehr Ziffern erforderlich, um dieser Regel zu entsprechen.

4
Marko Topolnik

Die 32-Bit-Gleitkommazahl nach IEEE-754, die 125.32 am nächsten liegt, beträgt tatsächlich 125.31999969482421875. Ziemlich nah, aber nicht ganz da (weil 0,32 binär wiederholt wird).

Wenn Sie das zu einem Double machen, wird der Wert 125.31999969482421875 zu einem Double gemacht (125.32 ist an dieser Stelle nirgends zu finden, die Information, dass es wirklich in .32 enden sollte, ist natürlich vollständig verloren) und kann natürlich dargestellt werden genau um ein Doppel. Wenn Sie dieses Doppelte drucken, glaubt die Druckroutine, dass sie mehr signifikante Ziffern hat, als sie tatsächlich hat (aber das kann sie natürlich nicht wissen). Sie druckt also 125.31999969482422, die kürzeste Dezimalstelle, die auf das genaue Doppel (und von allen Dezimalstellen dieser Länge ist dies die nächste).

3
harold

Wie bereits erläutert, können alle Gleitkommazahlen genau doppelt dargestellt werden. Der Grund für Ihr Problem besteht darin, dass System.out.println beim Anzeigen des Werts einer float oder double eine Rundung durchführt. In beiden Fällen ist die Rundungsmethode jedoch nicht identisch.

Um den genauen Wert des Float anzuzeigen, können Sie eine BigDecimal verwenden:

float f = 125.32f;
System.out.println("value of f = " + new BigDecimal(f));
double d = (double) 125.32f;
System.out.println("value of d = " + new BigDecimal(d));

welche Ausgänge:

value of f = 125.31999969482421875
value of d = 125.31999969482421875
1
assylias

Das Problem der Genauigkeit von Fließkommazahlen ist wirklich sprachunabhängig, daher werde ich in meiner Erklärung MATLAB verwenden.

Der Grund dafür ist, dass bestimmte Zahlen in einer festen Anzahl von Bits nicht genau darstellbar sind. Nehmen Sie zum Beispiel 0.1:

>> format hex

>> double(0.1)
ans =
   3fb999999999999a

>> double(single(0.1))
ans =
   3fb99999a0000000

Der Fehler in der Approximation von 0.1 in einfacher Genauigkeit wird größer, wenn Sie ihn als Gleitkommazahl mit doppelter Genauigkeit umwandeln. Das Ergebnis unterscheidet sich von seiner Näherung, wenn Sie direkt mit doppelter Genauigkeit begonnen haben.

>> double(single(0.1)) - double(0.1)
ans =
     1.490116113833651e-09
1
Amro

es wird in Java nicht funktionieren, da es in Java standardmäßig reale Werte als doppelt akzeptiert und wenn wir einen Float-Wert ohne Float-Darstellung wie wie 123.25f deklarieren wird es doppelt halten und es wird ein Fehler als Präzisionsverlust verursacht 

0
sekhar beri

Die Darstellung der Werte ändert sich aufgrund von Verträgen der Methoden, die numerische Werte in eine String umwandeln, entsprechend Java.lang.Float#toString(float) und Java.lang.Double#toString(double), während der tatsächliche Wert gleich bleibt. In Javadoc gibt es einen gemeinsamen Teil der beiden zuvor genannten Methoden, der Anforderungen an die String-Darstellung von Werten ausgearbeitet:

Es muss mindestens eine Ziffer vorhanden sein, um den gebrochenen Teil darzustellen, und darüber hinaus so viele, aber nur so viele, mehr Ziffern, als erforderlich sind, um den Argumentwert eindeutig von benachbarten Werten zu unterscheiden

Um die Ähnlichkeit bedeutender Teile für Werte beider Typen zu veranschaulichen, kann das folgende Snippet ausgeführt werden:

package com.my.sandbox.numbers;

public class FloatToDoubleConversion {

    public static void main(String[] args) {
        float f = 125.32f;
        floatToBits(f);
        double d = (double) f;
        doubleToBits(d);
    }

    private static void floatToBits(float floatValue) {
        System.out.println();
        System.out.println("Float.");
        System.out.println("String representation of float: " + floatValue);
        int bits = Float.floatToIntBits(floatValue);
        int sign = bits >>> 31;
        int exponent = (bits >>> 23 & ((1 << 8) - 1)) - ((1 << 7) - 1);
        int mantissa = bits & ((1 << 23) - 1);
        System.out.println("Bytes: " + Long.toBinaryString(Float.floatToIntBits(floatValue)));
        System.out.println("Sign: " + Long.toBinaryString(sign));
        System.out.println("Exponent: " + Long.toBinaryString(exponent));
        System.out.println("Mantissa: " + Long.toBinaryString(mantissa));
        System.out.println("Back from parts: " + Float.intBitsToFloat((sign << 31) | (exponent + ((1 << 7) - 1)) << 23 | mantissa));
        System.out.println(10D);
    }

    private static void doubleToBits(double doubleValue) {
        System.out.println();
        System.out.println("Double.");
        System.out.println("String representation of double: " + doubleValue);
        long bits = Double.doubleToLongBits(doubleValue);
        long sign = bits >>> 63;
        long exponent = (bits >>> 52 & ((1 << 11) - 1)) - ((1 << 10) - 1);
        long mantissa = bits & ((1L << 52) - 1);
        System.out.println("Bytes: " + Long.toBinaryString(Double.doubleToLongBits(doubleValue)));
        System.out.println("Sign: " + Long.toBinaryString(sign));
        System.out.println("Exponent: " + Long.toBinaryString(exponent));
        System.out.println("Mantissa: " + Long.toBinaryString(mantissa));
        System.out.println("Back from parts: " + Double.longBitsToDouble((sign << 63) | (exponent + ((1 << 10) - 1)) << 52 | mantissa));
    }
}

In meiner Umgebung lautet die Ausgabe:

Float.
String representation of float: 125.32
Bytes: 1000010111110101010001111010111
Sign: 0
Exponent: 110
Mantissa: 11110101010001111010111
Back from parts: 125.32

Double.
String representation of double: 125.31999969482422
Bytes: 100000001011111010101000111101011100000000000000000000000000000
Sign: 0
Exponent: 110
Mantissa: 1111010101000111101011100000000000000000000000000000
Back from parts: 125.31999969482422

Auf diese Weise können Sie sehen, dass das Vorzeichen der Werte und der Exponent der Werte gleich sind, während die Mantisse verlängert wurde, wobei der signifikante Teil (11110101010001111010111) genau gleich blieb.

Die verwendete Extraktionslogik der Gleitkommazahlenteile: 1 und 2 .

0
Pavel