round () dla float w C ++


232

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?


1
Jeśli chcesz tylko podać liczbę jako zaokrągloną, wydaje się, że możesz to zrobić std::cout << std::fixed << std::setprecision(0) << -0.9na przykład.
Frank

43
Ochrona tego ... Nowi użytkownicy ze wspaniałymi nowymi schematami zaokrąglania powinni najpierw przeczytać istniejące odpowiedzi.
Shog9

12
roundjest 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/…
Alessandro Jacopson

3
Jak zauważam w mojej odpowiedzi, rzucanie własnymi roundma 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, roundale także, jak zauważyłem, obejmuje, truncktó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.
Shafik Yaghmour

2
@ uvts_cvs nie wydaje się, aby był to problem z najnowszą wersją visual studio, zobacz na żywo .
Shafik Yaghmour

Odpowiedzi:


144

W standardowej bibliotece C ++ 98 nie ma round (). Możesz jednak napisać jeden sam. Oto implementacja zaokrąglania do połowy :

double round(double d)
{
  return floor(d + 0.5);
}

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.


53
To nie obsługuje poprawnie liczb ujemnych. Odpowiedź litb jest prawidłowa.
Zarejestrowany użytkownik

39
@InnerJoin: Tak, obsługuje liczby ujemne inaczej niż odpowiedź litba, ale nie oznacza to, że jest „niepoprawny”.
Roddy

39
Dodanie 0,5 przed obcięciem nie powoduje zaokrąglenia do najbliższej liczby całkowitej dla kilku danych wejściowych, w tym 0,49999999999999994. Zobacz blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq

10
@ Sergi0: Nie ma „poprawnych” i „niepoprawnych”, ponieważ istnieje więcej niż jedna definicja zaokrąglenia, która decyduje o tym, co stanie się w połowie. Sprawdź swoje fakty przed wydaniem wyroku.
Jon

16
@MuhammadAnnaqeeb: Masz rację, rzeczy uległy ogromnej poprawie od czasu wydania C ++ 11. To pytanie zostało zadane i udzielono na nie odpowiedzi w innym czasie, gdy życie było ciężkie, a radości było niewiele. Pozostaje tutaj jako oda dla bohaterów, którzy wtedy żyli i walczyli, oraz dla tych biednych dusz, które wciąż nie są w stanie korzystać z nowoczesnych narzędzi.
Andreas Magnusson

96

Boost oferuje prosty zestaw funkcji zaokrąglania.

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

Aby uzyskać więcej informacji, zobacz dokumentację doładowania .

Edit : Ponieważ C ++ 11, istnieje std::round, std::lroundistd::llround .


2
Już użyłem wzmocnienia w moim projekcie, +1 do tego, znacznie lepiej niż naiwne floor(value + 0.5)podejście!
Gustavo Maciel

@GustavoMaciel Wiem, że jestem trochę spóźniony do gry, ale przyspieszenie wdrożenia jest floor(value + 0.5).
n. „zaimki” m.

W rzeczywistości tak nie jest: github.com/boostorg/math/blob/develop/include/boost/math/... 4 lata później, chciałbym również powiedzieć, że floor(value + 0.5)to wcale nie jest naiwne, ale raczej zależy od kontekstu i natury wartości, które chcesz zaokrąglić!
Gustavo Maciel

84

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 :

Biblioteka opisana w klauzuli 7 ISO / IEC 9899: 1990 i klauzuli 7 ISO / IEC 9899 / Amd.1: 1995 jest dalej zwana Standardową Biblioteką C. 1)

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 :

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

Inną opcją również z C99 byłby std :: trunc, który:

Oblicza najbliższą liczbę całkowitą nie większą niż arg.

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

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::floori dodawaniu implementacji 0.5nie działa dla wszystkich danych wejściowych:

double myround(double d)
{
  return std::floor(d + 0.5);
}

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 ):

Wartość zmiennoprzecinkowa może zostać przekonwertowana na wartość całkowitą. Konwersja zostaje obcięta; to znaczy część ułamkowa jest odrzucana. Zachowanie jest niezdefiniowane, jeśli nie można przedstawić skróconej wartości w typie docelowym. [...]

Na przykład:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

Podane std::numeric_limits<unsigned int>::max()jest 4294967295zatem następujące wezwanie:

myround( 4294967296.5f ) 

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ę:

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

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.


5
@ downvoter proszę wyjaśnić, co można poprawić? Ogromna większość odpowiedzi tutaj jest po prostu błędna, ponieważ próbują przetoczyć własną rundę, która zawodzi w tej czy innej formie. Jeśli w moim wyjaśnieniu brakuje czegoś, proszę dać mi znać.
Shafik Yaghmour

