Convertissez float en double sans perdre de précision

97

J'ai un flotteur primitif et j'en ai besoin comme double primitif. Le simple fait de lancer le flotteur pour doubler me donne une précision supplémentaire étrange. Par exemple:

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

Cependant, si au lieu de lancer un cast, je produis le float sous forme de chaîne et analyse la chaîne comme un double, j'obtiens ce que je veux:

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

Y a-t-il un meilleur moyen que d'aller à String et revenir?

Steve Armstrong
la source

Réponses:

125

Ce n'est pas que vous réellement obtenir une précision supplémentaire - c'est que le flotteur ne représente pas exactement le nombre que vous visiez à l' origine. Le double est représentant le flotteur d' origine avec précision; toStringaffiche les données «supplémentaires» déjà présentes.

Par exemple (et ces chiffres ne sont pas corrects, je ne fais qu'inventer) supposons que vous aviez:

float f = 0.1F;
double d = f;

La valeur de fpourrait alors être exactement 0,100000234523. daura exactement la même valeur, mais lorsque vous la convertissez en une chaîne, elle «fera confiance» à sa précision avec une précision plus élevée, donc ne sera pas arrondie aussi tôt, et vous verrez les «chiffres supplémentaires» qui étaient déjà là, mais caché de vous.

Lorsque vous convertissez en chaîne et inversement, vous vous retrouvez avec une valeur double qui est plus proche de la valeur de la chaîne que ne l'était le flottant d'origine - mais ce n'est bon que si vous pensez vraiment que la valeur de chaîne est ce que vous vouliez vraiment.

Êtes-vous sûr que float / double sont les types appropriés à utiliser ici au lieu de BigDecimal? Si vous essayez d'utiliser des nombres qui ont des valeurs décimales précises (par exemple de l'argent), alors BigDecimalest un type IMO plus approprié.

Jon Skeet
la source
40

Je trouve que la conversion à la représentation binaire est plus facile à saisir ce problème.

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)));

Vous pouvez voir que le flottant est étendu au double en ajoutant des 0 à la fin, mais que la double représentation de 0,27 est «plus précise», d'où le problème.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
Brecht Yperman
la source
25

Cela est dû au contrat de Float.toString(float), qui dit en partie:

Combien de chiffres doivent être imprimés pour la partie fractionnaire […]? Il doit y avoir au moins un chiffre pour représenter la partie fractionnaire, et au-delà autant, mais seulement autant, plus de chiffres que nécessaire pour distinguer de manière unique la valeur d'argument des valeurs adjacentes de type float. Autrement dit, supposons que x soit la valeur mathématique exacte représentée par la représentation décimale produite par cette méthode pour un argument fini non nul f. Alors f doit être la valeur flottante la plus proche de x; ou, si deux valeurs flottantes sont également proches de x, alors f doit être l'une d'entre elles et le bit le moins significatif du significande de f doit être 0.

Erickson
la source
13

J'ai rencontré ce problème aujourd'hui et je n'ai pas pu utiliser refactor vers BigDecimal, car le projet est vraiment énorme. Cependant, j'ai trouvé une solution en utilisant

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

Et cela fonctionne.

Notez que l'appel de result.doubleValue () renvoie 5623.22998046875

Mais appeler doubleResult.doubleValue () renvoie correctement 5623.23

Mais je ne suis pas tout à fait sûr que ce soit une solution correcte.

Andrew
la source
1
A travaillé pour moi. Est également très rapide. Je ne sais pas pourquoi cela n'est pas marqué comme une réponse.
Sameer
C'est la méthode que j'utilise, et vous n'avez pas besoin de la nouvelle partie Float ... Créez simplement le FloatingDecimal avec la primitive. Il sera automatiquement encadré ... Et fonctionne vite aussi ... Il y a un inconvénient, FloatingDecimal est immuable, vous devez donc en créer un pour chaque flotteur ... imaginez un code de calcul de O (1e10)! !! Bien sûr, BigDecimal a le même inconvénient ...
Mostafa Zeinali
6
L'inconvénient de cette solution est que sun.misc.FloatingDecimal est la classe interne de JVM et la signature de son constructeur a été modifiée dans Java 1.8. Personne ne devrait utiliser des classes internes dans une application réelle.
Igor Bljahhin
8

J'ai trouvé la solution suivante:

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

Si vous utilisez float et double au lieu de Float et Double, utilisez ce qui suit:

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

Utilisez un BigDecimalau lieu de float/ double. Il y a beaucoup de nombres qui ne peuvent pas être représentés en virgule flottante binaire (par exemple, 0.1). Vous devez donc toujours arrondir le résultat à une précision ou à une utilisation connue BigDecimal.

Voir http://en.wikipedia.org/wiki/Floating_point pour plus d'informations.

Aaron Digulla
la source
Disons que vous avez des prix commerciaux flottants de 10 m dans votre cache, envisagez-vous toujours d'utiliser BigDecimal et d'instancier des objets uniques, disons ~ 6 m d'objets (pour réduire le poids mouche / immuable) .. ou y a-t-il plus?
Sendi_t
1
@Sendi_t Veuillez ne pas utiliser de commentaires pour poser des questions complexes :-)
Aaron Digulla
Merci d'avoir regardé! .. nous utilisons des flotteurs en ce moment comme cela a été convenu il y a dix ans - j'essayais de voir votre point de vue sur cette situation lors du suivi -. Merci!
Sendi_t
@Sendi_t Malentendu. Je ne peux pas répondre à votre question en 512 caractères. Veuillez poser une question appropriée et m'envoyer un lien.
Aaron Digulla
1
@GKFX Il n'y a pas de "taille unique" pour gérer les nombres décimaux sur les ordinateurs. Mais comme la plupart des gens ne comprennent pas, je leur signale BigDecimalcar cela permettra de détecter la plupart des erreurs courantes. Si les choses sont trop lentes, ils doivent en savoir plus et trouver des moyens d'optimiser leurs problèmes. L'optimisation prématurée est la racine de tout mal - DE Knuth.
Aaron Digulla
1

Les flotteurs, par nature, sont imprécis et ont toujours des «problèmes» d'arrondi soignés. Si la précision est importante, vous pouvez envisager de refactoriser votre application pour utiliser Decimal ou BigDecimal.

Oui, les flottants sont plus rapides que les nombres décimaux en raison de la prise en charge du processeur. Cependant, voulez-vous rapide ou précis?

Pas moi
la source
1
L'arithmétique décimale est également inexacte. (par exemple 1/3 * 3 == 0.9999999999999999999999999999) C'est, bien sûr, mieux pour représenter des quantités décimales exactes comme l'argent, mais pour les mesures physiques, cela n'a aucun avantage.
dan04
2
Mais 1 == 0.9999999999999999999999999999 :)
Emmanuel Bourg
0

Pour information, ceci relève de l'article 48 - Évitez les flottants et doublez lorsque des valeurs exactes sont requises, de Effective Java 2nd edition par Joshua Bloch. Ce livre regorge de bonnes choses et vaut vraiment le détour.

mR_fr0g
la source
0

Est-ce que ça marche?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
AesmaDiv
la source
0

Une solution simple qui fonctionne bien consiste à analyser le double à partir de la représentation sous forme de chaîne du float:

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

Pas super efficace, mais ça marche!

Alessandro Roaro
la source