Jaka jest różnica między float a double?

420

Czytałem o różnicy między podwójną precyzją a pojedynczą precyzją. Jednak w większości przypadków floati doublewydają się być wymienne, tj. Użycie jednego lub drugiego nie wydaje się wpływać na wyniki. Czy to naprawdę tak jest? Kiedy zmiennoprzecinkowe i podwajalne są wymienne? Jakie są między nimi różnice?

VaioIsBorn
źródło

Odpowiedzi:

521

Duża różnica.

Jak sama nazwa wskazuje, a doublema 2x precyzję [1] . Ogólnie rzecz biorąc a ma 15 cyfr dziesiętnych precyzji, a ma 7.floatdoublefloat

Oto jak obliczana jest liczba cyfr:

doublema 52 bity mantysy + 1 ukryty bit: log (2 53 ) ÷ log (10) = 15,95 cyfr

floatma 23 bity mantysy + 1 ukryty bit: log (2 24 ) ÷ log (10) = 7,22 cyfry

Ta utrata precyzji może prowadzić do gromadzenia większych błędów skracania, gdy wykonywane są powtarzane obliczenia, np

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

podczas

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

Ponadto maksymalna wartość liczby zmiennoprzecinkowej wynosi około 3e38, ale wartość podwójna wynosi około 1.7e308, więc użycie floatmoże osiągnąć „nieskończoność” (tj. Specjalną liczbę zmiennoprzecinkową) o wiele łatwiej niż w doubleprzypadku czegoś prostego, np. Obliczenie silni 60.

Podczas testowania może kilka przypadków testowych zawiera te ogromne liczby, co może spowodować awarię programów, jeśli używasz liczb zmiennoprzecinkowych.


Oczywiście czasami nawet doublenie jest wystarczająco dokładna, dlatego czasami mamy long double[1] (powyższy przykład podaje 9.000000000000000066 na Macu), ale wszystkie typy zmiennoprzecinkowe cierpią na błędy zaokrąglania , więc jeśli precyzja jest bardzo ważna (np. Pieniądze przetwarzanie) należy użyć intlub klasy ułamkowej.


Ponadto nie należy używać +=do sumowania wielu liczb zmiennoprzecinkowych, ponieważ błędy kumulują się szybko. Jeśli używasz Pythona, użyj fsum. W przeciwnym razie spróbuj zaimplementować algorytm sumowania Kahana .


[1]: C i C ++ normy nie określają reprezentację float, doublei long double. Możliwe jest, że wszystkie trzy zostaną zaimplementowane jako podwójna precyzja IEEE. Niemniej jednak dla większości architektur (gcc, MSVC; x86, x64, ARM) float jest rzeczywiście liczbą zmiennoprzecinkową pojedynczej precyzji IEEE (binary32) i double jest liczbą zmiennoprzecinkową podwójnej precyzji IEEE (binary64).

kennytm
źródło
9
Zwykłą radą przy sumowaniu jest posortowanie liczb zmiennoprzecinkowych według wielkości (najpierw najmniejsza) przed zsumowaniem.
R .. GitHub ZATRZYMAJ LÓD
Zauważ, że podczas gdy zmiennoprzecinkowe i podwójne C / C ++ są prawie zawsze IEEE pojedyncze i podwójna precyzja odpowiednio C / C ++ długie podwójne są znacznie bardziej zmienne w zależności od procesora, kompilatora i systemu operacyjnego. Czasami jest taki sam jak podwójny, czasami jest to jakiś rozszerzony format specyficzny dla systemu, Czasami jest to quad precyzja IEEE.
płyn do płukania
@ R..GitHubSTOPHELPINGICE: dlaczego? Czy możesz wytłumaczyć?
InQusitive
@InQusitive: Weźmy na przykład tablicę składającą się z wartości 2 ^ 24, a następnie 2 ^ 24 powtórzeń wartości 1. Sumowanie w kolejności daje 2 ^ 24. Cofanie daje 2 ^ 25. Oczywiście możesz podać przykłady (np. Zrobić 2 ^ 25 powtórzeń 1), w których każde zamówienie kończy się katastrofalnie złym pojedynczym akumulatorem, ale pierwsze z nich o najmniejszej wielkości jest najlepsze. Aby zrobić to lepiej, potrzebujesz jakiegoś drzewa.
R .. GitHub ZATRZYMAJ LÓD
56

