Czy powinniśmy ogólnie używać liter float dla pływaków zamiast prostszych podwójnych liter?

W C++ (a może tylko nasze Kompilatory VC8 i VC10)3.14 jest podwójnym literałem, a {[3] } jest literałem float.

Teraz mam kolegę, który stwierdził:

Powinniśmy używać float-literałów do obliczeń float i podwójnych liter do podwójnych obliczeń, ponieważ może to mieć wpływ na precyzję obliczeń, gdy stałe są używane w obliczeniach.

Konkretnie, myślę, że miał na myśli:

double d1, d2;
float f1, f2;
... init and stuff ...
f1 = 3.1415  * f2;
f1 = 3.1415f * f2; // any difference?
d1 = 3.1415  * d2;
d1 = 3.1415f * d2; // any difference?

Lub, dodany przeze mnie, parzyste:

d1 = 42    * d2;
d1 = 42.0f * d2; // any difference?
d1 = 42.0  * d2; // any difference?

Bardziej ogólnie rzecz biorąc, jedynym punktem , który widzę przy użyciu 2.71828183f, jest upewnienie się, że stała, którą próbuję określić, rzeczywiście zmieści się w float (błąd kompilatora/ostrzeżenie w przeciwnym razie).

Czy ktoś może rzucić na to trochę światła? Czy podajesz f postfix? Dlaczego?

Cytuję z odpowiedzi, co uznałem za oczywiste:

Jeśli pracujesz ze zmienną zmiennoprzecinkową i podwójnym literałem, całość operacja zostanie wykonana jako podwoić, a następnie przekształcić z powrotem do float.

Czy może być w tym coś złego? (Inny niż bardzo, bardzo teoretyczny wpływ wydajności?)

Dalsza edycja: byłoby miło, gdyby odpowiedzi zawierające szczegóły techniczne (docenione!)może również obejmować, w jaki sposób te różnice wpływają na kod ogólnego przeznaczenia . (Tak, jeśli jesteś number crunching, prawdopodobnie chcesz się upewnić, że twoje operacje zmiennoprzecinkowe big-n są tak skuteczne (i poprawne), jak to tylko możliwe -- ale czy to ma znaczenie dla kodu ogólnego przeznaczenia, który jest wywoływany kilka razy? Czy nie jest czystsze, jeśli kod po prostu używa 0.0 i pomija -- trudne do utrzymania! -- przyrostek float?)

Author: Deduplicator, 2011-10-05

7 answers

Tak, powinieneś użyć przyrostka f. Powody:

  1. Wydajność. Kiedy piszesz float foo(float x) { return x*3.14; }, zmuszasz kompilator do emitowania kodu, który konwertuje x na podwójne, następnie mnożenie, a następnie konwertuje wynik z powrotem na pojedynczy. Jeśli dodasz przyrostek f, wtedy obie konwersje zostaną wyeliminowane. Na wielu platformach każda z tych konwersji jest tak samo kosztowna jak samo mnożenie.

  2. Wydajność (ciąg dalszy). Znajdują się tu perony (większość np. komórki), na których arytmetyka podwójnej precyzji jest znacznie wolniejsza od pojedynczej precyzji. Nawet ignorując konwersję (ujętą w 1.), za każdym razem, gdy wymusisz podwójną ocenę obliczeń, spowolnisz program. Nie jest to tylko kwestia "teoretyczna".

  3. Zmniejsz narażenie na błędy. Rozważmy przykład float x = 1.2; if (x == 1.2) // something; Czy something został wykonany? Nie, Nie jest, ponieważ X posiada 1.2 zaokrąglone do float, ale jest porównywane do wartość podwójnej precyzji 1.2. Te dwa nie są sobie równe.

 46
Author: Stephen Canon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 14:21:15

Podejrzewam coś takiego: jeśli pracujesz ze zmienną float i podwójnym literałem, cała operacja zostanie wykonana jako Podwójna, a następnie przekonwertowana z powrotem do float.

Jeśli używasz literału float, teoretycznie rzecz biorąc obliczenia będą wykonywane z precyzją float, nawet jeśli niektóre urządzenia i tak przekonwertują je na podwójne, aby wykonać obliczenia.

 9
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 13:41:21

Zrobiłem test.

Skompilowałem ten kod:

float f1(float x) { return x*3.14; }            
float f2(float x) { return x*3.14F; }   

Użycie gcc 4.5.1 Dla i686 z optimization-O2.

To był Kod assembly wygenerowany dla f1:

pushl   %ebp
movl    %esp, %ebp
subl    $4, %esp # Allocate 4 bytes on the stack
fldl    .LC0     # Load a double-precision floating point constant
fmuls   8(%ebp)  # Multiply by parameter
fstps   -4(%ebp) # Store single-precision result on the stack
flds    -4(%ebp) # Load single-precision result from the stack
leave
ret