1
Ładna kompletna odpowiedź - szczególnie część poniżej 0,5. Innym niszowych: round(-0.0). Wygląda na to, że specyfikacja C nie wydaje się określać. Spodziewałbym -0.0się w rezultacie.
chux - Przywróć Monikę

3
@chux ciekawe, a standard IEEE 754-2008 określa, że ​​zaokrąglanie zachowuje znaki zer i nieskończoności (patrz 5.9).
Ruslan

1
@Shafik to świetna odpowiedź. Nigdy nie myślałem, że nawet zaokrąglanie jest operacją nietrywialną.
Ruslan

1
Być może warto wspomnieć, że std::rint()jest to często lepsze niż w std::round()przypadku, gdy C ++ 11 jest dostępny z powodów numerycznych i wydajnościowych. Wykorzystuje bieżący tryb zaokrąglania, w przeciwieństwie do round()trybu specjalnego. Może być znacznie wydajniejszy na x86, gdzie rintmoż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żny nearbyint()) ARM ma obsługę pojedynczej instrukcji round(), ale na x86 może tylko inline z wieloma instrukcjami i tylko z-ffast-math
Peter Cordes

71

Warto zauważyć, że jeśli chcesz zaokrąglić liczbę całkowitą, nie musisz jej przechodzić przez sufit ani podłogę. To znaczy,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}

3
Nie daje jednak oczekiwanego wyniku dla 0,4999999999999999994 (cóż, w zależności od tego, czego się oczywiście spodziewasz, ale 0 wydaje mi się bardziej rozsądne niż 1)
stijn

@stijn Good catch. Odkryłem, że dodanie długiego podwójnego sufiksu literału do moich stałych naprawiło twój przykładowy problem, ale nie wiem, czy istnieją inne precyzyjne przykłady, których nie mógł złapać.
kalaxy

1
btw, jeśli dodasz 0,49999999999999994 zamiast 0,5, działa to poprawnie zarówno dla 0,49999999999999994, jak i 5000000000000001.0 jako danych wejściowych. Nie jestem jednak pewien, czy jest w porządku dla wszystkich wartości i nie mogłem znaleźć żadnego odniesienia stwierdzającego, że jest to ostateczna poprawka.
stijn

1
@stijn To jest w porządku dla wszystkich wartości, jeśli nie obchodzi cię, w którym kierunku wartości między dwiema liczbami całkowitymi są zaokrąglane. Bez zastanowienia udowodnię to poprzez analizę przypadków w następujących przypadkach: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. Dokładnie przetestowałem też obudowę o pojedynczej precyzji.
Pascal Cuoq

3
Zgodnie z 4.9 [conv.fpint] „Zachowanie jest niezdefiniowane, jeśli obciętej wartości nie można przedstawić w typie docelowym”. , więc jest to trochę niebezpieczne. Inne odpowiedzi SO opisują, jak to zrobić solidnie.
Tony Delroy

41

Jest dostępny od C ++ 11 w cmath (zgodnie z http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

Wynik:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2

1
są też lroundi llrounddla integralnych wyników
sp2danny

@ sp2danny: lub lepiej, lrintaby użyć bieżącego trybu zaokrąglania zamiast roundfunky tie-off zera.
Peter Cordes,

27

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.


1
Dobrze jest rozróżniać różne wersje „okrągłych”. Dobrze jest też wiedzieć, kiedy wybrać.
xtofl

5
Rzeczywiście istnieją różne algorytmy zaokrąglania, które mogą uzasadniać twierdzenie, że są „poprawne”. Jednak podłoga (wartość + 0,5) nie jest jedną z nich. W przypadku niektórych wartości, takich jak 0,49999997f lub równoważne podwójne, odpowiedź jest po prostu błędna - zostanie zaokrąglona do 1,0, gdy wszyscy zgodzą się, że powinna wynosić zero. Zobacz ten post, aby uzyskać szczegółowe informacje: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson

14

Patrzymy na 2 problemy:

  1. zaokrąglanie konwersji
  2. konwersja typu.

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

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

z: http://www.cs.tut.fi/~jkorpela/round.html


Używanie LONG_MIN-0.5i LONG_MAX+0.5 wprowadzanie komplikacji, ponieważ matematyka może nie być dokładna. LONG_MAXmoże przekraczać doubleprecyzję dla dokładnej konwersji. Ponadto prawdopodobnie chcesz assert(x < LONG_MAX+0.5); (<vs <=), ponieważ LONG_MAX+0.5może być dokładnie reprezentowalny i (x)+0.5może mieć dokładny wynik, LONG_MAX+1którego longrzutowanie nie powiedzie się . Inne problemy w rogu.
chux - Przywróć Monikę

Nie wywoływaj swojej funkcji 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.
Peter Cordes,

11

Pewny rodzaj zaokrąglania jest również zaimplementowany w Boost:

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

Pamiętaj, że działa to tylko wtedy, gdy wykonasz konwersję na liczbę całkowitą.


2
Boost oferuje również zestaw prostych funkcji zaokrąglania; zobacz moją odpowiedź.
Daniel Wolf

Możesz także użyć boost:numeric::RoundEven< double >::nearbyintbezpoś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.nice
stijn

6

Możesz zaokrąglić do n cyfr z dokładnością:

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}

