Powinienem używać double czy float?

89

Jakie są zalety i wady używania jednego zamiast drugiego w C ++?

AraK
źródło
Czy ktoś próbował stworzyć tablicę liczb zmiennoprzecinkowych i tablicę podwójnych i sprawdzić, czy rzeczywiście między elementami zmiennoprzecinkowymi znajdują się 4 bajty, a na podwójnych - 8 bajtów? Możliwe, że 64-bitowy kompilator / komputer może nadal rezerwować 8 bajtów na element członkowski dla elementów zmiennoprzecinkowych, mimo że nie potrzebują tak dużo.
user3015682

Odpowiedzi:

104

Jeśli chcesz poznać prawdziwą odpowiedź, przeczytaj artykuł Co każdy informatyk powinien wiedzieć o arytmetyce zmiennoprzecinkowej .

Krótko mówiąc, chociaż doublepozwala na większą precyzję w reprezentacji, dla niektórych obliczeń spowodowałaby większe błędy . „Właściwy” wybór to: użyj tyle precyzji, ile potrzebujesz, ale nie więcej, i wybierz właściwy algorytm .

Wiele kompilatorów i tak wykonuje rozszerzone obliczenia zmiennoprzecinkowe w trybie „nieścisłym” (tj. Używa szerszego typu zmiennoprzecinkowego dostępnego w sprzęcie, np. Zmiennoprzecinkowy 80-bitowy i 128-bitowy zmiennoprzecinkowy). W praktyce nie widać prawie żadnej różnicy w szybkości - i tak są to rdzenni mieszkańcy sprzętu.

J-16 SDiZ
źródło
10
Tak. W przypadku nowoczesnych procesorów wstępnie pobierających coraz większe porcje pamięci, równoległych jednostek przetwarzania numerycznego i architektur potokowych, problem z szybkością naprawdę nie jest problemem. Jeśli masz do czynienia z ogromnymi ilościami liczb, być może różnica rozmiaru między 4-bajtowym zmiennoprzecinkowym a 8-bajtowym podwójnym może wpłynąć na zużycie pamięci.
lavinio
5
Cóż, SSE (lub dowolna jednostka zmiennoprzecinkowa) będzie w stanie przetworzyć dwukrotnie większą liczbę flopów przy pojedynczej precyzji w porównaniu z podwójną precyzją. Jeśli robisz tylko zmiennoprzecinkowe x87 (lub jakikolwiek skalarny), prawdopodobnie nie będzie to miało znaczenia.
Greg Rogers,
1
@Greg Rogers: kompilatory nie są w tej chwili zbyt inteligentne. O ile nie piszesz asemblera surowego, nie ma dużego innego. I tak, może się to zmienić wraz z rozwojem kompilatora.
J-16 SDiZ,
Dodatkowe uwagi: Jeśli absolutnie nie masz pojęcia, jak wyglądają dane (lub po prostu nie masz pojęcia o wszystkich obliczeniach w linkach), po prostu użyj double- jest to w większości przypadków bezpieczniejsze.
J-16 SDiZ,
2
Ten artykuł nie jest krótki.
żart
44

Jeśli nie masz konkretnego powodu, aby postąpić inaczej, użyj double.

Być może zaskakujące jest to, że jest to double, a nie float, czyli „normalny” typ zmiennoprzecinkowy w C (i C ++). Standardowe funkcje matematyczne, takie jak sin i log, przyjmują podwojenie jako argumenty i zwracają podwojenie. Normalny literał zmiennoprzecinkowy, tak jak podczas pisania 3.14 w programie, ma typ double. Nie unosi się.

Na typowych nowoczesnych komputerach podwójne mogą być równie szybkie, jak pływające lub nawet szybsze, więc wydajność zwykle nie jest czynnikiem do rozważenia, nawet w przypadku dużych obliczeń. (A to musiałyby być duże obliczenia, inaczej wydajność nie powinna przychodzić ci do głowy. Mój nowy komputer stacjonarny i7 może wykonać sześć miliardów razy podwojenie w ciągu jednej sekundy.)

