Potrzebuję prostej funkcji zaokrąglania zmiennoprzecinkowego, a zatem:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
Potrafię znaleźć ceil()
i floor()
w matematyce. H - ale nie round()
.
Czy jest obecny w standardowej bibliotece C ++ pod inną nazwą, czy go brakuje?
c++
floating-point
rounding
Roddy
źródło
źródło
std::cout << std::fixed << std::setprecision(0) << -0.9
na przykład.round
jest dostępny od C ++ 11 w<cmath>
. Niestety, jeśli korzystasz z Microsoft Visual Studio, nadal go brakuje: connect.microsoft.com/VisualStudio/feedback/details/775474/…round
ma wiele zastrzeżeń. Przed C ++ 11 standard opierał się na C90, który nie obejmowałround
. C ++ 11 opiera się na C99, który ma,round
ale także, jak zauważyłem, obejmuje,trunc
który ma różne właściwości i może być bardziej odpowiedni w zależności od zastosowania. Większość odpowiedzi wydaje się ignorować fakt, że użytkownik może chcieć zwrócić typ integralny, który ma jeszcze więcej problemów.Odpowiedzi:
W standardowej bibliotece C ++ 98 nie ma round (). Możesz jednak napisać jeden sam. Oto implementacja zaokrąglania do połowy :
Prawdopodobnym powodem, dla którego nie ma funkcji okrągłej w standardowej bibliotece C ++ 98, jest fakt, że można ją w rzeczywistości zaimplementować na różne sposoby. Powyższe jest jednym powszechnym sposobem, ale są też inne, takie jak zaokrąglanie do parzystości , które jest mniej stronnicze i ogólnie lepsze, jeśli zamierzasz dużo zaokrąglać; jest jednak nieco bardziej skomplikowany do wdrożenia.
źródło
Boost oferuje prosty zestaw funkcji zaokrąglania.
Aby uzyskać więcej informacji, zobacz dokumentację doładowania .
Edit : Ponieważ C ++ 11, istnieje
std::round
,std::lround
istd::llround
.źródło
floor(value + 0.5)
podejście!floor(value + 0.5)
.floor(value + 0.5)
to wcale nie jest naiwne, ale raczej zależy od kontekstu i natury wartości, które chcesz zaokrąglić!Standard C ++ 03 opiera się na standardzie C90, co standard nazywa Standardową Biblioteką C, która jest uwzględniona w projekcie standardu C ++ 03 ( najbliższym publicznie dostępnym projektem standardu do C ++ 03 jest N1804 ) sekcja
1.2
Odniesienia normatywne :Jeśli przejdziemy do dokumentacji C dla round, lround, llround na cppreference , zobaczymy, że round i powiązane funkcje są częścią C99, a zatem nie będą dostępne w C ++ 03 lub wcześniejszych.
W C ++ 11 zmienia się to, ponieważ C ++ 11 opiera się na standardzie roboczym C99 dla biblioteki standardowej C, a zatem zapewnia std :: round i dla integralnych typów zwracanych std :: lround, std :: llround :
Inną opcją również z C99 byłby std :: trunc, który:
Jeśli potrzebujesz obsługiwać aplikacje inne niż C ++ 11, najlepszym rozwiązaniem byłoby użycie opcji boost round, iround, lround, llround lub boost trunc .
Toczenie własnej wersji rundy jest trudne
Rzutowanie własnego prawdopodobnie nie jest warte wysiłku, ponieważ trudniejsze niż się wydaje: zaokrąglanie pływaka do najbliższej liczby całkowitej, część 1 , Zaokrąglanie pływaka do najbliższej liczby całkowitej, część 2 i Zaokrąglanie pływaka do najbliższej liczby całkowitej, część 3 wyjaśniają:
Na przykład wspólny rzut przy użyciu
std::floor
i dodawaniu implementacji0.5
nie działa dla wszystkich danych wejściowych:Jedno wejście, którego to nie powiedzie się, to
0.49999999999999994
( zobacz na żywo ).Inna powszechna implementacja polega na rzutowaniu typu zmiennoprzecinkowego na typ całkowy, który może wywoływać niezdefiniowane zachowanie w przypadku, gdy część integralna nie może być reprezentowana w typie docelowym. Widzimy to w części roboczej standardowej wersji C ++
4.9
Konwersje zmiennoprzecinkowe, która mówi ( wyróżnienie moje ):Na przykład:
Podane
std::numeric_limits<unsigned int>::max()
jest4294967295
zatem następujące wezwanie:spowoduje przepełnienie ( zobacz na żywo ).
Możemy zobaczyć, jak trudne jest to naprawdę, patrząc na tę odpowiedź na Zwięzły sposób implementacji round () w C? która odwołuje się do nowej wersji pływającej pojedynczej precyzji w newlibs . Jest to bardzo długa funkcja dla czegoś, co wydaje się proste. Wydaje się mało prawdopodobne, aby ktokolwiek bez ścisłej wiedzy o implementacjach zmiennoprzecinkowych mógł poprawnie wdrożyć tę funkcję:
Z drugiej strony, jeśli żadne z innych rozwiązań nie jest użyteczne, newlib może potencjalnie być opcją, ponieważ jest to dobrze przetestowane wdrożenie.
źródło
round(-0.0)
. Wygląda na to, że specyfikacja C nie wydaje się określać. Spodziewałbym-0.0
się w rezultacie.std::rint()
jest to często lepsze niż wstd::round()
przypadku, gdy C ++ 11 jest dostępny z powodów numerycznych i wydajnościowych. Wykorzystuje bieżący tryb zaokrąglania, w przeciwieństwie doround()
trybu specjalnego. Może być znacznie wydajniejszy na x86, gdzierint
może być dołączony do pojedynczej instrukcji. (gcc i clang robią to nawet bez-ffast-math
godbolt.org/g/5UsL2e , podczas gdy tylko clang wstawia prawie równoważnynearbyint()
) ARM ma obsługę pojedynczej instrukcjiround()
, ale na x86 może tylko inline z wieloma instrukcjami i tylko z-ffast-math
Warto zauważyć, że jeśli chcesz zaokrąglić liczbę całkowitą, nie musisz jej przechodzić przez sufit ani podłogę. To znaczy,
źródło
Jest dostępny od C ++ 11 w cmath (zgodnie z http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )
Wynik:
źródło
lround
illround
dla integralnych wynikówlrint
aby użyć bieżącego trybu zaokrąglania zamiastround
funky tie-off zera.Zwykle jest implementowany jako
floor(value + 0.5)
.Edycja: i prawdopodobnie nie jest nazywana zaokrąglaniem, ponieważ znam co najmniej trzy algorytmy zaokrąglania: zaokrąglanie do zera, zaokrąglanie do najbliższej liczby całkowitej i zaokrąglanie przez bankiera. Pytasz o zaokrąglenie do najbliższej liczby całkowitej.
źródło
Patrzymy na 2 problemy:
Przeliczanie zaokrągleń oznacza zaokrąglanie ± liczba zmiennoprzecinkowa / podwójna do najbliższej podłogi / liczba zmiennoprzecinkowa sufit / podwójna. Być może Twój problem się tutaj kończy. Ale jeśli oczekuje się zwrotu Int / Long, należy wykonać konwersję typu, a zatem problem „przepełnienia” może trafić w rozwiązanie. SO, sprawdź, czy nie ma błędów w swojej funkcji
z: http://www.cs.tut.fi/~jkorpela/round.html
źródło
LONG_MIN-0.5
iLONG_MAX+0.5
wprowadzanie komplikacji, ponieważ matematyka może nie być dokładna.LONG_MAX
może przekraczaćdouble
precyzję dla dokładnej konwersji. Ponadto prawdopodobnie chceszassert(x < LONG_MAX+0.5);
(<vs <=), ponieważLONG_MAX+0.5
może być dokładnie reprezentowalny i(x)+0.5
może mieć dokładny wynik,LONG_MAX+1
któregolong
rzutowanie nie powiedzie się . Inne problemy w rogu.round(double)
, istnieje już standardowa funkcja biblioteki matematycznej o tej nazwie (w C ++ 11), więc jest to mylące. Użyj,std::lrint(x)
jeśli jest dostępny.Pewny rodzaj zaokrąglania jest również zaimplementowany w Boost:
Pamiętaj, że działa to tylko wtedy, gdy wykonasz konwersję na liczbę całkowitą.
źródło
boost:numeric::RoundEven< double >::nearbyint
bezpośrednio, jeśli nie chcesz liczb całkowitych. @DanielWolf zauważa, że prosta funkcja jest implementowana za pomocą +0,5, który ma problemy określone przez aka.niceMożesz zaokrąglić do n cyfr z dokładnością:
źródło
int
. (W praktyce na x86, poza zakresem wartości PF będzieCVTTSD2SI
produktów0x80000000
jako bitów całkowita, to znaczyINT_MIN
, który następnie przekształcana z powrotem dodouble
,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óraround()
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()
istd::rint()
obie zostały dodane w C99 / C ++ 11, więc są dostępne w każdej chwilistd::round()
. Jedyną różnicą jest to, żenearbyint
nigdy nie podnosi FE_INEXACT.Preferuj
rint()
ze względu na wydajność : zarówno gcc, jak i clang wstawiają go łatwiej, ale gcc nigdy nie inlinenearbyint()
(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,
roundsd
może automatycznie wektoryzować w SSE4.1roundpd
(lub AVXvroundpd
). (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.)std::nearbyint()
:-msse4.1
.-msse4.1 -ffast-math
i tylko na gcc 5.4 i wcześniejszych . Później gcc nigdy go nie podkreśla (może nie zdawali sobie sprawy, że jeden z bezpośrednich bitów może stłumić niedokładny wyjątek? Tego właśnie używa clang, ale starszy gcc używa tego samego natychmiastowego, co w przypadku,rint
gdy to wstawia)std::rint
:-msse4.1
-msse4.1
. (Bez SSE4.1, inline do kilku instrukcji)-ffast-math -msse4.1
.std::round
:-ffast-math -msse4.1
, wymagając dwóch stałych wektorowych.std::floor
/std::ceil
/std::trunc
-msse4.1
-msse4.1
-ffast-math -msse4.1
Zaokrąglenie do
int
/long
/long long
:Masz tutaj dwie opcje: użyj
lrint
(jakrint
ale zwracalong
lublong long
dlallrint
) 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.Zauważ, że najpierw
int i = lrint(x)
konwertujefloat
lubdouble
->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_MIN
lubLLONG_MIN
(wzorzec bitowy0x8000000
lub odpowiednik 64-bitowy, tylko z ustawionym bitem znaku). Intel nazywa to „liczbą całkowitą nieokreśloną”. (Zobacz nacvttsd2si
rę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
unsigned
int / 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)rint
inlines toroundsd
/cvttsd2si
. (pominięto optymalizację docvtsd2si
).lrint
wcale nie inline.x86 gcc6.xi wcześniejsze bez
-ffast-math
: inline w żaden sposób-ffast-math
:(int/long)rint
zaokrągla i konwertuje osobno (z 2 całkowitymi instrukcjami SSE4.1 jest włączony, w przeciwnym razie z wiązką kodu wstawioną dlarint
bezroundsd
).lrint
nie inline.x86 gcc z
-ffast-math
: wszystkie sposoby docvtsd2si
(optymalne) , nie ma potrzeby SSE4.1.AArch64 gcc6.3 bez
-ffast-math
: wstawia(int/long)rint
do 2 instrukcji.lrint
nie inline-ffast-math
:(int/long)rint
kompiluje do połączenia zlrint
.lrint
nie inline. Może to być pominięta optymalizacja, chyba że dwie instrukcje, które otrzymujemy,-ffast-math
są bardzo wolne.źródło
rint()
gdzie jest to możliwy wybór, co zwykle ma miejsce.round()
Ta nazwa sugeruje niektórym programistom, że tego właśnie chcą, a jednocześnierint()
wydaje się tajemnicza. Pamiętaj, żeround()
nie używa trybu zaokrąglania „funky”: zaokrąglanie do najbliższych remisów jest oficjalnym trybem zaokrąglania IEEE-754 (2008). To ciekawe, żenearbyint()
się nie uwypukla, biorąc pod uwagę, że jest w dużej mierze takie samorint()
i powinno być identyczne w określonych-ffast-math
warunkach. Dla mnie to wygląda na błąd.Uważaj na
floor(x+0.5)
. Oto, co może się zdarzyć dla liczb nieparzystych w zakresie [2 ^ 52,2 ^ 53]:To jest http://bugs.squeak.org/view.php?id=7134 . Użyj rozwiązania takiego jak @konik.
Moja własna solidna wersja byłaby mniej więcej taka:
Innym powodem do podłogi lub ujemnych (x + 0,5) podano tutaj .
źródło
Jeśli ostatecznie chcesz przekonwertować dane
double
wyjściowe swojejround()
funkcji naint
, wówczas zaakceptowane rozwiązania tego pytania będą wyglądać mniej więcej tak:To osiąga około 8,88 ns na mojej maszynie, gdy zostanie przekazane równomiernie losowe wartości.
Poniższe informacje są funkcjonalnie równoważne, o ile mogę stwierdzić, ale na moim komputerze osiąga 2,48 ns , co zapewnia znaczną przewagę wydajności:
Jednym z powodów lepszej wydajności jest pominięcie rozgałęzienia.
źródło
int
. (W praktyce na x86 wartości FP poza zakresem powodują, żeCVTTSD2SI
produkuje się0x80000000
jako liczbę bitów całkowitych, tzn.INT_MIN
Która następnie zostanie przekonwertowana z powrotem nadouble
.Nie trzeba niczego implementować, więc nie jestem pewien, dlaczego tak wiele odpowiedzi wymaga definicji, funkcji lub metod.
W C99
Mamy następujące i nagłówek <tgmath.h> dla makr rodzajowych.
Jeśli nie możesz tego skompilować, prawdopodobnie pominąłeś bibliotekę matematyczną. Polecenie podobne do tego działa na każdym kompilatorze C, który mam (kilka).
W C ++ 11
Mamy następujące i dodatkowe przeciążenia w #include <cmath>, które opierają się na zmiennoprzecinkowym podwójnej precyzji IEEE.
Istnieją również odpowiedniki w przestrzeni nazw std .
Jeśli nie możesz tego skompilować, być może używasz kompilacji C zamiast C ++. Poniższe podstawowe polecenie nie powoduje błędów ani ostrzeżeń w wersjach g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 i Visual C ++ 2015 Community.
Z podziałem porządkowym
Przy dzieleniu dwóch liczb porządkowych, gdzie T jest krótkie, całkowite, długie lub inne porządkowe, wyrażenie zaokrąglające jest takie.
Precyzja
Nie ma wątpliwości, że dziwnie wyglądające niedokładności pojawiają się w operacjach zmiennoprzecinkowych, ale dzieje się tak tylko wtedy, gdy pojawiają się liczby i mają niewiele wspólnego z zaokrąglaniem.
Źródłem nie jest tylko liczba cyfr znaczących w mantyzie reprezentacji liczb zmiennoprzecinkowych IEEE, jest ona związana z naszym myśleniem dziesiętnym jako ludzi.
Dziesięć to iloczyn pięciu i dwóch, a 5 i 2 są względnie pierwsze. Dlatego standardy zmiennoprzecinkowe IEEE nie mogą być idealnie reprezentowane jako liczby dziesiętne dla wszystkich binarnych reprezentacji cyfrowych.
Nie jest to problem z algorytmami zaokrąglania. Jest to rzeczywistość matematyczna, którą należy wziąć pod uwagę przy wyborze typów i projektowaniu obliczeń, wprowadzaniu danych i wyświetlaniu liczb. Jeśli aplikacja wyświetla cyfry wskazujące te problemy z konwersją dziesiętno-binarną, oznacza to, że wizualnie wyraża dokładność, która nie istnieje w rzeczywistości cyfrowej i powinna zostać zmieniona.
źródło
Funkcja
double round(double)
z wykorzystaniemmodf
funkcji:Aby uzyskać czystą kompilację, konieczne są „math.h” i „limit”. Funkcja działa zgodnie z następującym schematem zaokrąglania:
źródło
rint()
lubnearbyint()
, ale jeśli naprawdę nie możesz użyć kompilatora, który zapewnia odpowiednią funkcję zaokrąglania i potrzebujesz precyzji bardziej niż wydajności ...Jeśli musisz mieć możliwość kompilacji kodu w środowiskach obsługujących standard C ++ 11, ale musisz także mieć możliwość kompilacji tego samego kodu w środowiskach, które go nie obsługują, możesz użyć makra funkcji, aby wybrać między standardowym :: round () i funkcja niestandardowa dla każdego systemu. Po prostu przekaż
-DCPP11
lub/DCPP11
do kompilatora zgodnego z C ++ 11 (lub użyj jego wbudowanych makr wersji) i utwórz taki nagłówek:Szybki przykład można znaleźć na stronie http://ideone.com/zal709 .
Przybliża to std :: round () w środowiskach, które nie są zgodne z C ++ 11, w tym zachowanie bitu znaku dla -0.0. Może to jednak spowodować niewielkie pogorszenie wydajności i prawdopodobnie będzie mieć problemy z zaokrąglaniem niektórych znanych zmiennych „zmiennoprzecinkowych”, takich jak 0,49999999999999994 lub podobnych wartości.
Alternatywnie, jeśli masz dostęp do kompilatora zgodnego z C ++ 11, możesz po prostu pobrać std :: round () z jego
<cmath>
nagłówka i użyć go do utworzenia własnego nagłówka, który definiuje funkcję, jeśli nie jest jeszcze zdefiniowana. Pamiętaj jednak, że może to nie być optymalne rozwiązanie, szczególnie jeśli potrzebujesz kompilować dla wielu platform.źródło
Na podstawie odpowiedzi Kalaxy, poniższe jest szablonowe rozwiązanie, które zaokrągla dowolną liczbę zmiennoprzecinkową do najbliższego typu liczby całkowitej na podstawie naturalnego zaokrąglenia. Zgłasza również błąd w trybie debugowania, jeśli wartość jest poza zakresem typu liczba całkowita, tym samym służąc z grubsza jako realna funkcja biblioteki.
źródło
0.5
nie działa we wszystkich przypadkach. Chociaż przynajmniej masz do czynienia z problemem przepełnienia, aby uniknąć niezdefiniowanego zachowania.Jak wskazano w komentarzach i innych odpowiedziach, standardowa biblioteka
round()
ISO C ++ została dodana dopiero w ISO C ++ 11, kiedy ta funkcja została wciągnięta przez odniesienie do standardowej biblioteki matematycznej ISO C99.Dla dodatnich argumentów operacji w [½, ub ]
round(x) == floor (x + 0.5)
, gdzie ub wynosi 2 23 dlafloat
po zmapowaniu do IEEE-754 (2008)binary32
i 2 52 dladouble
po odwzorowaniu na IEEE-754 (2008)binary64
. Liczby 23 i 52 odpowiadają liczbie przechowywanych bitów mantysy w tych dwóch formatach zmiennoprzecinkowych. Dla dodatnich argumentów w [+0, ½)round(x) == 0
i dodatnich argumentów w ( ub , + ∞]round(x) == x
. Ponieważ funkcja jest symetryczna względem osi x, argumenty ujemnex
mogą być obsługiwane zgodnie zround(-x) == -round(x)
.To prowadzi do kompaktowego kodu poniżej. Kompiluje się w rozsądną liczbę instrukcji maszynowych na różnych platformach. Zauważyłem najbardziej kompaktowy kod na GPU, gdzie
my_roundf()
wymaga kilkunastu instrukcji. W zależności od architektury procesora i łańcucha narzędzi takie podejście oparte na liczbach zmiennoprzecinkowych może być szybsze lub wolniejsze niż implementacja oparta na liczbach całkowitych z newlib, o której mowa w innej odpowiedzi .my_roundf()
Dokładnie przetestowałem pod kątemroundf()
implementacji newlib przy użyciu kompilatora Intel w wersji 13, zarówno z, jak/fp:strict
i/fp:fast
. Sprawdziłem również, czy wersja newlib jest zgodnaroundf()
zmathimf
biblioteką kompilatora Intel. Wyczerpujące testy nie są możliwe w przypadku podwójnej precyzjiround()
, jednak kod jest strukturalnie identyczny z implementacją pojedynczej precyzji.źródło
int
ma więcej niż 16 bitów. Oczywiście nadal zakłada, żefloat
jest to 4-bajtowy plik binarny IEEE75432. C ++ 11static_assert
lub może makro#ifdef
/#error
może to sprawdzić. (Ale oczywiście, jeśli C ++ 11 jest dostępny, powinieneś użyćstd::round
, lub do bieżącego trybu zaokrąglania,std::rint
który ładnie współgra z gcc i clang).gcc -ffast-math -msse4.1
wstawiastd::round()
doadd( AND(x, L1), OR(x,L2)
, a następnie aroundsd
. tzn. dość skutecznie wdrażaround
pod względemrint
. Ale nie ma powodu, aby robić to ręcznie w źródle C ++, ponieważ jeśli maszstd::rint()
lubstd::nearbyint()
masz równieżstd::round()
. Zobacz moją odpowiedź na godbolt link i podsumowanie tego, co inline lub nie z różnymi wersjami gcc / clang.round()
efektywnie wdrażać pod względemrint()
(gdy ten ostatni działa w trybie od okrągłego do najbliższego lub nawet): zaimplementowałem to dla standardowej biblioteki matematycznej CUDA. Jednak pytanie to wydawało się zadawać pytanie, jak zaimplementowaćround()
w C ++ wcześniejszym niż C ++ 11, więcrint()
nie będzie dostępne, tylkofloor()
iceil()
.round()
jest łatwo zsyntetyzować zerint()
w okrągłym do zera trybie akatrunc()
. Nie powinienem był odpowiedzieć przed pierwszą kawą.round()
; większość programistów po prostu nie zdaje sobie sprawy z rozróżnienia międzyround()
vsrint()
a round-to-close-even, przy czym ten ostatni jest zwykle zapewniany bezpośrednio przez sprzęt, a zatem bardziej wydajny; Przeliterowałem to w Przewodniku programowania CUDA, aby uświadomić programistom: „Zalecanym sposobem na zaokrąglenie argumentu zmiennoprzecinkowego pojedynczej precyzji do liczby całkowitej, z wynikiem w postaci liczby zmiennoprzecinkowej pojedynczej precyzjirintf()
, jestroundf()
„ nie ”.Korzystam z następującej implementacji round w asm dla architektury x86 i specyficznego dla MS VS C ++:
UPD: aby zwrócić podwójną wartość
Wynik:
źródło
rint()
lubnearbyint()
doroundsd
instrukcji SSE4.1 lub instrukcji x87frndint
, która będzie znacznie szybsza niż dwie podróże w obie strony niezbędne do użycia tego wbudowanego asm na danych w rejestrze. MSM inline asm jest do bani, jeśli chodzi o zawijanie pojedynczych instrukcji,frndint
ponieważ nie ma sposobu, aby uzyskać dane wejściowe do rejestru. Użycie go na końcu funkcji z wynikiemst(0)
może być niezawodne jako sposób na zwrócenie wyjścia; najwyraźniej jest to bezpieczne dlaeax
liczb całkowitych, nawet jeśli wstawia funkcję zawierającą asm.double
, a zatem powinien mócfrndint
sam się wyemitowaćrint()
. Jeśli twój kompilator używa SSE2, odesłanie adouble
z rejestru XMM do x87 i wstecz może nie być tego warte.Najlepszym sposobem na zaokrąglenie wartości zmiennoprzecinkowej o miejsca dziesiętne „n” jest użycie czasu O (1): -
Wartość należy zaokrąglić o 3 miejsca, tj. N = 3, więc
źródło
Może to być nieefektywny brudny sposób konwersji, ale do cholery, działa lol. I to dobrze, ponieważ dotyczy rzeczywistej liczby zmiennoprzecinkowej. Nie tylko wizualnie wpływa na wydajność.
źródło
Ja to zrobiłem:
źródło