Obecnie nie powinno być problemu z użyciem kompilatora C ++ 11, który zawiera bibliotekę matematyczną C99 / C ++ 11. Ale wtedy pojawia się pytanie: jaką funkcję zaokrąglania wybierasz?
C99 / C ++ 11 round()często nie jest tak naprawdę funkcją zaokrąglania, którą chcesz . Wykorzystuje funky tryb zaokrąglania, który zaokrągla wartość od 0 jako remis w przypadkach w połowie drogi ( +-xxx.5000). Jeśli szczególnie chcesz ten tryb zaokrąglania lub celujesz w implementację C ++, która round()jest szybsza niż rint(), użyj go (lub naśladuj jego zachowanie z jedną z pozostałych odpowiedzi na to pytanie, która uznała go za wartościowy i dokładnie odtworzyła ten konkretny zaokrąglanie).
round()Zaokrąglanie różni się od domyślnej zaokrąglenia IEEE754 do najbliższego trybu, nawet jako rozstrzygnięcie remisu . Najbliższy - nawet unika statystycznego błędu w średniej wielkości liczb, ale powoduje odchylenie w kierunku liczb parzystych.
Istnieją dwie funkcje zaokrąglania biblioteki matematycznej, które używają bieżącego domyślnego trybu zaokrąglania: std::nearbyint()i std::rint()obie zostały dodane w C99 / C ++ 11, więc są dostępne w każdej chwili std::round(). Jedyną różnicą jest to, że nearbyintnigdy nie podnosi FE_INEXACT.
Preferuj rint()ze względu na wydajność : zarówno gcc, jak i clang wstawiają go łatwiej, ale gcc nigdy nie inline nearbyint()(nawet z -ffast-math)
gcc / clang dla x86-64 i AArch64
Umieściłem niektóre funkcje testowe w Eksploratorze kompilatorów Matta Godbolta , gdzie można zobaczyć źródło + wyjście asm (dla wielu kompilatorów). Więcej informacji na temat czytania wyjście kompilatora, zobacz ten Q & A i Matta CppCon2017 Dyskusja: „Co ma moje Compiler dla mnie zrobił ostatnio? Odblokowanie pokrywy kompilatora ” ,
W kodzie FP zwykle wstawianie małych funkcji jest zwykle dużą wygraną. Zwłaszcza w systemach innych niż Windows, w których standardowa konwencja wywoływania nie ma rejestrów zachowanych połączeń, więc kompilator nie może przechowywać żadnych wartości FP w rejestrach XMM w całym call. Więc nawet jeśli tak naprawdę nie znasz asm, nadal możesz łatwo sprawdzić, czy jest to tylko wywołanie ogonowe funkcji bibliotecznej, czy też jest ono powiązane z jedną lub dwiema instrukcjami matematycznymi. Wszystko, co wpisuje się w jedną lub dwie instrukcje, jest lepsze niż wywołanie funkcji (dla tego konkretnego zadania na x86 lub ARM).
Na x86 wszystko, co wpisuje się w SSE4.1, roundsdmoże automatycznie wektoryzować w SSE4.1 roundpd(lub AVX vroundpd). (Konwersje FP-> liczby całkowite są również dostępne w postaci spakowanej karty SIMD, z wyjątkiem FP-> 64-bitowa liczba całkowita, która wymaga AVX512.)
Zaokrąglenie do int/ long/ long long:
Masz tutaj dwie opcje: użyj lrint(jak rintale zwraca longlub long longdla llrint) lub użyj funkcji zaokrąglania FP-> FP, a następnie przekonwertuj na typ całkowity w zwykły sposób (z obcięciem). Niektóre kompilatory optymalizują jeden sposób lepiej niż drugi.
long l = lrint(x);
int i = (int)rint(x);
Zauważ, że najpierw int i = lrint(x)konwertuje floatlub double-> long, a następnie obcina liczbę całkowitą int. To robi różnicę dla liczb całkowitych poza zasięgiem: Niezdefiniowane zachowanie w C ++, ale dobrze zdefiniowane dla instrukcji x86 FP -> int (które wyemituje kompilator, chyba że zobaczy UB w czasie kompilacji podczas ciągłej propagacji, to jest to wolno tworzyć kod, który pęka, jeśli zostanie kiedykolwiek wykonany).
Na x86 konwersja liczb całkowitych FP->, która przepełnia liczbę całkowitą, daje INT_MINlub LLONG_MIN(wzorzec bitowy 0x8000000lub odpowiednik 64-bitowy, tylko z ustawionym bitem znaku). Intel nazywa to „liczbą całkowitą nieokreśloną”. (Zobacz na cvttsd2siręczne wprowadzanie , instrukcja SSE2, który konwertuje (z obcinania) skalarne dwukrotnie do podpisanego całkowitej. Jest ona dostępna z 32-bitową lub 64-bitową docelowego Integer (tylko w trybie 64-bitowym). Jest też cvtsd2si(konwersja z aktualnym zaokrąglania tryb), co chcielibyśmy, aby kompilator wyemitował, ale niestety bez gcc i clang nie zrobi tego -ffast-math.
Uważaj również, że FP do / z unsignedint / long jest mniej wydajny na x86 (bez AVX512). Konwersja do wersji 32-bitowej bez znaku na komputerze 64-bitowym jest dość tania; wystarczy przekonwertować na 64-bitowy podpisany i obciąć. Ale poza tym jest znacznie wolniejszy.
x86 clang z / bez -ffast-math -msse4.1: (int/long)rintinlines to roundsd/ cvttsd2si. (pominięto optymalizację do cvtsd2si). lrintwcale nie inline.
x86 gcc6.xi wcześniejsze bez -ffast-math: inline w żaden sposób
- x86 gcc7 bez
-ffast-math: (int/long)rintzaokrągla i konwertuje osobno (z 2 całkowitymi instrukcjami SSE4.1 jest włączony, w przeciwnym razie z wiązką kodu wstawioną dla rintbez roundsd). lrintnie inline.
x86 gcc z -ffast-math : wszystkie sposoby do cvtsd2si(optymalne) , nie ma potrzeby SSE4.1.
AArch64 gcc6.3 bez -ffast-math: wstawia (int/long)rintdo 2 instrukcji. lrintnie inline
- AArch64 gcc6.3 z
-ffast-math: (int/long)rintkompiluje do połączenia z lrint. lrintnie inline. Może to być pominięta optymalizacja, chyba że dwie instrukcje, które otrzymujemy, -ffast-mathsą bardzo wolne.
std::cout << std::fixed << std::setprecision(0) << -0.9na przykład.