4
Jeśli domyślny rozmiar twojego kompilatora nie będzie
wynosił

Myślę, że jest to dopuszczalne, biorąc pod uwagę, kiedy zostanie użyte: Jeśli twoja podwójna wartość wynosi 1,0 e + 19, zaokrąglenie do 3 miejsc nie ma sensu.
Carl

3
jasne, ale pytanie dotyczy ogólnej rundy i nie można kontrolować, w jaki sposób będzie on używany. Nie ma powodu, by runda zawiodła, gdyby nie sufit i podłoga.
aka.nice

Nieokreślone zachowanie dla argumentów spoza zakresu int. (W praktyce na x86, poza zakresem wartości PF będzie CVTTSD2SIproduktów0x80000000 jako bitów całkowita, to znaczy INT_MIN, który następnie przekształcana z powrotem do double,
Peter Cordes

5

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.)

  • std::nearbyint():

    • x86 clang: wstawia do jednej insn z -msse4.1.
    • x86 gcc: wstawia do jednej insn tylko z -msse4.1 -ffast-mathi 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, rintgdy to wstawia)
    • AArch64 gcc6.3: domyślnie jest wstawiany do jednej insn.
  • std::rint:

    • x86 clang: wstawia do jednej insn z -msse4.1
    • x86 gcc7: wstawia do jednej insn za pomocą -msse4.1. (Bez SSE4.1, inline do kilku instrukcji)
    • x86 gcc6.xi wcześniejsze: wstawia do jednej insn za pomocą -ffast-math -msse4.1.
    • AArch64 gcc: domyślnie jest wstawiany do jednej insn
  • std::round:

    • Clang x86: nie inline
    • x86 gcc: wstawia do wielu instrukcji za pomocą -ffast-math -msse4.1, wymagając dwóch stałych wektorowych.
    • AArch64 gcc: wstawia do pojedynczej instrukcji (obsługa HW dla tego trybu zaokrąglania, jak również domyślna IEEE i większość innych).
  • std::floor/ std::ceil/std::trunc

    • x86 clang: wstawia do jednej insn z -msse4.1
    • x86 gcc7.x: wstawia do jednej insn za pomocą -msse4.1
    • x86 gcc6.xi wcześniejsze: wstawia do jednej insn za pomocą -ffast-math -msse4.1
    • AArch64 gcc: domyślnie wstawia pojedynczą instrukcję

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.

DO ZROBIENIA: ICC i MSVC są również dostępne na Godbolt, ale nie sprawdziłem ich wyników w tym zakresie. edycje mile widziane ... Także: czy bardziej użyteczne byłoby podzielenie według kompilatora / wersji, a następnie funkcji w tym zakresie? Większość ludzi nie zamierza przełączać kompilatorów na podstawie tego, jak dobrze kompilują zaokrąglanie liczb całkowitych FP-> FP lub FP->.
Peter Cordes,

2
+1 za polecenie, 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śnie rint()wydaje się tajemnicza. Pamiętaj, że round()nie używa trybu zaokrąglania „funky”: zaokrąglanie do najbliższych remisów jest oficjalnym trybem zaokrąglania IEEE-754 (2008). To ciekawe, że nearbyint()się nie uwypukla, biorąc pod uwagę, że jest w dużej mierze takie samo rint()i powinno być identyczne w określonych -ffast-mathwarunkach. Dla mnie to wygląda na błąd.
njuffa

4

Uważaj na floor(x+0.5). Oto, co może się zdarzyć dla liczb nieparzystych w zakresie [2 ^ 52,2 ^ 53]:

-bash-3.2$ cat >test-round.c <<END

