Qual é a diferença entre float e double?

Eu li sobre a diferença entre precisão dupla e precisão única. No entanto, na maioria dos casos, float e double parecem ser intercambiáveis, ou seja, usar um ou outro não parece afetar os resultados. É este realmente o caso? Quando flutuadores e duplos são intercambiáveis? Quais são as diferenças entre eles?

Enorme diferença.

Como o nome indica, um double tem 2x a precisão de float [1] . Em geral, um double tem 15 dígitos decimais de precisão, enquanto float tem 7.

Veja como o número de dígitos é calculado:

double tem 52 bits de mantissa + 1 bit oculto: log (2 53 ) ÷ log (10) = 15,95 dígitos

float tem 23 bits de mantissa + 1 bit oculto: log (2 24 ) ÷ log (10) = 7,22 dígitos

Essa perda de precisão pode levar a erros de truncamento muito mais fáceis de flutuar, por exemplo

 float a = 1.f / 81; float b = 0; for (int i = 0; i < 729; ++ i) b += a; printf("%.7g\n", b); // prints 9.000023 

enquanto

 double a = 1.0 / 81; double b = 0; for (int i = 0; i < 729; ++ i) b += a; printf("%.15g\n", b); // prints 8.99999999999996 

Além disso, o valor máximo de float é de cerca de 3e38 , mas o dobro é de aproximadamente 1.7e308 , então usar float pode atingir "infinito" (ou seja, um número especial de ponto flutuante) muito mais facilmente do que o double para algo simples, por exemplo. .

Durante os testes, talvez alguns casos de teste contenham esses números enormes, o que pode fazer com que seus programas falhem se você usar floats.


É claro que, às vezes, mesmo o double não é preciso o suficiente, por isso às vezes temos long double [1] (o exemplo acima dá 9.000000000000000066 no Mac), mas todos os tipos de ponto flutuante sofrem erros de arredondamento , portanto se precisão é muito importante (por exemplo, processamento de dinheiro) você deve usar int ou uma class fracionária.


Além disso, não use += para sumr vários números de ponto flutuante, pois os erros se acumulam rapidamente. Se você estiver usando Python, use fsum . Caso contrário, tente implementar o algoritmo de sum Kahan .


[1]: Os padrões C e C ++ não especificam a representação de float , double e long double . É possível que todos os três sejam implementados como precisão dupla IEEE. No entanto, para a maioria das arquiteturas (gcc, MSVC; x86, x64, ARM) float é de fato um número de ponto flutuante de precisão simples IEEE (binary32) e double é um número de ponto flutuante de precisão dupla IEEE (binary64).

Aqui está o que os padrões C99 (ISO-IEC 9899 6.2.5 §10) ou C ++ 2003 (ISO-IEC 14882-2003 3.1.9 §8) dizem:

Existem três tipos de ponto flutuante: float , double e long double . O tipo double fornece pelo menos tanta precisão quanto float , e o tipo long double fornece pelo menos tanta precisão quanto double . O conjunto de valores do tipo float é um subconjunto do conjunto de valores do tipo double ; O conjunto de valores do tipo double é um subconjunto do conjunto de valores do tipo long double .

O padrão C ++ adiciona:

A representação de valor de tipos de ponto flutuante é definida pela implementação.

Eu sugeriria dar uma olhada no excelente O que todo cientista da computação deve saber sobre a aritmética de ponto flutuante que cobre o padrão de ponto flutuante do IEEE em profundidade. Você aprenderá sobre os detalhes da representação e perceberá que há uma compensação entre magnitude e precisão. A precisão da representação do ponto flutuante aumenta à medida que a magnitude diminui, portanto, os números de ponto flutuante entre -1 e 1 são aqueles com maior precisão.

Dada uma equação quadrática: x 2 – 4.0000000 x + 3.9999999 = 0, as raízes exatas para 10 dígitos significativos são, r 1 = 2.000316228 e r 2 = 1.999683772.

Usando float e double , podemos escrever um programa de teste:

 #include  #include  void dbl_solve(double a, double b, double c) { double d = b*b - 4.0*a*c; double sd = sqrt(d); double r1 = (-b + sd) / (2.0*a); double r2 = (-b - sd) / (2.0*a); printf("%.5f\t%.5f\n", r1, r2); } void flt_solve(float a, float b, float c) { float d = b*b - 4.0f*a*c; float sd = sqrtf(d); float r1 = (-b + sd) / (2.0f*a); float r2 = (-b - sd) / (2.0f*a); printf("%.5f\t%.5f\n", r1, r2); } int main(void) { float fa = 1.0f; float fb = -4.0000000f; float fc = 3.9999999f; double da = 1.0; double db = -4.0000000; double dc = 3.9999999; flt_solve(fa, fb, fc); dbl_solve(da, db, dc); return 0; } 

Executando o programa me dá:

 2.00000 2.00000 2.00032 1.99968 

Observe que os números não são grandes, mas você ainda obtém efeitos de cancelamento usando float .

