Qual'è la differenza tra float e double?


420

Ho letto della differenza tra precisione doppia e precisione singola. Tuttavia, nella maggior parte dei casi, floate doublesembrano essere intercambiabili, ovvero l'utilizzo dell'uno o dell'altro non sembra influire sui risultati. È davvero così? Quando sono intercambiabili float e doppi? Quali sono le differenze tra loro?

Risposte:


521

Differenza enorme.

Come suggerisce il nome, a doubleha una precisione doppia di [1] . In generale a ha 15 cifre decimali di precisione, mentre ha 7.floatdoublefloat

Ecco come vengono calcolati il ​​numero di cifre:

doubleha 52 bit mantissa + 1 bit nascosto: log (2 53 ) ÷ log (10) = 15,95 cifre

floatha 23 bit mantissa + 1 bit nascosto: log (2 24 ) ÷ log (10) = 7,22 cifre

Questa perdita di precisione potrebbe comportare l'accumulo di maggiori errori di troncamento quando si eseguono calcoli ripetuti, ad es

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

mentre

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

Inoltre, il valore massimo di float è circa 3e38, ma doppio è circa 1.7e308, quindi l'utilizzo floatpuò colpire "infinito" (cioè uno speciale numero in virgola mobile) molto più facilmente rispetto doublea qualcosa di semplice, ad esempio calcolare il fattoriale di 60.

Durante il test, forse alcuni casi di test contengono questi numeri enormi, che possono causare il fallimento dei programmi se si utilizzano float.