#include <math.h>
#include <stdio.h>

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

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:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

Innym powodem do podłogi lub ujemnych (x + 0,5) podano tutaj .


2
Chcę wiedzieć o głosach negatywnych. Czy to dlatego, że remis jest rozstrzygany od zera, a nie do najbliższego parzystego?
aka.nice

1
Uwaga: specyfikacja C mówi „zaokrąglanie w połowie przypadków od zera, niezależnie od bieżącego kierunku zaokrąglania.”, Więc zaokrąglanie bez względu na nieparzyste / parzyste jest zgodne.
chux - Przywróć Monikę

4

Jeśli ostatecznie chcesz przekonwertować dane doublewyjściowe swojej round()funkcji na int, wówczas zaakceptowane rozwiązania tego pytania będą wyglądać mniej więcej tak:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

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:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

Jednym z powodów lepszej wydajności jest pominięcie rozgałęzienia.


Nieokreślone zachowanie dla argumentów spoza zakresu int. (W praktyce na x86 wartości FP poza zakresem powodują, że CVTTSD2SIprodukuje się0x80000000 jako liczbę bitów całkowitych, tzn. INT_MINKtóra następnie zostanie przekonwertowana z powrotem na double.
Peter Cordes,

2

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.

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

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).

gcc -lm -std=c99 ...

W C ++ 11

Mamy następujące i dodatkowe przeciążenia w #include <cmath>, które opierają się na zmiennoprzecinkowym podwójnej precyzji IEEE.

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

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.

g++ -std=c++11 -Wall

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.

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

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.


1
„Nie jestem pewien, dlaczego tak wiele odpowiedzi wymaga definicji, funkcji lub metod”. Spójrz, kiedy został zapytany - C ++ 11 jeszcze nie było dostępne. ;)
jaggedSpire

@jaggedSpire, daj mi kciuki do góry, jeśli uważasz, że jest to właściwe, ponieważ wszystkie odpowiedzi o wysokich wynikach są przestarzałe i mylące w kontekście najczęściej używanych dziś kompilatorów.
FauChristian

2

Funkcja double round(double)z wykorzystaniem modffunkcji:

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

Aby uzyskać czystą kompilację, konieczne są „math.h” i „limit”. Funkcja działa zgodnie z następującym schematem zaokrąglania:

  • runda 5.0 to 5.0
  • runda 3,8 to 4,0
  • Runda 2.3 to 2.0
  • Runda 1.5 to 2.0
  • runda 0,501 to 1,0
  • runda 0,5 to 1,0
  • runda 0,499 to 0,0
  • runda 0,01 to 0,0
  • runda 0,0 to 0,0
  • runda -0,01 jest -0,0
  • runda -0,499 to -0,0
  • runda -0,5 to -0,0
  • runda -0.501 wynosi -1.0
  • runda -1,5 wynosi -1,0
  • runda -2,3 to -2,0
  • runda -3,8 to -4,0
  • runda -5,0 to -5,0

2
To dobre rozwiązanie. Nie jestem jednak pewien, czy zaokrąglanie -1,5 do -1,0 jest standardowe, oczekiwałbym -2,0 według symetrii. Nie widzę też sensu wiodącego strażnika, pierwsze dwa, jeśli można je usunąć.
aka.nice

2
Sprawdziłem w normie ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf, a z załącznika B.5.2.4 funkcja zaokrąglania musi być rzeczywiście symetryczna, zaokrąglając_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice

Będzie to wolniejsze w porównaniu do C ++ 11 rint()lub nearbyint(), ale jeśli naprawdę nie możesz użyć kompilatora, który zapewnia odpowiednią funkcję zaokrąglania i potrzebujesz precyzji bardziej niż wydajności ...
Peter Cordes,

1

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ż -DCPP11lub /DCPP11do kompilatora zgodnego z C ++ 11 (lub użyj jego wbudowanych makr wersji) i utwórz taki nagłówek:

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

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.


1

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.

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }

1
Jak wskazałem w odpowiedzi, dodawanie 0.5nie działa we wszystkich przypadkach. Chociaż przynajmniej masz do czynienia z problemem przepełnienia, aby uniknąć niezdefiniowanego zachowania.
Shafik Yaghmour