Oto, co mówią standardowe normy C99 (ISO-IEC 9899 6.2.5 §10) lub C ++ 2003 (ISO-IEC 14882-2003 3.1.9 §8):

Istnieją trzy typy floating point: float, double, i long double. Typ doublezapewnia co najmniej tyle samo precyzji co float, a typ long doublezapewnia co najmniej tyle samo precyzji co double. Zbiór wartości typu floatjest podzbiorem zbioru wartości typu double; zbiór wartości typu doublejest podzbiorem zbioru wartości typu long double.

Standard C ++ dodaje:

Reprezentacja wartości typów zmiennoprzecinkowych jest zdefiniowana w implementacji.

Proponuję rzucić okiem na to, co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej, która obejmuje głębię standardu zmiennoprzecinkowego IEEE. Dowiesz się o szczegółach reprezentacji i zrozumiesz, że istnieje kompromis między wielkością a precyzją. Precyzja reprezentacji zmiennoprzecinkowej rośnie wraz ze spadkiem wielkości, stąd liczby zmiennoprzecinkowe od -1 do 1 to te z największą precyzją.

Grzegorz Pakosz
źródło
27

Biorąc pod uwagę równanie kwadratowe: x 2  - 4,0000000  x  + 3,9999999 = 0, dokładne pierwiastki do 10 cyfr znaczących to: r 1  = 2.000316228 i r 2  = 1.999683772.

Za pomocą floati doublemożemy napisać program testowy:

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

Uruchomienie programu daje mi:

2.00000 2.00000
2.00032 1.99968

Zauważ, że liczby nie są duże, ale nadal otrzymujesz efekty anulowania za pomocą float.

(W rzeczywistości powyższe nie jest najlepszym sposobem rozwiązywania równań kwadratowych za pomocą liczb zmiennoprzecinkowych o pojedynczej lub podwójnej precyzji, ale odpowiedź pozostaje niezmieniona, nawet jeśli stosuje się bardziej stabilną metodę ).

Alok Singhal
źródło
19
  • Podwójne to 64, a pojedyncza precyzja (liczba zmiennoprzecinkowa) to 32 bity.
  • Podwójny ma większą mantysę (bity całkowite liczby rzeczywistej).
  • Wszelkie niedokładności będą mniejsze w podwójnej.
graham.reeds
źródło
12

Rozmiar liczb biorących udział w obliczeniach zmiennoprzecinkowych nie jest najważniejszy. Ważne jest wykonanie obliczeń.

Zasadniczo, jeśli wykonujesz obliczenia, a wynikiem jest liczba niewymierna lub powtarzająca się liczba dziesiętna, wystąpią błędy zaokrąglania, gdy liczba ta zostanie zmiażdżona w używanej strukturze danych o skończonym rozmiarze. Ponieważ double jest dwa razy większy niż liczba zmiennoprzecinkowa, błąd zaokrąglenia będzie znacznie mniejszy.

Testy mogą w szczególności używać liczb, które spowodowałyby tego rodzaju błąd, a zatem przetestowały, czy użyłeś odpowiedniego typu w kodzie.

Dolbz
źródło
9

Typ float, o długości 32 bitów, ma dokładność 7 cyfr. Chociaż może przechowywać wartości o bardzo dużym lub bardzo małym zakresie (+/- 3,4 * 10 ^ 38 lub * 10 ^ -38), ma tylko 7 cyfr znaczących.

Typ double, 64-bitowy, ma większy zakres (* 10 ^ + / - 308) i 15 cyfrową dokładność.

