J'ai essayé de trouver la raison, mais je ne pouvais pas. Quelqu'un peut-il m'aider?
Regardez l'exemple suivant.
float f;
f = 125.32f;
System.out.println("value of f = " + f);
double d = (double) 125.32f;
System.out.println("value of d = " + d);
C'est la sortie:
valeur de f = 125.32
valeur de d = 125.31999969482422
float
en un double
name__, vous ne perdez aucune information. Chaque float
peut être représenté exactement comme un double
name__.System.out.println
n'est la valeur exacte du nombre. Une représentation décimale exacte peut nécessiter environ 760 chiffres décimaux. System.out.println
imprime exactement le nombre de chiffres décimaux permettant d'analyser la représentation décimale dans le nom d'origine float
ou double
name__. Il y a plus de double
name__s; ainsi, lors de l'impression d'un, System.out.println
doit imprimer plus de chiffres avant que la représentation ne devienne non ambiguë.La valeur de float
ne change pas lors de la conversion en double
. Il y a une différence dans les chiffres affichés, car il faut plus de chiffres pour distinguer une valeur double
de ses voisins, ce qui est requis par la documentation Java . C'est la documentation pour toString
, qui est référencée (via plusieurs liens) à partir de la documentation pour println
.
La valeur exacte de 125.32f
est 125.31999969482421875. Les deux valeurs float
voisines sont 125.3199920654296875 et 125.32000732421875. Observez que 125.32 est plus proche de 125.31999969482421875 que de l'un des voisins. Par conséquent, en affichant «125.32», Java a affiché suffisamment de chiffres pour que la conversion du nombre décimal en float
reproduise la valeur de la variable float
transmise à println
.
Les deux valeurs double
voisines de 125,31999969482421875 sont 125,3199996948242045391452847979962825775146484375 et 125.31999969482423296085471520200171724243535625. Notez que 125.32 est plus proche de ce dernier voisin que de la valeur d'origine. Par conséquent, l’impression «125.32» ne contient pas assez de chiffres pour distinguer la valeur originale. Java doit imprimer plus de chiffres afin de garantir qu’une conversion du chiffre affiché en double
reproduise la valeur de double
passée à println
.
La conversion de float
en double
est une conversion élargie, comme spécifiée par le JLS . Une conversion d'élargissement est définie comme une cartographie injective d'un plus petit ensemble dans son sur-ensemble. Par conséquent, le nombre représenté ne change pas après une conversion de float
en double
.
Dans votre mise à jour, vous avez ajouté un exemple censé démontrer que le nombre a changé. Cependant, cela montre seulement que la représentation de chaîne du nombre a changé, ce qui est le cas en raison de la précision supplémentaire acquise lors de la conversion en double
. Notez que votre première sortie est juste un arrondi de la deuxième sortie. Comme spécifié par Double.toString
,
Il doit y avoir au moins un chiffre pour représenter la partie décimale et au-delà, autant de chiffres que nécessaire pour distinguer de manière unique la valeur d'argument des valeurs adjacentes de type
double
.
Étant donné que les valeurs adjacentes dans le type double
sont beaucoup plus proches que dans float
, il faut plus de chiffres pour se conformer à cette décision.
Le nombre à virgule flottante IEEE-754 32 bits le plus proche de 125.32 est en réalité 125.31999969482421875. Assez proche, mais pas tout à fait là (c'est parce que 0.32 se répète en binaire).
Lorsque vous transposez cela en double, c'est la valeur 125.31999969482421875 qui sera transformée en double (125.32 est introuvable à ce stade, les informations qui devraient se terminer par .32 sont complètement perdues) et peuvent bien sûr être représentées exactement par un double. Lorsque vous imprimez ce double, la routine d’impression pense qu’elle a des chiffres plus significatifs qu’elle ne l’a réellement (mais elle ne peut bien sûr pas le savoir); de toutes les décimales de cette longueur, c’est la plus proche).
Comme expliqué précédemment, tous les flottants peuvent être représentés exactement par un double et la raison de votre problème est que System.out.println
effectue des arrondis lors de l'affichage de la valeur d'une float
ou double
, mais la méthode d'arrondi n'est pas la même dans les deux cas.
Pour voir la valeur exacte du float, vous pouvez utiliser un BigDecimal
:
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));
qui produit:
value of f = 125.31999969482421875
value of d = 125.31999969482421875
La question de la précision des nombres à virgule flottante est vraiment indépendante de la langue. J'utiliserai donc MATLAB dans mes explications.
La raison pour laquelle vous voyez une différence est que certains nombres ne sont pas exactement représentables dans un nombre fixe de bits. Prenez 0.1
par exemple:
>> format hex
>> double(0.1)
ans =
3fb999999999999a
>> double(single(0.1))
ans =
3fb99999a0000000
Ainsi, l'erreur dans l'approximation de 0.1
en simple précision augmente lorsque vous le transformez en nombre à virgule flottante en double précision. Le résultat est différent de son approximation si vous avez commencé directement en double précision.
>> double(single(0.1)) - double(0.1)
ans =
1.490116113833651e-09
cela ne fonctionnera pas en Java car en Java par défaut, il prendra les valeurs réelles doubles et si nous déclarons une valeur float sans représentation float comme 123.45f par défaut, il le prendra comme double et il causera une erreur comme perte de précision
La représentation des valeurs change en raison des contrats des méthodes qui convertissent les valeurs numériques en String
, en conséquence Java.lang.Float#toString(float)
et Java.lang.Double#toString(double)
, tandis que la valeur réelle reste la même. Javadoc contient une partie commune des deux méthodes susmentionnées qui élabore des exigences pour la représentation des valeurs String
:
Il doit y avoir au moins un chiffre pour représenter la partie décimale et au-delà, autant de chiffres que nécessaire pour distinguer de manière unique la valeur de l'argument des valeurs adjacentes.
Pour illustrer la similitude de parties significatives pour les valeurs des deux types, l'extrait de code suivant peut être exécuté:
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));
}
}
Dans mon environnement, le résultat est:
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
De cette façon, vous pouvez voir que le signe, l’exposant des valeurs sont les mêmes, alors que sa mantisse a été étendue conservant sa partie significative (11110101010001111010111
) exactement de la même.
La logique d’extraction utilisée pour les parties de nombres en virgule flottante: 1 et 2 .