1

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 dla floatpo zmapowaniu do IEEE-754 (2008) binary32i 2 52 dla doublepo 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) == 0i dodatnich argumentów w ( ub , + ∞] round(x) == x. Ponieważ funkcja jest symetryczna względem osi x, argumenty ujemne xmogą być obsługiwane zgodnie z round(-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ątem roundf()implementacji newlib przy użyciu kompilatora Intel w wersji 13, zarówno z, jak /fp:stricti /fp:fast. Sprawdziłem również, czy wersja newlib jest zgodna roundf()z mathimfbiblioteką kompilatora Intel. Wyczerpujące testy nie są możliwe w przypadku podwójnej precyzji round(), jednak kod jest strukturalnie identyczny z implementacją pojedynczej precyzji.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t exponent_mask = 0x007fffff >> exponent_less_127;
            if ((w & exponent_mask) == 0) {
                /* x has an integral value. */
                return x;
            }
            w += 0x00400000 >> exponent_less_127;
            w &= ~exponent_mask;
        }
    } else {
        if (exponent_less_127 == 128) {
            /* x is NaN or infinite so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}

Dokonałem edycji, aby uniknąć założenia, że intma więcej niż 16 bitów. Oczywiście nadal zakłada, że floatjest to 4-bajtowy plik binarny IEEE75432. C ++ 11 static_assertlub może makro #ifdef/ #errormoż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::rintktóry ładnie współgra z gcc i clang).
Peter Cordes,

BTW, gcc -ffast-math -msse4.1wstawia std::round()do add( AND(x, L1), OR(x,L2), a następnie a roundsd. tzn. dość skutecznie wdraża roundpod względem rint. Ale nie ma powodu, aby robić to ręcznie w źródle C ++, ponieważ jeśli masz std::rint()lub std::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.
Peter Cordes,

@PeterCordes Wiem dobrze, jak round()efektywnie wdrażać pod względem rint()(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ęc rint()nie będzie dostępne, tylko floor()i ceil().
njuffa

@PeterCordes Przepraszamy, popełniłem błąd. round()jest łatwo zsyntetyzować ze rint()w okrągłym do zera trybie aka trunc(). Nie powinienem był odpowiedzieć przed pierwszą kawą.
njuffa

1
@PeterCordes Zgadzam się, że jest prawdopodobne, że OP nie potrzebuje określonego zachowania zaokrąglania round(); większość programistów po prostu nie zdaje sobie sprawy z rozróżnienia między round()vs rint()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 precyzji rintf(), jest roundf()„ nie ”.
njuffa

0

Korzystam z następującej implementacji round w asm dla architektury x86 i specyficznego dla MS VS C ++:

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

UPD: aby zwrócić podwójną wartość

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

Wynik:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000

Wartość wynikowa powinna być wartością zmiennoprzecinkową z podwójną precyzją.
poszukiwacz prawdy

@ truthseeker: Tak, musiałem zobaczyć wymagany typ zwracanej wartości. OK, patrz „UPD”.
Aleksey F.

Miejmy nadzieję, że kompilator będzie wbudowany rint()lub nearbyint()do roundsdinstrukcji SSE4.1 lub instrukcji x87 frndint, 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, frndintponieważ nie ma sposobu, aby uzyskać dane wejściowe do rejestru. Użycie go na końcu funkcji z wynikiem st(0)może być niezawodne jako sposób na zwrócenie wyjścia; najwyraźniej jest to bezpieczne dla eaxliczb całkowitych, nawet jeśli wstawia funkcję zawierającą asm.
Peter Cordes,

@PeterCordes Nowoczesne optymalizacje są mile widziane. Nie mogłem jednak użyć SSE4.1, ponieważ w tym momencie nie istniał. Moim celem było zapewnienie minimalnej implementacji rundy, która mogłaby działać nawet na starych rodzinach Intel P3 lub P4 z lat 2000.
Aleksey F.

P3 nawet nie ma SSE2, więc kompilator będzie już używał x87 do double, a zatem powinien móc frndintsam się wyemitować rint(). Jeśli twój kompilator używa SSE2, odesłanie a doublez rejestru XMM do x87 i wstecz może nie być tego warte.
Peter Cordes,

0

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

float a=47.8732355;
printf("%.3f",a);

-4
// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

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ść.


Jest to przezabawnie nieefektywne, a także obcina (zawsze odrzucając końcowe cyfry) zamiast zaokrąglać do najbliższego.
Peter Cordes,

-6

Ja to zrobiłem:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}

3
Czy nie miałeś na myśli pow (10, miejsce) zamiast operatora binarnego ^ na 10 ^ miejscu? 10 ^ 2 na mojej maszynie daje mi 8 !! Niemniej jednak na moim Macu 10.7.4 i gcc kod nie działa, zwracając oryginalną wartość.
Pete855217,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.