Eu tenho o seguinte código simples:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
e speed2
deve ter o mesmo valor, mas, na verdade, tenho:
speed1 = 61
speed2 = 62
Eu sei que provavelmente deveria usar Math.Round em vez de converter, mas gostaria de entender por que os valores são diferentes.
Eu olhei para o bytecode gerado, mas, exceto uma loja e uma carga, os códigos de operação são os mesmos.
Também tentei o mesmo código em java e obtenho corretamente 62 e 62.
Alguém pode explicar isso?
Edit: No código real, não é diretamente 6.2f * 10, mas uma chamada de função * uma constante. Eu tenho o seguinte bytecode:
para 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
para 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
podemos ver que os operandos são flutuadores e que a única diferença é a stloc/ldloc
.
Quanto à máquina virtual, tentei com o Mono / Win7, Mono / MacOS e .NET / Windows, com os mesmos resultados.
fonte
Respostas:
Primeiro, suponho que você saiba que
6.2f * 10
não é exatamente 62 devido ao arredondamento de ponto flutuante (na verdade é o valor 61.99999809265137 quando expresso como adouble
) e que sua pergunta é apenas sobre por que dois cálculos aparentemente idênticos resultam no valor errado.A resposta é que, no caso de
(int)(6.2f * 10)
, você está pegando odouble
valor 61.99999809265137 e truncando-o para um número inteiro, que gera 61.No caso de
float f = 6.2f * 10
, você está pegando o valor duplo 61.99999809265137 e arredondando para o valor mais próximofloat
, que é 62. Em seguida, você o truncafloat
para um número inteiro, e o resultado é 62.Exercício: Explique os resultados da seguinte sequência de operações.
Atualização: conforme observado nos comentários, a expressão
6.2f * 10
é formalmente uma,float
já que o segundo parâmetro tem uma conversão implícita nafloat
qual é melhor que a conversão implícita emdouble
.O problema real é que o compilador tem permissão (mas não é obrigatório) de usar um intermediário com maior precisão do que o tipo formal (seção 11.2.2) . É por isso que você vê um comportamento diferente em sistemas diferentes: Na expressão
(int)(6.2f * 10)
, o compilador tem a opção de manter o valor6.2f * 10
em uma forma intermediária de alta precisão antes de converter paraint
. Se isso acontecer, o resultado será 61. Caso contrário, o resultado será 62.No segundo exemplo, a atribuição explícita a
float
força o arredondamento antes da conversão para inteiro.fonte
(int)(6.2f * 10)
pegar odouble
valor, comof
especifica que é umfloat
? Eu acho que o ponto principal (ainda sem resposta) está aqui.6.2f * 10
é realmentefloat
, nãodouble
. Eu acho que o compilador está otimizando o intermediário, conforme permitido pelo último parágrafo do 11.1.6 .float
conversão primeiro.Descrição
Números flutuantes raramente são exatos.
6.2f
é algo parecido6.1999998...
. Se você converter para int, ele truncará e isso * 10 resultará em 61.Confira a
DoubleConverter
aula de Jon Skeets . Com essa classe, você pode realmente visualizar o valor de um número flutuante como string.Double
efloat
são ambos números flutuantes , decimal não é (é um número de ponto fixo).Amostra
Mais Informações
fonte
Veja o IL:
O compilador reduz expressões constantes em tempo de compilação ao seu valor constante, e acho que faz uma aproximação errada em algum momento quando converte a constante em
int
. No caso despeed2
, essa conversão é feita não pelo compilador, mas pelo CLR, e eles parecem aplicar regras diferentes ...fonte
Meu palpite é que
6.2f
a representação real com precisão de flutuação é6.1999999
while62f
provavelmente é algo semelhante a62.00000001
.(int)
a conversão sempre trunca o valor decimal, e é por isso que você obtém esse comportamento.EDIT : De acordo com os comentários, reformulei o comportamento do
int
casting para uma definição muito mais precisa.fonte
int
trunca o valor decimal, não é arredondado.float
->int
envolve arredondamentos. = DCompilei e desmontei esse código (no Win7 / .NET 4.0). Eu acho que o compilador avalia a expressão constante flutuante como dupla.
fonte
Single
mantém apenas 7 dígitos e ao convertê-lo em umInt32
compilador truncar todos os dígitos de ponto flutuante. Durante a conversão, um ou mais dígitos significativos podem ser perdidos.fornece o resultado de 619999980, então (Int32) (6,2f * 10) fornece 61.
É diferente quando dois Single são multiplicados; nesse caso, não há operação truncada, mas apenas aproximação.
Consulte http://msdn.microsoft.com/en-us/library/system.single.aspx
fonte
Existe uma razão pela qual você está digitando a conversão em
int
vez de analisar?então leria
A diferença provavelmente está relacionada ao arredondamento: se você converter para
double
você, provavelmente obterá algo como 61.78426.Observe a seguinte saída
É por isso que você está recebendo valores diferentes!
fonte
Int.Parse
pega uma string como parâmetro.