A to jest kod assembly wygenerowany dla f2:

pushl   %ebp
flds    .LC2          # Load a single-precision floating point constant
movl    %esp, %ebp
fmuls   8(%ebp)       # Multiply by parameter
popl    %ebp
ret

Interesujące jest to, że dla f1 kompilator zapisał wartość i załadował ją ponownie, aby upewnić się, że wynik został obcięty do pojedynczej precyzji.

Jeśli użyjemy opcji-ffast-math, to ta różnica jest znacznie zmniejszona:

pushl   %ebp
fldl    .LC0             # Load double-precision constant
movl    %esp, %ebp
fmuls   8(%ebp)          # multiply by parameter
popl    %ebp
ret


pushl   %ebp
flds    .LC2             # Load single-precision constant
movl    %esp, %ebp
fmuls   8(%ebp)          # multiply by parameter
popl    %ebp
ret

Ale nadal istnieje różnica między załadowaniem pojedynczej lub podwójnej stałej precyzji.

Aktualizacja dla 64-bitowych

Oto wyniki z gcc 5.2.1 Dla x86-64 Z optimization-O2:

F1:

cvtss2sd  %xmm0, %xmm0       # Convert arg to double precision
mulsd     .LC0(%rip), %xmm0  # Double-precision multiply
cvtsd2ss  %xmm0, %xmm0       # Convert to single-precision
ret

F2:

mulss     .LC2(%rip), %xmm0  # Single-precision multiply
ret

Z -ffast-math, wyniki są takie same.

 6
Author: Vaughn Cato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-16 13:03:26

Zazwyczaj nie sądzę, aby to miało jakąś różnicę, ale warto wskazując, że 3.1415f i 3.1415 są (zazwyczaj) nie równe. On z drugiej strony, normalnie nie robisz żadnych obliczeń w float w każdym razie, przynajmniej na zwykłych platformach. (double jest tak samo szybki, jeśli nie szybciej.) Mniej więcej jedyny czas, kiedy powinieneś zobaczyć float jest wtedy, gdy są duże tablice, a nawet wtedy wszystkie obliczenia będą zazwyczaj być wykonane w double.

 3
Author: James Kanze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 14:05:16

Jest różnica: jeśli użyjesz podwójnej stałej i pomnożysz ją ze zmienną zmiennoprzecinkową, zmienna jest najpierw zamieniona na podwójną, obliczenia są wykonywane na podwójną, a następnie wynik jest zamieniony na zmiennoprzecinkową. Chociaż precyzja nie jest tu problemem, może to prowadzić do zamieszania.

 1
Author: thiton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 13:41:00

Ja osobiście używam notacji f postfix jako zasady i aby było oczywiste, jak tylko mogę, że jest to typ float, a nie double.

Moje dwa grosze

 1
Author: Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 13:52:29

Ze standardu C++ (roboczy szkic) , Sekcja 5 o operatorach binarnych

Wiele operatorów binarnych, które oczekują operandów arytmetyki lub Typ wyliczenia powoduje konwersje i daje typy wyników w podobnym sposób. Celem jest uzyskanie wspólnego typu, który jest również rodzajem wynik. Wzorzec ten nazywa się zwykłymi konwersjami arytmetycznymi, które są zdefiniowane w następujący sposób: - jeżeli któryś z operandów ma zakres Typ wyliczenia (7.2) , nie ma konwersji wykonywana; jeżeli druga operand nie ma tego samego typu, wyrażenie jest źle uformowane. - Jeżeli jeden z operandów jest typu long double, drugi jest konwertowany za long double. - W przeciwnym razie, jeśli jeden z operandów jest podwójny, drugi przelicza się na podwójne. - W przeciwnym razie, jeśli któryś z operandów jest float, drugi przekształca się w pływak.

Oraz punkt 4.8

Wartość prvalue typu zmiennoprzecinkowego może być przekonwertowana na wartość prvalue o wartości inny zmiennoprzecinkowy Typ. Jeśli wartość źródłowa może być dokładnie reprezentowany w typie docelowym, wynikiem konwersji jest to dokładne przedstawienie. Jeśli wartość źródłowa znajduje się pomiędzy dwoma sąsiadującymi wartości docelowych, wynikiem konwersji jest implementacja-zdefiniowany wybór jednej z tych wartości. W przeciwnym razie behavior is undefined

Wynikiem tego jest to, że można uniknąć niepotrzebnych konwersji, określając swoje stałe w precyzji podyktowanej przez miejsce docelowe wpisz, pod warunkiem, że nie stracisz precyzji w obliczeniach w ten sposób (tzn. Twoje operandy są dokładnie reprezentowalne w precyzji typu docelowego ).

 1
Author: Andrew Marshall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 13:57:24