Qual é a diferença entre float e double?

420

Eu li sobre a diferença entre precisão dupla e precisão única. No entanto, na maioria dos casos, floate doubleparece ser intercambiável, ou seja, o uso de um ou de outro não parece afetar os resultados. É este realmente o caso? Quando os carros alegóricos e duplos são intercambiáveis? Quais são as diferenças entre eles?

VaioIsBorn
fonte

Respostas:

521

Enorme diferença.

Como o nome indica, a doubletem 2x a precisão de [1] . Em geral, a possui 15 dígitos decimais de precisão, enquanto que 7.floatdoublefloat

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

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

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

Essa perda de precisão pode levar ao aumento de erros de truncamento quando cálculos repetidos são feitos, 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, portanto, o uso floatpode atingir "infinito" (ou seja, um número de ponto flutuante especial) muito mais facilmente do que doublepara algo simples, como calcular o fatorial de 60.

Durante o teste, talvez alguns casos de teste contenham esses números enormes, o que pode causar falhas nos programas se você usar flutuadores.


É claro que, às vezes, nem doubleé preciso o suficiente, portanto, temos long double[1] (o exemplo acima fornece 9.000000000000000066 no Mac), mas todos os tipos de ponto flutuante sofrem erros de arredondamento , portanto, se a precisão é muito importante (por exemplo, dinheiro processamento) você deve usar intou uma classe de fração.


Além disso, não use +=para somar muitos 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 somação Kahan .


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

kennytm
fonte
9
O conselho usual para a soma é ordenar os números de ponto flutuante por magnitude (o menor primeiro) antes de somar.
R .. GitHub Pare de ajudar o gelo
Observe que, enquanto C / C ++ float e double são quase sempre IEEE simples e dupla precisão, respectivamente, o C / C ++ long double é muito mais variável, dependendo da CPU, compilador e SO. Às vezes é o mesmo que o dobro, às vezes é algum formato estendido específico do sistema, às vezes é precisão quad IEEE.
plugwash
@ R..GitHubSTOPHELPINGICE: por que? Você poderia explicar?
InQusitive
@InQusitive: considere, por exemplo, uma matriz que consiste no valor 2 ^ 24 seguido por 2 ^ 24 repetições do valor 1. A soma em ordem produz 2 ^ 24. A reversão produz 2 ^ 25. Claro que você pode fazer exemplos (por exemplo, faça 2 ^ 25 repetições de 1) em que qualquer ordem acaba sendo catastroficamente errada com um único acumulador, mas a menor magnitude primeiro é a melhor dentre elas. Para fazer melhor, você precisa de algum tipo de árvore.
R .. GitHub Pare de ajudar o gelo
56

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

Existem três tipos de ponto flutuante: float, double, e long double. O tipo doublefornece pelo menos tanta precisão quanto floate o tipo long doublefornece pelo menos a mesma precisão que 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 do valor dos tipos de ponto flutuante é definida pela implementação.

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

Gregory Pakosz
fonte
27

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 er 2  = 1.999683772.

Usando floate double, podemos escrever um programa de teste:

#include <stdio.h>
#include <math.h>

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;
}  

A execução do 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.

(De fato, o exposto acima não é a melhor maneira de resolver equações quadráticas usando números de ponto flutuante de precisão única ou dupla, mas a resposta permanece inalterada, mesmo se alguém usar um método mais estável .)

Alok Singhal
fonte
19
  • Um duplo é 64 e a precisão única (flutuante) é 32 bits.
  • O duplo tem uma mantissa maior (os bits inteiros do número real).
  • Quaisquer imprecisões serão menores no dobro.
graham.reeds
fonte
12

O tamanho dos números envolvidos nos cálculos de ponto flutuante 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 compactado na estrutura de dados de tamanho finito que você está usando. Como o dobro é o dobro do tamanho da flutuação, o erro de arredondamento será muito menor.

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

Dolbz
fonte
9

O tipo float, com 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.

O tipo double, com 64 bits de comprimento, possui um intervalo maior (* 10 ^ + / - 308) e precisão de 15 dígitos.

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

De um modo geral, basta usar o tipo double quando precisar de um valor / variável de ponto flutuante. Os valores literais de ponto flutuante usados ​​nas 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 dobras. Você economizará muitas dores de cabeça e lançamentos de texto se usar apenas o dobro.

Zain Ali
fonte
Na verdade, para flutuação , é entre 7 e 8, 7.225 para ser exato .
Peter Mortensen
9

Acabei de encontrar um erro que me levou uma eternidade para descobrir e potencialmente pode lhe dar um bom exemplo de precisão de flutuação.

#include <iostream>
#include <iomanip>

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 após 0,83, a precisão diminui significativamente.

No entanto, se eu configurar o tdobro, esse problema não acontecerá.

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

Elliscope Fang
fonte
4
só para ter certeza: a solução do seu problema deve ser usar um int de preferência? Se você quiser iterar 100 vezes, conte com um int em vez de usar um double
BlueTrin 19/09/16
8
Usar doublenão é uma boa solução aqui. Você costuma intcontar e fazer uma multiplicação interna para obter seu valor de ponto flutuante.
Richard
3

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

Tuomas Pelkonen
fonte
3

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, flutuante ou duplo) pode resultar em resultados diferentes.

Johnathan Lau
fonte
1

Se alguém trabalha com processamento incorporado, eventualmente o hardware subjacente (por exemplo, FPGA ou algum modelo específico de processador / microcontrolador) terá flutuado implementado de maneira ideal no hardware, enquanto o dobro utilizará rotinas de software. Portanto, se a precisão de um float for suficiente para atender às necessidades, o programa será executado algumas vezes mais rápido com o float do que o dobro. Conforme observado em outras respostas, cuidado com os erros de acumulação.

Lissandro
fonte
-1

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

Nykal
fonte
4
Isso não significa nada disso. Na verdade, significa o dobro de dígitos decimais integrais e é mais do que o dobro. A relação entre dígitos fracionários e precisão não é linear: depende do valor: por exemplo, 0,5 é preciso, mas 0,33333333333333333333 não.
Marquês de Lorne