Sobre a precisão de números flutuantes em C
Quando se utilizam número de ponto flutuante (float) é importante considerar questões de precisão e arredondamento dos valores. A precisão nos cálculos utilizando valores de ponto flutuante pode gerar resultados inesperados.
É preciso observar que um número decimal, seja um valor inteiro ou um valor float, possui representação binária no computador. Ou seja, o número é convertido da base decimal para a binária. Embora essa transformação consiga representar precisamente os valores inteiros, ela não é precisa com valores float.
Por exemplo, um valor float decimal .1 transformado para binário é .0001100110011 (com uma dízima periódica 0011 que se repete infinitamente). Portanto, o valor não pode ser representado precisamente.
Utilizo o código a seguir para ilustrar a questão da precisão. Veja que no código realizei a divisão 1/3.0 e não 1/3, pois a divisão de um número interior por outro número inteiro, na linguagem C, resulta em um valor inteiro. Para testar online os códigos disponibilizados nesse post acesse https://ideone.com/TmCCaN.
int main() {
printf("%f\n", 1/3.0);
printf("%f\n", 2/3.0);
printf("%f\n", 1/3.0 + 2/3.0);
printf("%f\n", 1/3.0 + 1/3.0 + 1/3.0);
return 0;
}
Sabemos que a divisão de 1/3.0 e 2/3.0 gera, respectivamente, as dízimas periódicas 0,33333333333… e 0,6666666666…. Ou seja, os valores após a vírgula se repetem infinitamente. Um valor float é representado com apenas 6 casas depois da vírgula e com isso o primeiro printf mostra o valor 0.333333 (um truncamento do valor da dízima periódica com 6 dígitos após a vírgula).
O terceiro printf mostra o resultado da soma de 1/3.0 e 2/3.0. Se for considerado o truncamento de 2/3.0, o valor esperado seria 0.666666 e a soma das duas divisões seria 0.999999, mas isso não se confirma com o terceiro printf, que mostra que o valor é 1.000000 (o que é esperado pois 1/3 + 2/3 é igual a 1 inteiro).
Veja que o segundo printf mostra o valor 0.666667, um arredondamento do valor correto. Com isso, a soma do terceiro printf é o resultado de 0.333333 + 0.666667, que resulta em 1.000000, o que parece responder a questão. Mas veja que o quarto printf, que soma 0.333333 + 0.333333 + 0.333333 também resulta em 1.000000.
Sendo assim, é importante não presumir resultados precisos quando se utilizam números float. Então, cuidado em comparar valores float quanto sua igualdade. Para ilustrar essa questão trago dois exemplos.
Como primeiro exemplo para ilustrar como não devemos presumir resultados quando utilizamos float, trago o código a seguir. Uma vez que sabemos que 1/3.0 resulta em 0.333333 é esperado que a comparação realizada no if resulte true, mas isso não acontece e com isso o resultado da execução do código não mostrará o printf com a palavra “Entrou”.
int main() {
if (0.333333 == 1/3.0) {
printf("Entrou");
}
return 0;
}
Como segundo exemplo trago uma comparação utilizando variáveis double e float. Veja que a única diferença dos dois trechos de código é que as variáveis soma1 e soma2 são declaradas com tipos diferentes. Para ambas as variáveis eu somo 30 vezes 1/3.0 (somo 10 vezes 1/3.0+1/3.0+1/3.0). O resultado matemático esperado é o valor 10.0, mas apenas a variável soma2, que é tipo double e com isso com maior precisão, consegue realmente alcançar esse valor.
int main() {
float soma1 = 0;
for(int i=0; i < 10*3; i++) {
soma1+= 1/3.0;
}
printf("%f \n", soma1);
double soma2 = 0;
for(int i=0; i < 10*3; i++) {
soma2+= 1/3.0;
}
printf("%f \n", soma2);
return 0;
}
Acho que essa questão de precisão dos valores de ponto flutuante merece mais explicações, o que pretendo fazer no futuro.