Typ double double ma nominalnie 80 bitów, chociaż dane parowanie kompilatora / systemu operacyjnego może przechowywać go jako 12-16 bajtów dla celów wyrównania. Długi podwójny ma wykładnik, który jest absurdalnie ogromny i powinien mieć 19-cyfrową precyzję. Microsoft, w swojej nieskończonej mądrości, ogranicza długi podwójny do 8 bajtów, taki sam jak zwykły podwójny.

Ogólnie rzecz biorąc, po prostu użyj typu double, gdy potrzebujesz wartości zmiennoprzecinkowej / zmiennej. Dosłowne wartości zmiennoprzecinkowe używane w wyrażeniach będą domyślnie traktowane jako liczby podwójne, a większość funkcji matematycznych zwracających wartości zmiennoprzecinkowe zwraca liczbę podwójną. Zaoszczędzisz sobie wielu bólów głowy i rzutów, jeśli użyjesz podwójnie.

Zain Ali
źródło
W rzeczywistości dla liczby zmiennoprzecinkowej wynosi ona od 7 do 8, a dokładnie 7,225 .
Peter Mortensen,
9

Właśnie natrafiłem na błąd, który wymyślił mi wieczność i potencjalnie może dać dobry przykład precyzji float.

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

Dane wyjściowe to

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

Jak widać po 0,83, precyzja znacznie spada.

Jednak jeśli skonfiguruję tjako podwójny, taki problem się nie stanie.

Pięć godzin zajęło mi zrozumienie tego drobnego błędu, który zrujnował mój program.

Elliscope Fang
źródło
4
dla pewności: rozwiązaniem Twojego problemu powinno być użycie int? Jeśli chcesz iterować 100 razy, powinieneś liczyć z int, a nie z podwójnym
BlueTrin,
8
Używanie doublenie jest tutaj dobrym rozwiązaniem. Służy intdo liczenia i wewnętrznego mnożenia w celu uzyskania wartości zmiennoprzecinkowej.
Richard
3

Korzystając z liczb zmiennoprzecinkowych, nie można ufać, że testy lokalne będą dokładnie takie same, jak testy wykonywane po stronie serwera. Środowisko i kompilator prawdopodobnie różnią się w twoim systemie lokalnym i gdzie przeprowadzane są końcowe testy. Widziałem ten problem wiele razy wcześniej w niektórych konkursach TopCoder, zwłaszcza jeśli próbujesz porównać dwie liczby zmiennoprzecinkowe.

Tuomas Pelkonen
źródło
3

Wbudowane operacje porównania różnią się, ponieważ przy porównywaniu 2 liczb z liczbą zmiennoprzecinkową różnica w typie danych (tj. Zmiennoprzecinkowa lub podwójna) może powodować różne wyniki.

Johnathan Lau
źródło
1

Jeśli ktoś pracuje z wbudowanym przetwarzaniem, ostatecznie sprzęt bazowy (np. FPGA lub jakiś konkretny model procesora / mikrokontrolera) będzie optymalnie zaimplementowany w sprzęcie, podczas gdy podwójne będą korzystać z procedur programowych. Więc jeśli dokładność liczby zmiennoprzecinkowej jest wystarczająca do zaspokojenia potrzeb, program będzie działał kilka razy szybciej z liczbą zmiennoprzecinkową niż dwukrotnie. Jak zauważono w innych odpowiedziach, uważaj na błędy akumulacji.

Lissandro
źródło
-1

W przeciwieństwie do int(liczby całkowitej), a floatma przecinek dziesiętny, a więc może również double. Różnica między nimi polega na tym, że a doublejest dwa razy bardziej szczegółowe niż a float, co oznacza, że ​​może mieć podwojoną liczbę liczb po przecinku.

Nykal
źródło
4
To wcale nie znaczy, że W rzeczywistości oznacza to dwa razy więcej całkowitych cyfr dziesiętnych i jest ponad dwukrotnie. Zależność między cyframi ułamkowymi a precyzją nie jest liniowa: zależy od wartości: np. 0,5 jest precyzyjne, ale 0.33333333333333333333 nie.
Markiz Lorne