52 votes

Double multiplié par 100, puis lancé sur long donne une mauvaise valeur

J'ai le code suivant:

 Double i=17.31;
long j=(long) (i*100);
System.out.println(j);
 

O / P: 1730 //Expected:1731

 Double i=17.33;
long j=(long) (i*100);
System.out.println(j);
 

O / P: 1732 //Expected:1733

 Double i=17.32;
long j=(long) (i*100);
System.out.println(j);
 

O / P: 1732 //Expected:1732{As expected}

 Double i=15.33;
long j=(long) (i*100);
System.out.println(j);
 

O / P: 1533 //Expected:1533{as Expected}

J'ai essayé de Google mais incapable de trouver la raison. Je suis désolé si la question est triviale.

78voto

Anirudh Ramanathan Points 25113

Aucune des réponses semblent traiter avec pourquoi 17.32 agi différents.

1. Pourquoi cela s'est produit

La différence de comportement entre 17.32 et 17.33 & 17.31 est due à la norme IEEE-754 Arrondissement règles.

Règle d'arrondi appliqué: à partir de, La Virtuelle Java™ Machine Spécification §2.8.1

L'arrondi des opérations de la machine virtuelle Java, utilisez toujours de l'IEEE 754 tour la plus proche de mode. Inexact, les résultats sont arrondis à l'unité la plus proche représentable valeur, avec des liens d'aller à la valeur zéro les moins de bits significatifs. C'est la norme IEEE 754 mode par défaut. La Java virtual la machine ne donne pas tous les moyens pour modifier l'arrondi à virgule flottante mode


2. Votre cas:

Est Double: (1 signe-bit + 11 exposant-bits + 52 fraction bits = 64bits). La représentation interne, après arrondi ci-dessous:

             1 [63]      11 [62-52]           52 [51-00]
              Sign        Exponent             Fraction

17.31 -->    0 (+)       10000000011 (+4)     1.0001010011110101110000101000111101011100001010001111
17.32 -->    0 (+)       10000000011 (+4)     1.0001010100011110101110000101000111101011100001010010 //rounded up
17.33 -->    0 (+)       10000000011 (+4)     1.0001010101000111101011100001010001111010111000010100

3. La représentation interne (la Preuve):

17.31: (Mantisse de comparaison)

Actual:   1.00010100111101011100001010001111010111000010100011110...
Internal: 1.0001010011110101110000101000111101011100001010001111

17.32: (Mantisse de comparaison)

Actual:   1.00010101000111101011100001010001111010111000010100011... 
Internal: 1.0001010100011110101110000101000111101011100001010010    //round-up!

17.33: (Mantisse de comparaison)

Actual:   1.00010101010001111010111000010100011110101110000101000...
Internal: 1.0001010101000111101011100001010001111010111000010100

4. Conversion décimal:

17.31 ->  17.309999999999998721023075631819665431976318359375...
17.32 ->  17.32000000000000028421709430404007434844970703125... //(was rounded up)
17.33 ->  17.3299999999999982946974341757595539093017578125...

(IEEE-754 Outil d'Analyse)

5. Fonte à long

EDIT: Il y a un facteur de plus à jouer à la multiplication des étapes comme @Jeppe Stig Nielsen dit. Le résultat de la FP de multiplication (de Référence) de l'étape est sa propre arrondi vers le plus proche. Cela change dont les résultats sont attendus et qui ne le sont pas, mais la raison est toujours exactement le même comme indiqué ci-dessus.

Enfin, l'étape, en raison de la fonte d' (long) de troncature, vous laisse avec les résultats que vous voyez. (1730, 1732, 1732)

Rétrécissement Primitives De Conversion : La Java™ Spécification Du Langage §5.1.3

Si le nombre à virgule flottante n'est pas une infinité, la virgule flottante la valeur est arrondie à un nombre entier de valeur V, arrondi vers zéro à l'aide de La norme IEEE 754 aller-vers-le mode de zéro

22voto

svz Points 2078

La valeur double est représentée non pas comme 17.31, mais comme 17.30999999999999999. C'est pourquoi quand vous le multipliez par 100, vous obtenez 1730.9999999999999999999. Après la conversion en Long votre valeur double est tronquée vers zéro. Donc, vous obtenez 1730.

5voto

PearsonArtPhoto Points 14639

Comme cela a été expliqué, cela est dû à une très petite précision en virgule flottante.

Cela peut être résolu en utilisant une commande Math.round (), comme suit:

 long j=Math.round(i*100);
 

Cela permettra au programme de compenser les très petites erreurs héritées à l'aide de calculs en virgule flottante, en n'utilisant pas d'opération plancher, comme le fait la valeur par défaut (long).

5voto

Kevin Points 1686

Cthulhu et svz réponses sont correctes. Si vous voulez multiplier double par 100 et éviter à virgule flottante, des erreurs d'arrondi, vous pouvez utiliser Math.round() d'arrondir le résultat au plus proche long après chaque multiplication:

Double i=17.31;
long j=Math.round(i*100);
System.out.println(j);

Cela va avoir de virgule flottante d'erreur lorsque vous traitez extrêmement grande (ou négatif) en double. Plus la valeur absolue d'un double, la plus la différence entre elle et la prochaine double que Java peut représenter. Après un certain point, consécutifs, les chambres doubles sont plus qu'à un nombre entier d'intervalle, et les classiques de l'arrondissement ne sera pas en mesure de lisser la différence. Pour les exemples que vous avez posté, cela devrait fonctionner, cependant.

4voto

Burkhard Points 6734

Cela a à voir avec la représentation interne. Si vous jetez un coup d'œil à i * 100 dans le premier cas, vous verrez qu'il s'agit de 1730.999999999999998. La fonte enlèvera seulement la partie après le point (tronquée).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X