round () для поплавка в C ++ - PullRequest
       122

round () для поплавка в C ++

225 голосов
/ 28 января 2009

Мне нужна простая функция округления с плавающей запятой, таким образом:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

Я могу найти ceil() и floor() в math.h - но не round().

Он присутствует в стандартной библиотеке C ++ под другим именем или отсутствует ??

Ответы [ 20 ]

139 голосов
/ 28 января 2009

В стандартной библиотеке C ++ 98 нет функции round (). Вы можете написать один самостоятельно, хотя. Ниже приведена реализация округления до половины :

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

Вероятная причина того, что в стандартной библиотеке C ++ 98 нет функции округления, заключается в том, что она может быть реализована различными способами. Выше приведен один из распространенных способов, но есть и другие, такие как округление до четного , которое менее предвзято и, как правило, лучше, если вы собираетесь делать много округлений; хотя это немного сложнее в реализации.

94 голосов
/ 01 мая 2011

Boost предлагает простой набор функций округления.

#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

Для получения дополнительной информации см. Документацию Boost .

Редактировать : Начиная с C ++ 11, есть std::round, std::lround и std::llround.

77 голосов
/ 22 июня 2014

Стандарт C ++ 03 опирается на стандарт C90 для того, что стандарт называет Стандарт C Library , который описан в проекте стандарта C ++ 03 (, ближайший к общедоступному доступу). черновой вариант стандарта C ++ 03 - N1804 ) раздел 1.2 нормативные ссылки :

Библиотека, описанная в пункте 7 ИСО / МЭК 9899: 1990 и в пункте 7 ISO / IEC 9899 / Amd.1: 1995 в дальнейшем называется стандартом C Библиотека. 1)

Если мы перейдем к документации C для round, lround, llround по cppreference , мы увидим, что round и связанные с ним функции являются частью C99 и, таким образом, не будет доступен в C ++ 03 или ранее.

В C ++ 11 это меняется, поскольку C ++ 11 использует черновой стандарт C99 для стандартной библиотеки C и, следовательно, предоставляет std :: round и для целочисленных возвращаемых типов 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 ;
}

Другой вариант также из C99 будет std :: trunc который:

Вычисляет ближайшее целое число, не большее по величине, чем аргумент.

#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 ;

}

Если вам требуется поддержка приложений, не поддерживающих C ++ 11, лучше всего использовать бустер-раунд, округлый, объемный, округлый или буст-усечение .

Раскатать свою версию раунда сложно

Свертывание собственного, вероятно, не стоит усилий, поскольку Сложнее, чем кажется: округление числа с плавающей точкой до ближайшего целого числа, часть 1 , Округление числа с плавающей точкой до ближайшего целого числа, часть 2 и Округление с плавающей точкой до ближайшего целого, часть 3 Объясните:

Например, обычная проверка вашей реализации с использованием std::floor и добавление 0.5 не работает для всех входных данных:

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

Один вход, для которого это не удастся: 0.49999999999999994, ( посмотреть вживую ).

Другая распространенная реализация включает приведение типа с плавающей запятой к целочисленному типу, который может вызывать неопределенное поведение в случае, когда интегральная часть не может быть представлена ​​в типе назначения. Это видно из черновика стандартного раздела C ++ 4.9 Плавающее-целочисленное преобразование , которое гласит ( выделение шахты ):

Значение типа с плавающей запятой может быть преобразовано в значение типа целочисленный тип. Преобразование усекается; то есть дробная часть отбрасывается Поведение не определено, если усеченное значение не может быть представленным в типе назначения. [...]

Например:

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

Если std::numeric_limits<unsigned int>::max() равно 4294967295, то следующий вызов:

myround( 4294967296.5f ) 

вызовет переполнение, ( посмотреть вживую ).

Мы можем увидеть, насколько это действительно сложно, посмотрев на этот ответ Краткий способ реализации round () в C? , который ссылается на newlibs версию с плавающей запятой одинарной точности. Это очень длинная функция для чего-то, что кажется простым. Представляется маловероятным, чтобы кто-либо, не имеющий глубоких знаний о реализации с плавающей запятой, мог правильно реализовать эту функцию:

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;
}

С другой стороны, если ни одно из других решений не пригодно для использования newlib потенциально может быть вариантом, поскольку это хорошо протестированная реализация.

71 голосов
/ 11 января 2011

Возможно, стоит отметить, что если вы хотите получить целочисленный результат округления, вам не нужно пропускать его через потолок или пол. То есть.,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}
41 голосов
/ 19 июня 2012

Доступно с C ++ 11 в cmath (согласно http://www.open -std.org / jtc1 / sc22 / wg21 / docs /apers / 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;
}

Выход:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2
27 голосов
/ 28 января 2009

Обычно это реализовано как floor(value + 0.5).

Редактировать: и, вероятно, он не называется округлением, так как есть по крайней мере три известных мне алгоритма округления: округление до нуля, округление до ближайшего целого и округление банкира. Вы запрашиваете округление до ближайшего целого числа.

13 голосов
/ 29 мая 2010

Есть 2 проблемы, на которые мы смотрим:

  1. округления
  2. преобразование типов.

Преобразования округления означают округление ± число с плавающей запятой / двойной до ближайшего этажа / число с плавающей запятой / двойной. Может быть, ваша проблема заканчивается здесь. Но если ожидается, что вы вернете Int / Long, вам нужно выполнить преобразование типов, и, таким образом, проблема «переполнения» может попасть в ваше решение. ТАК, сделайте проверку на ошибку в вашей функции

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

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

11 голосов
/ 29 июня 2010

Определенный тип округления также реализован в 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;
}

Обратите внимание, что это работает, только если вы делаете целочисленное преобразование.

6 голосов
/ 12 февраля 2010

Вы можете округлить до n цифр с точностью:

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;
}
5 голосов
/ 26 апреля 2013

Если вы в конечном итоге захотите преобразовать вывод double вашей функции round() в int, то принятые решения этого вопроса будут выглядеть примерно так:

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

На моем компьютере это время составляет 8,88 нс , когда передается равномерно случайным образом.

Нижеприведенное, насколько я могу судить, функционально эквивалентно, но на моей машине имеет значение 2,48 нс , что значительно повышает производительность:

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

Среди причин лучшей производительности - пропущенное ветвление.

...