Thomas Padron-McCarthy
źródło
26

Na to pytanie nie można odpowiedzieć, ponieważ nie ma kontekstu w pytaniu. Oto kilka rzeczy, które mogą wpłynąć na wybór:

  1. Implementacja w kompilatorze zmiennych typu float, double i long double. Standard C ++ stwierdza:

    Istnieją trzy typy zmiennoprzecinkowe: zmiennoprzecinkowe, podwójne i długie podwójne. Typ double zapewnia co najmniej taką samą precyzję jak zmiennoprzecinkowy, a typ long double zapewnia co najmniej taką samą precyzję jak double.

    Zatem wszystkie trzy mogą mieć ten sam rozmiar w pamięci.

  2. Obecność FPU. Nie wszystkie procesory mają jednostki FPU i czasami typy zmiennoprzecinkowe są emulowane, a czasami typy zmiennoprzecinkowe po prostu nie są obsługiwane.

  3. Architektura FPU. FPU IA32 jest wewnętrznie 80-bitowe - 32-bitowe i 64-bitowe zmiennoprzecinkowe są rozszerzane do 80-bitowych przy obciążeniu i zmniejszane w magazynie. Istnieje również SIMD, który może wykonywać cztery 32-bitowe zmiennoprzecinkowe lub dwa 64-bitowe zmiennoprzecinkowe równolegle. Użycie SIMD nie jest zdefiniowane w standardzie, więc wymagałoby kompilatora, który przeprowadza bardziej złożoną analizę w celu ustalenia, czy można użyć SIMD, lub wymaga użycia funkcji specjalnych (bibliotek lub wewnętrznych). Konsekwencją 80-bitowego formatu wewnętrznego jest to, że można uzyskać nieco inne wyniki w zależności od tego, jak często dane są zapisywane w pamięci RAM (co powoduje utratę precyzji). Z tego powodu kompilatory nie optymalizują szczególnie dobrze kodu zmiennoprzecinkowego.

  4. Przepustowość pamięci. Jeśli double wymaga więcej miejsca niż float, odczyt danych zajmie więcej czasu. To naiwna odpowiedź. Na nowoczesnym IA32 wszystko zależy od tego, skąd pochodzą dane. Jeśli jest w pamięci podręcznej L1, obciążenie jest pomijalne, pod warunkiem, że dane pochodzą z jednej linii pamięci podręcznej. Jeśli obejmuje więcej niż jedną linię pamięci podręcznej, jest niewielki narzut. Jeśli jest z L2, zajmuje to trochę więcej czasu, jeśli jest w pamięci RAM, to nadal jest dłużej i wreszcie, jeśli jest na dysku, to ogromny czas. Zatem wybór liczby zmiennoprzecinkowej lub podwójnej jest mniej ważny niż sposób wykorzystania danych. Jeśli chcesz wykonać małe obliczenia na wielu sekwencyjnych danych, preferowany jest mały typ danych. Wykonywanie wielu obliczeń na małym zestawie danych umożliwiłoby użycie większych typów danych z istotnym skutkiem. Jeśli ty' ponowne uzyskiwanie dostępu do danych w sposób bardzo losowy, wtedy wybór rozmiaru danych nie ma znaczenia - dane są ładowane w stronach / wierszach pamięci podręcznej. Więc nawet jeśli chcesz tylko bajt z pamięci RAM, możesz przesłać 32 bajty (jest to bardzo zależne od architektury systemu). Ponadto procesor / FPU może być superskalarny (inaczej potokowy). Tak więc, nawet jeśli ładowanie może zająć kilka cykli, procesor / FPU może być zajęty robieniem czegoś innego (na przykład mnożenia), co do pewnego stopnia ukrywa czas ładowania.

  5. Standard nie wymusza żadnego konkretnego formatu dla wartości zmiennoprzecinkowych.

Jeśli masz specyfikację, poprowadzi Cię ona do optymalnego wyboru. W przeciwnym razie zależy od doświadczenia, czego użyć.

Skizz
źródło
16

