Le double multiplié par 100 et jeté à long donne une valeur erronée

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 sortingviale.

Aucune des réponses ne semble expliquer pourquoi 17.32 agi différemment.

1. Pourquoi cela s’est produit

La différence de comportement entre 17.32 et 17.33 & 17.31 est due aux règles d’ arrondi IEEE-754.

Règle d’arrondi appliquée: à partir de, la machine virtuelle Java ™ spécification §2.8.1

Les opérations d’arrondi de la machine virtuelle Java utilisent toujours le mode IEEE 754 rond vers le mode le plus proche. Les résultats inexacts sont arrondis à la valeur représentable la plus proche, les liens allant à la valeur avec un bit le moins significatif. C’est le mode par défaut IEEE 754. La machine virtuelle Java ne donne aucun moyen de modifier le mode d’arrondi en virgule flottante


2. Votre cas:

Double est : (1 bit de signe + 11 bits d’exposant + 52 bits de fraction = 64 bits). 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. Représentation interne (preuve):

17.31: (comparaison Mantissa)

 Actual: 1.00010100111101011100001010001111010111000010100011110... Internal: 1.0001010011110101110000101000111101011100001010001111 

17.32: (comparaison Mantissa)

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

17.33: (comparaison Mantissa)

 Actual: 1.00010101010001111010111000010100011110101110000101000... Internal: 1.0001010101000111101011100001010001111010111000010100 

4. Conversion en décimal:

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

( Outil d’parsing IEEE-754 )

5. Jeter à long

EDIT: Il y a un facteur plus en jeu lors de votre étape de multiplication, comme l’a dit @Jeppe Stig Nielsen. Le résultat de l’étape de multiplication FP ( référence ) fait son propre arrondi vers le plus proche. Cela change les résultats escomptés et ceux qui ne le sont pas, mais la raison est toujours la même que celle indiquée ci-dessus.

Enfin, en raison de la dissortingbution (long) , une troncature se produit et vous laisse les résultats que vous voyez. (1730, 1732, 1732)

Conversion primitive rétrécissante: Spécification du langage Java ™ §5.1.3

Si le nombre à virgule flottante n’est pas un infini, la valeur à virgule flottante est arrondie à une valeur entière V, en arrondissant vers zéro en utilisant le mode de retour à zéro IEEE 754

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

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 qui sont héritées en utilisant des calculs en virgule flottante, en n’utilisant pas une opération de plancher, comme le fait la valeur par défaut (long).

Cela a à voir avec la représentation interne. Si vous regardez i * 100 dans le premier cas, vous verrez que c’est 1730.9999999999998. La dissortingbution ne supprimera que la partie après le point (tronqué).

Les réponses de Cthulhu et de svz sont correctes. Si vous souhaitez multiplier les doubles par 100 et éviter les erreurs d’arrondi en virgule flottante, vous pouvez utiliser Math.round() pour arrondir le résultat le plus long après chaque multiplication:

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

Cela aura toujours une erreur en virgule flottante lors du traitement de doubles extrêmement grands (ou négatifs). Plus la valeur absolue d’un double est grande, plus la différence entre le double et le prochain double que Java peut représenter. Au bout d’un certain temps, les doubles consécutifs sont séparés par un nombre entier, et l’arrondi conventionnel ne pourra pas atténuer la différence. Pour les exemples que vous avez publiés, cela devrait fonctionner, cependant.

Lorsque vous faites ce genre de conversion longue, c’est le sol. Votre 17.31 pourrait effectivement être 17.30999999999 et c’est pourquoi il en est résulté 1730 au lieu de 1731.

utilisez i = i * 100, alors i.longValue () résoudra le problème.