(Na verdade, a descrição acima não é a melhor maneira de resolver equações quadráticas usando números de ponto flutuante de precisão simples ou dupla, mas a resposta permanece inalterada, mesmo que se use um método mais estável .)

  • Um double é 64 e a precisão simples (float) é de 32 bits.
  • O duplo tem uma mantissa maior (os bits inteiros do número real).
  • Quaisquer imprecisões serão menores no dobro.

O tamanho dos números envolvidos nos cálculos de ponto de flutuação não é o mais relevante. É o cálculo que está sendo realizado que é relevante.

Em essência, se você estiver executando um cálculo e o resultado for um número irracional ou decimal recorrente, haverá erros de arredondamento quando esse número for espremido na estrutura de dados de tamanho finito que você está usando. Como o dobro tem o dobro do tamanho do flutuador, o erro de arredondamento será muito menor.

Os testes podem usar especificamente números que causariam esse tipo de erro e, portanto, testaram que você usou o tipo apropriado em seu código.

Os carros alegóricos têm menos precisão que os duplos. Embora você já saiba, leia O que devemos saber sobre aritmética de ponto flutuante para entender melhor.

Tipo float, 32 bits de comprimento, tem uma precisão de 7 dígitos. Embora possa armazenar valores com um intervalo muito grande ou muito pequeno (+/- 3,4 * 10 ^ 38 ou * 10 ^ -38), ele possui apenas 7 dígitos significativos.

Tipo duplo, com 64 bits de comprimento, possui um intervalo maior (* 10 ^ + / – 308) e 15 dígitos de precisão.

Type long double é nominalmente de 80 bits, embora um determinado pareamento de compilador / SO possa armazená-lo como 12-16 bytes para fins de alinhamento. O duplo longo tem um expoente que é ridiculamente grande e deve ter precisão de 19 dígitos. A Microsoft, em sua infinita sabedoria, limita o dobro ao longo de 8 bytes, o mesmo que o dobro simples.

De um modo geral, basta usar o tipo double quando você precisa de um valor / variável de ponto flutuante. Valores de ponto flutuante literal usados ​​em expressões serão tratados como duplos por padrão, e a maioria das funções matemáticas que retornam valores de ponto flutuante retornam duplas. Você vai economizar muitas dores de cabeça e typecastings se você usar apenas o dobro.

Acabei de me deparar com um erro que me levou para sempre para descobrir e potencialmente pode dar-lhe um bom exemplo de precisão de flutuação.

 #include  #include  int main(){ for(float t=0;t<1;t+=0.01){ std::cout < < std::fixed << std::setprecision(6) << t << std::endl; } } 

A saída é

 0.000000 0.010000 0.020000 0.030000 0.040000 0.050000 0.060000 0.070000 0.080000 0.090000 0.100000 0.110000 0.120000 0.130000 0.140000 0.150000 0.160000 0.170000 0.180000 0.190000 0.200000 0.210000 0.220000 0.230000 0.240000 0.250000 0.260000 0.270000 0.280000 0.290000 0.300000 0.310000 0.320000 0.330000 0.340000 0.350000 0.360000 0.370000 0.380000 0.390000 0.400000 0.410000 0.420000 0.430000 0.440000 0.450000 0.460000 0.470000 0.480000 0.490000 0.500000 0.510000 0.520000 0.530000 0.540000 0.550000 0.560000 0.570000 0.580000 0.590000 0.600000 0.610000 0.620000 0.630000 0.640000 0.650000 0.660000 0.670000 0.680000 0.690000 0.700000 0.710000 0.720000 0.730000 0.740000 0.750000 0.760000 0.770000 0.780000 0.790000 0.800000 0.810000 0.820000 0.830000 0.839999 0.849999 0.859999 0.869999 0.879999 0.889999 0.899999 0.909999 0.919999 0.929999 0.939999 0.949999 0.959999 0.969999 0.979999 0.989999 0.999999 

Como você pode ver depois de 0,83, a precisão diminui significativamente.

No entanto, se eu configurá-lo como duplo, tal problema não acontecerá.

Levei cinco horas para perceber esse pequeno erro, que arruinou meu programa.

Ao usar números de ponto flutuante, você não pode confiar que seus testes locais serão exatamente iguais aos testes feitos no lado do servidor. O ambiente e o compilador provavelmente são diferentes em seu sistema local e onde os testes finais são executados. Eu já vi esse problema muitas vezes antes em algumas competições do TopCoder, especialmente se você tentar comparar dois números de ponto flutuante.

As operações de comparação internas diferem como quando você compara 2 números com ponto flutuante, a diferença no tipo de dados (ou seja, float ou double) pode resultar em resultados diferentes.

Ao contrário de um int (número inteiro), um float tem um ponto decimal e um double . Mas a diferença entre os dois é que um double é duas vezes mais detalhado que um float , o que significa que ele pode ter o dobro da quantidade de números após o ponto decimal.