J'ai le code simple suivant:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
et speed2
devrait avoir la même valeur, mais en fait, j'ai:
speed1 = 61
speed2 = 62
Je sais que je devrais probablement utiliser Math.Round au lieu de cast, mais j'aimerais comprendre pourquoi les valeurs sont différentes.
J'ai regardé le bytecode généré, mais à part un magasin et une charge, les opcodes sont les mêmes.
J'ai également essayé le même code en java, et j'obtiens correctement 62 et 62.
Quelqu'un peut-il expliquer cela?
Edit: Dans le vrai code, ce n'est pas directement 6.2f * 10 mais un appel de fonction * une constante. J'ai le bytecode suivant:
pour speed1
:
IL_01b3: ldloc.s V_8
IL_01b5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ba: ldc.r4 10.
IL_01bf: mul
IL_01c0: conv.i4
IL_01c1: stloc.s V_9
pour speed2
:
IL_01c3: ldloc.s V_8
IL_01c5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ca: ldc.r4 10.
IL_01cf: mul
IL_01d0: stloc.s V_10
IL_01d2: ldloc.s V_10
IL_01d4: conv.i4
IL_01d5: stloc.s V_11
nous pouvons voir que les opérandes sont des flottants et que la seule différence est le stloc/ldloc
.
Pour ce qui est de la machine virtuelle, j'ai essayé avec Mono / Win7, Mono / MacOS et .NET / Windows, avec les mêmes résultats.
la source
Réponses:
Tout d'abord, je suppose que vous savez que ce
6.2f * 10
n'est pas exactement 62 en raison de l'arrondissement en virgule flottante (c'est en fait la valeur 61,99999809265137 lorsqu'elle est exprimée en adouble
) et que votre question porte uniquement sur la raison pour laquelle deux calculs apparemment identiques aboutissent à la mauvaise valeur.La réponse est que dans le cas de
(int)(6.2f * 10)
, vous prenez ladouble
valeur 61,99999809265137 et la tronquez en un entier, ce qui donne 61.Dans le cas de
float f = 6.2f * 10
, vous prenez la valeur double 61,99999809265137 et arrondissez au plus prochefloat
, qui est 62. Vous tronquez ensuite celafloat
à un entier, et le résultat est 62.Exercice: Expliquez les résultats de la séquence d'opérations suivante.
Mise à jour: comme indiqué dans les commentaires, l'expression
6.2f * 10
est formellement afloat
puisque le deuxième paramètre a une conversion implicite versfloat
laquelle est meilleure que la conversion implicite endouble
.Le problème réel est que le compilateur est autorisé (mais pas obligatoire) à utiliser un intermédiaire qui est plus précis que le type formel (section 11.2.2) . C'est pourquoi vous voyez un comportement différent sur différents systèmes: dans l'expression
(int)(6.2f * 10)
, le compilateur a la possibilité de conserver la valeur6.2f * 10
sous une forme intermédiaire de haute précision avant la conversion enint
. Si tel est le cas, le résultat est 61. Si ce n'est pas le cas, le résultat est 62.Dans le deuxième exemple, l'affectation explicite à
float
force l'arrondi avant la conversion en entier.la source
(int)(6.2f * 10)
prend ladouble
valeur, commef
spécifie que c'est unfloat
? Je pense que le point principal (toujours sans réponse) est ici.6.2f * 10
est en faitfloat
, nondouble
. Je pense que le compilateur optimise l'intermédiaire, comme le permet le dernier paragraphe de 11.1.6 .float
conversion.La description
Les nombres flottants sont rarement exacts.
6.2f
est quelque chose comme6.1999998...
. Si vous transtypez ceci en un entier, cela le tronquera et cela * 10 donnera 61.Découvrez la
DoubleConverter
classe Jon Skeets . Avec cette classe, vous pouvez vraiment visualiser la valeur d'un nombre flottant sous forme de chaîne.Double
etfloat
sont tous deux des nombres flottants , décimal ne l'est pas (c'est un nombre à virgule fixe).Échantillon
Plus d'information
la source
Regardez l'IL:
Le compilateur réduit les expressions constantes au moment de la compilation à leur valeur constante, et je pense qu'il fait une mauvaise approximation à un moment donné lorsqu'il convertit la constante en
int
. Dans le cas despeed2
, cette conversion n'est pas effectuée par le compilateur, mais par le CLR, et ils semblent appliquer des règles différentes ...la source
Je pense que la
6.2f
représentation réelle avec une précision de flotteur est6.1999999
en62f
est probablement quelque chose de similaire à62.00000001
.(int)
le cast tronque toujours la valeur décimale , c'est pourquoi vous obtenez ce comportement.EDIT : Selon les commentaires, j'ai reformulé le comportement du
int
casting pour une définition beaucoup plus précise.la source
int
tronque la valeur décimale, elle n'est pas arrondie.float
->int
implique l'arrondi. = DJ'ai compilé et désassemblé ce code (sur Win7 / .NET 4.0). Je suppose que le compilateur évalue l'expression constante flottante comme double.
la source
Single
ne contient que 7 chiffres et lors de sa conversion en un,Int32
le compilateur tronque tous les chiffres à virgule flottante. Lors de la conversion, un ou plusieurs chiffres significatifs peuvent être perdus.donne le résultat de 619999980 donc (Int32) (6.2f * 10) donne 61.
C'est différent lorsque deux Single sont multipliés, dans ce cas, il n'y a pas d'opération tronquée mais seulement une approximation.
Voir http://msdn.microsoft.com/en-us/library/system.single.aspx
la source
Y a-t-il une raison pour laquelle vous tapez un cast au
int
lieu d'analyser?lirait alors
La différence est probablement
double
liée à l'arrondi: si vous lancez, vous obtiendrez probablement quelque chose comme 61.78426.Veuillez noter la sortie suivante
C'est pourquoi vous obtenez des valeurs différentes!
la source
Int.Parse
prend une chaîne comme paramètre.