Certo, a volte, anche doublenon è abbastanza preciso, quindi a volte abbiamo long double[1] (l'esempio sopra fornisce 9.000000000000000066 su Mac), ma tutti i tipi in virgola mobile soffrono di errori di arrotondamento , quindi se la precisione è molto importante (ad es. Denaro elaborazione) è necessario utilizzare into una classe di frazione.


Inoltre, non utilizzare +=per sommare molti numeri in virgola mobile, poiché gli errori si accumulano rapidamente. Se stai usando Python, usa fsum. Altrimenti, prova a implementare l' algoritmo di somma Kahan .


[1]: la C e C ++ standard non specificano la rappresentazione di float, doublee long double. È possibile che tutti e tre siano implementati come doppia precisione IEEE. Tuttavia, per la maggior parte delle architetture (gcc, MSVC; x86, x64, ARM) float è effettivamente un numero in virgola mobile a precisione singola IEEE (binary32), ed double è un numero in virgola mobile a precisione doppia IEEE (binary64).


9
Il consueto consiglio per la somma è di ordinare i numeri in virgola mobile per grandezza (prima il più piccolo) prima della somma.
R .. GitHub smette di aiutare ICE il

Si noti che mentre C / C ++ float e double sono quasi sempre IEEE a precisione singola e doppia rispettivamente C / C ++ long double è molto più variabile a seconda della CPU, del compilatore e del sistema operativo. A volte è uguale al doppio, a volte è un formato esteso specifico del sistema, a volte è la precisione quad IEEE.
lavaggio

@ R..GitHubSTOPHELPINGICE: perché? Potresti spiegare?
InQusitive

@InQusitive: si consideri ad esempio un array costituito dal valore 2 ^ 24 seguito da 2 ^ 24 ripetizioni del valore 1. La somma in ordine produce 2 ^ 24. L'inversione produce 2 ^ 25. Ovviamente puoi fare degli esempi (es. Fare 2 ^ 25 ripetizioni di 1) in cui qualsiasi ordine finisce per essere catastroficamente sbagliato con un singolo accumulatore ma il primo di magnitudo prima è il migliore tra questi. Per fare meglio hai bisogno di una specie di albero.
R .. GitHub smette di aiutare ICE il

56

Ecco cosa dicono gli standard C99 (ISO-IEC 9899 6.2.5 §10) o C ++ 2003 (ISO-IEC 14882-2003 3.1.9 §8):

Ci sono tre tipi in virgola mobile: float, double, e long double. Il tipo doublefornisce almeno la stessa precisione floate il tipo long doublefornisce almeno la stessa precisione double. L'insieme di valori del tipo floatè un sottoinsieme dell'insieme di valori del tipo double; l'insieme di valori del tipo doubleè un sottoinsieme dell'insieme di valori del tipo long double.

Lo standard C ++ aggiunge:

La rappresentazione del valore dei tipi a virgola mobile è definita dall'implementazione.

Suggerirei di dare un'occhiata all'eccellente Cosa ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile che copre in profondità lo standard IEEE in virgola mobile. Imparerai a conoscere i dettagli della rappresentazione e ti renderai conto che c'è un compromesso tra grandezza e precisione. La precisione della rappresentazione in virgola mobile aumenta al diminuire dell'intensità, pertanto i numeri in virgola mobile compresi tra -1 e 1 sono quelli con la massima precisione.


27

Data un'equazione quadratica: x 2  - 4.0000000  x  + 3.9999999 = 0, le radici esatte su 10 cifre significative sono, r 1  = 2.000316228 e r 2  = 1.999683772.

Usando floate double, possiamo scrivere un programma di test:

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

L'esecuzione del programma mi dà:

2.00000 2.00000
2.00032 1.99968

Si noti che i numeri non sono grandi, ma si ottengono comunque effetti di annullamento tramite float.

(In effetti, quanto sopra non è il modo migliore per risolvere equazioni quadratiche usando numeri a virgola mobile a precisione singola o doppia, ma la risposta rimane invariata anche se si utilizza un metodo più stabile .)


19
  • Un doppio è 64 e la precisione singola (float) è 32 bit.
  • Il doppio ha una mantissa più grande (i bit interi del numero reale).
  • Eventuali imprecisioni saranno minori nel doppio.

12

La dimensione dei numeri coinvolti nei calcoli in virgola mobile non è la cosa più rilevante. È rilevante il calcolo che viene eseguito.

In sostanza, se stai eseguendo un calcolo e il risultato è un numero irrazionale o decimale ricorrente, allora ci saranno errori di arrotondamento quando quel numero viene schiacciato nella struttura di dati di dimensioni finite che stai utilizzando. Poiché il doppio ha una dimensione doppia del float, l'errore di arrotondamento sarà molto più piccolo.

I test possono utilizzare specificamente numeri che causerebbero questo tipo di errore e pertanto hanno verificato che hai utilizzato il tipo appropriato nel tuo codice.


9

Il tipo float, lungo 32 bit, ha una precisione di 7 cifre. Mentre può memorizzare valori con un intervallo molto grande o molto piccolo (+/- 3,4 * 10 ^ 38 o * 10 ^ -38), ha solo 7 cifre significative.

Il tipo doppio, lungo 64 bit, ha un intervallo maggiore (* 10 ^ + / - 308) e una precisione di 15 cifre.

Il tipo long double è nominalmente 80 bit, sebbene un dato accoppiamento compilatore / sistema operativo possa memorizzarlo come 12-16 byte ai fini dell'allineamento. Il lungo doppio ha un esponente che è incredibilmente enorme e dovrebbe avere una precisione di 19 cifre. Microsoft, nella sua infinita saggezza, limita il doppio doppio a 8 byte, lo stesso del doppio normale.

In generale, basta usare il tipo double quando è necessario un valore / variabile in virgola mobile. I valori letterali in virgola mobile utilizzati nelle espressioni verranno trattati come doppi per impostazione predefinita e la maggior parte delle funzioni matematiche che restituiscono valori in virgola mobile restituiscono doppi. Ti risparmierai molti mal di testa e typecasting se solo usi il doppio.



9

Ho appena incontrato un errore che mi ha impiegato per sempre a capire e potenzialmente può darti un buon esempio di precisione del galleggiante.

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

L'output è

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

Come puoi vedere dopo 0,83, la precisione diminuisce in modo significativo.

Tuttavia, se ho impostato il tdoppio, un tale problema non accadrà.

Mi ci sono volute cinque ore per realizzare questo piccolo errore, che ha rovinato il mio programma.


4
solo per essere sicuri: la soluzione del tuo problema dovrebbe essere quella di utilizzare un int preferibilmente? Se vuoi iterare 100 volte, dovresti contare con un int anziché usare un doppio
BlueTrin,

8
L'uso doublenon è una buona soluzione qui. Utilizzi intper contare ed eseguire una moltiplicazione interna per ottenere il valore in virgola mobile.
Richard,


3

Quando si utilizzano numeri in virgola mobile non ci si può fidare che i test locali saranno esattamente gli stessi dei test effettuati sul lato server. L'ambiente e il compilatore sono probabilmente diversi sul sistema locale e su dove vengono eseguiti i test finali. Ho già visto questo problema molte volte in alcune competizioni di TopCoder soprattutto se si tenta di confrontare due numeri in virgola mobile.


3

Le operazioni di confronto integrate differiscono come quando si confrontano 2 numeri con virgola mobile, la differenza nel tipo di dati (ovvero float o double) può comportare risultati diversi.


1

Se si lavora con l'elaborazione integrata, alla fine l'hardware sottostante (ad es. FPGA o un modello specifico di processore / microcontrollore) avrà un float implementato in modo ottimale nell'hardware mentre il doppio utilizzerà le routine del software. Quindi, se la precisione di un float è sufficiente per gestire le esigenze, il programma verrà eseguito alcune volte più velocemente con float quindi doppio. Come notato in altre risposte, fare attenzione agli errori di accumulazione.


-1

A differenza di un int(numero intero), a floatha un punto decimale e quindi a double. Ma la differenza tra i due è che a doubleè due volte più dettagliata di a float, il che significa che può avere il doppio della quantità di numeri dopo il punto decimale.


4
Questo non significa affatto. In realtà significa il doppio delle cifre decimali integrali ed è più del doppio. La relazione tra le cifre frazionarie e la precisione non è lineare: dipende dal valore: ad es. 0,5 è preciso, ma 0,33333333333333333333 no.
Marchese di Lorne,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.