Double jest bardziej precyzyjne, ale jest zakodowane na 8 bajtach. float to tylko 4 bajty, więc mniej miejsca i mniejsza precyzja.

Powinieneś być bardzo ostrożny, jeśli w swojej aplikacji masz double i float. Miałem z tego powodu błąd w przeszłości. Jedna część kodu korzystała z float, podczas gdy reszta kodu korzystała z double. Kopiowanie double do float, a następnie float do double może spowodować błąd precyzji, który może mieć duży wpływ. W moim przypadku była to fabryka chemiczna ... mam nadzieję, że nie miało to dramatycznych konsekwencji :)

Myślę, że to właśnie z powodu tego rodzaju błędu rakieta Ariane 6 eksplodowała kilka lat temu !!!

Zastanów się dokładnie, jaki typ zmiennej ma być użyty

luc
źródło
3
Zauważ, że 4/8 bajtów dla float / double nie jest nawet gwarantowane, będzie to zależeć od platformy. To może być nawet ten sam typ ...
sleske
2
Kod Ariane 5 próbował przekształcić 64-bitową liczbę zmiennoprzecinkową, której wartość była większa niż 32767, na 16-bitową liczbę całkowitą ze znakiem. To wygenerowało wyjątek przepełnienia, który spowodował, że rakieta zainicjowała sekwencję samozniszczenia. Kod, o którym mowa, był kodem, który został ponownie użyty ze starszej, mniejszej rakiety.
cmwt
5

Osobiście cały czas idę podwójnie, dopóki nie zobaczę wąskich gardeł. Następnie rozważam przejście na float lub zoptymalizowanie innej części

Eric
źródło
4

Zależy to od tego, jak kompilator implementuje double. Dopuszczalne jest, aby double i float były tego samego typu (i tak jest w niektórych systemach).

Biorąc to pod uwagę, jeśli rzeczywiście są różne, główną kwestią jest precyzja. Podwójna ma znacznie większą precyzję ze względu na różnicę w rozmiarze. Jeśli liczby, których używasz, zwykle przekraczają wartość zmiennoprzecinkową, użyj podwójnej.

Kilka innych osób wspomniało o problemach z wydajnością. To byłoby dokładnie ostatnie na mojej liście rozważań. Poprawność powinna być twoją najważniejszą kwestią.

JaredPar
źródło
2

Myślę, że niezależnie od różnic (które jak wszyscy podkreślają, pływaki zajmują mniej miejsca i są generalnie szybsze) ... czy ktoś kiedykolwiek miał problemy z wydajnością, używając double? Mówię, używaj podwójnego ... a jeśli później zdecydujesz "wow, to jest naprawdę wolne" ... znajdź wąskie gardło wydajności (co prawdopodobnie nie jest faktem, że użyłeś podwójnego). WTEDY, jeśli nadal jest dla Ciebie za wolny, zobacz, gdzie możesz poświęcić trochę precyzji i użyć spławika.

Tomek
źródło
1

W dużej mierze zależy to od procesora, a najbardziej oczywiste są kompromisy między precyzją a pamięcią. Przy GB pamięci RAM pamięć nie stanowi większego problemu, więc generalnie lepiej jest używać doubles.

Jeśli chodzi o wydajność, zależy to w dużym stopniu od procesora. floats zwykle uzyskuje lepszą wydajność niż doubles na komputerze 32-bitowym. Na 64 bitach doubles są czasami szybsze, ponieważ jest to (zwykle) rozmiar natywny. Jednak to, co będzie miało znacznie większe znaczenie niż wybór typów danych, to to, czy możesz skorzystać z instrukcji SIMD na swoim procesorze.

Zifre
źródło
0

double ma większą precyzję, podczas gdy zmiennoprzecinkowe zajmują mniej pamięci i są szybsze. Ogólnie rzecz biorąc, powinieneś używać float, chyba że masz przypadek, w którym nie jest wystarczająco dokładny.

Tal Pressman
źródło
5
Na typowych nowoczesnych komputerach podwojona jest tak samo szybka jak pływająca.
Thomas Padron-McCarthy