Por que meu número está sendo arredondado incorretamente?
-
22-09-2019 - |
Pergunta
Parece o tipo de código que só falha in-situ, mas tentarei adaptá-lo em um trecho de código que represente o que estou vendo.
float f = myFloat * myConstInt; /* Where myFloat==13.45, and myConstInt==20 */
int i = (int)f;
int i2 = (int)(myFloat * myConstInt);
Depois de percorrer o código, i==269 e i2==268.O que está acontecendo aqui para explicar a diferença?
Solução
A matemática de flutuação pode ser realizada com maior precisão do que o anunciado. Mas assim que você o armazena em Float F, essa precisão extra é perdida. Você não está perdendo essa precisão no segundo método até que, é claro, você lançou o resultado para a Int.
Editar: veja esta pergunta Por que difere a precisão do ponto flutuante em C# quando separado por paranteses e quando separado por declarações? Para uma explicação melhor do que eu provavelmente forneci.
Outras dicas
Como as variáveis de ponto flutuante não são infinitamente preciso.Use um decimal se precisar desse tipo de precisão.
Diferente modos de arredondamento também pode estar relacionado a esse problema, mas o problema de precisão é o que você está enfrentando aqui, AFAIK.
O ponto flutuante tem precisão limitado e é baseado em binário e não decimal. O número decimal 13.45 não pode ser representado com precisão no ponto flutuante binário, portanto, redondo. A multiplicação em 20 exagera ainda mais a perda de precisão. Neste ponto, você tem 268.999 ... - não 269 - Portanto, a conversão para truncados inteiros para 268.
Para obter arredondamento para o número inteiro mais próximo, você pode tentar adicionar 0,5 antes de converter de volta ao número inteiro.
Para a aritmética "perfeita", você pode tentar usar um tipo numérico decimal ou racional - acredito que C# possui bibliotecas para ambos, mas não tenho certeza. Estes serão mais lentos, no entanto.
EDITAR - Encontrei um tipo "decimal" até agora, mas não um racional - posso estar errado sobre isso estar disponível. O ponto flutuante decimal é impreciso, como binário, mas é o tipo de imprecisão que estamos acostumados, por isso fornece resultados menos surpreendentes.
Substituir com
double f = myFloat * myConstInt;
E veja se você obtém a mesma resposta.
Eu gostaria de oferecer uma explicação diferente.
Aqui está o código, que anotei (procurei na memória para dissecar os carros alegóricos):
float myFloat = 13.45; //In binary is 1101.01110011001100110011 int myConstInt = 20; float f = myFloat * myConstInt; //In binary is exactly 100001101 (269 decimal) int i = (int)f; // Turns float 269 into int 269 -- no surprises int i2 = (int)(myFloat * myConstInt);//"Extra precision" causes round to 268
Vamos olhar mais de perto os cálculos:
f = 1101.01110011001100110011 * 10100 = 100001100.1111111111111111 111 111
A peça após o espaço é de 25-27, que fazem com que 24 sejam arredondados e, portanto, todo o valor será arredondado para 269
int i2 = (int) (myfloat * myconstint)
O MyFloat é estendido para a dupla precisão para o cálculo (0s são anexados): 1101.0111001100110011001100100000000000000000000000000000
myfloat * 20 = 100001100.111111111111111111100000000000000000000000000
Os bits 54 e além são 0s, portanto, nenhum arredondamento é feito: o elenco resulta no número inteiro 268.
(Uma explicação semelhante funcionaria se a precisão estendida fosse usada.)
Atualização: refinei minha resposta e escrevi um artigo completo chamado Quando os carros alegóricos não se comportam como carros alegóricos