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


232

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

double round(double);

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

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

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


1
Если вы просто хотите вывести число в виде округленного числа, кажется, вы можете просто сделать std::cout << std::fixed << std::setprecision(0) << -0.9, например.
Фрэнк

44
Защита этого ... Новые пользователи с новыми блестящими схемами округления должны сначала прочитать существующие ответы.
Shog9

12
roundдоступен с C ++ 11 в <cmath>. К сожалению, если вы находитесь в Microsoft Visual Studio, он все еще отсутствует: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Алессандро Якопсон,

3
Как я отмечаю в своем ответе, у вас roundесть много предостережений. До C ++ 11 стандарт опирался на C90, который не включал round. C ++ 11 опирается на C99, который имеет, roundно, как я уже отметил, включает в себя, truncкоторый имеет различные свойства и может быть более подходящим в зависимости от приложения. В большинстве ответов также игнорируется тот факт, что пользователь может захотеть вернуть целочисленный тип, который имеет еще больше проблем.
Шафик Ягмур

2
@uvts_cvs, похоже, это не проблема с последней версией visual studio, смотрите ее вживую .
Шафик Ягмур

Ответы:


144

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

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

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


53
Это не обрабатывает отрицательные числа правильно. Ответ от LITB является правильным.
Зарегистрированный пользователь

39
@InnerJoin: Да, он обрабатывает отрицательные числа иначе, чем ответ Литба, но это не делает его «неправильным».
Родди

39
При добавлении 0,5 перед усечением не удается округлить до ближайшего целого числа для нескольких входов, включая 0,49999999999999994. См. Blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Паскаль Куок

10
@ Sergi0: Здесь нет «правильных» и «неправильных», потому что существует более одного определения округления, которое решает, что происходит на полпути. Проверьте свои факты, прежде чем выносить суждение.
Джон

16
@MuhammadAnnaqeeb: Вы правы, с момента выпуска C ++ 11 все значительно улучшилось. Этот вопрос задавали и отвечали в другое время, когда жизнь была тяжелой, а радостей было мало. Это остается здесь как ода героям, которые жили и боролись тогда и для тех бедных душ, которые все еще не могут использовать современные инструменты.
Андреас Магнуссон

96

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 .

Edit : Так как C ++ 11, есть std::round, std::lroundиstd::llround .


2
Я уже использовал повышение в моем проекте, +1 для этого, намного лучше, чем использование наивного floor(value + 0.5)подхода!
Густаво Масиэль

@GustavoMaciel Я знаю, что немного опаздываю к игре, но ускоренная реализация есть floor(value + 0.5).
нет. местоимения м.

На самом деле это не так: github.com/boostorg/math/blob/develop/include/boost/math/… 4 года спустя я также хотел бы сказать, что floor(value + 0.5)это вовсе не наивно, а скорее зависит от контекста и характера значений, которые вы хотите округлить!
Густаво Масиэль

84

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

Библиотека, описанная в п. 7 ИСО / МЭК 9899: 1990 и п. 7 ИСО / МЭК 9899 / Amd.1: 1995, в дальнейшем называется стандартной библиотекой С. 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, который:

Вычисляет ближайшее целое число, не большее по величине, чем 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 ;

}

Если вам необходимо поддерживать приложения, не относящиеся к 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 round. Это очень длинная функция для чего-то, что кажется простым. Представляется маловероятным, чтобы кто-либо, не имеющий глубоких знаний о реализации с плавающей запятой, мог правильно реализовать эту функцию:

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 потенциально может быть вариантом, поскольку это хорошо проверенная реализация.


5
@ downvoter объясни пожалуйста что можно улучшить? Подавляющее большинство ответов здесь просто неверны, так как они пытаются свернуть свой собственный раунд, который все терпят неудачу в той или иной форме. Если в моем объяснении чего-то не хватает, пожалуйста, дайте мне знать.
Шафик Ягмур

1
Хороший полный ответ - особенно чуть ниже 0,5 части. Еще одна ниша round(-0.0). C spec не указывается, чтобы указать. Я ожидаю -0.0в результате.
chux - Восстановить Монику

3
@chux интересно, а стандарт IEEE 754-2008 действительно указывает, что при округлении сохраняются знаки нулей и бесконечностей (см. 5.9).
Руслан

1
@ Шафик, это отличный ответ. Я никогда не думал, что даже округление - нетривиальная операция.
Руслан

1
Возможно, стоит упомянуть, что std::rint()часто предпочтительнее, std::round()когда C ++ 11 доступен по численным причинам и по соображениям производительности. Он использует текущий режим округления, в отличие от round()специального режима. Это может быть гораздо более эффективным на x86, где rintможет быть встроена в одну инструкцию. (gcc и clang делают это даже без -ffast-math godbolt.org/g/5UsL2e , в то время как только clang указывает на почти эквивалентный nearbyint()) ARM имеет поддержку для одной инструкции round(), но на x86 он может быть встроенным только с несколькими инструкциями и только с-ffast-math
Peter Cordes

71

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

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

3
Не дает ожидаемого результата для 0.49999999999999994, хотя (ну, в зависимости от того, что вы ожидаете, конечно, но 0 кажется мне более разумным, чем 1)
stijn

@stijn Хороший улов. Я обнаружил, что добавление длинного двойного литерального суффикса к моим константам устранило проблему вашего примера, но я не знаю, есть ли другие примеры точности, которые он не уловит.
kalaxy

1
Кстати, если вы добавляете 0,49999999999999994 вместо 0,5, это работает нормально для обоих 0,49999999999999994 и 5000000000000001.0 в качестве ввода. Не уверен, что это нормально для всех значений, и я не смог найти никаких ссылок, утверждающих, что это окончательное решение.
Стийн

1
@stijn Это нормально для всех значений, если вам все равно, в каком направлении округляются значения точно между двумя целыми числами. Недолго думая, я докажу это путем анализа случаев со следующими случаями: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. Я также тщательно протестировал корпус с одинарной точностью.
Паскаль Куок

3
Согласно 4.9 [conv.fpint], «поведение не определено, если усеченное значение не может быть представлено в типе назначения». так что это немного опасно. Другие ответы SO описывают, как сделать это надежно.
Тони Делрой

41

Он доступен с C ++ 11 в cmath (согласно 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;
}

Вывод:

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
есть также lroundи llroundдля интегральных результатов
sp2danny

@ sp2danny: или лучше, lrintчтобы использовать текущий режим округления вместо roundфанк-тай-брейка.
Питер Кордес

27

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

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


1
Хорошо проводить различие между разными версиями «раунда». Хорошо знать, когда выбирать что тоже.
xtofl

5
Действительно, существуют разные алгоритмы округления, которые все могут претендовать на «правильность». Однако пол (значение + 0,5) не является одним из них. Для некоторых значений, таких как 0,49999997f или эквивалентного двойного, ответ просто неправильный - он будет округлен до 1,0, когда все согласятся, что он должен быть равен нулю. Подробности смотрите в этом посте: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Брюс Доусон,

14

Есть 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


Использование LONG_MIN-0.5и LONG_MAX+0.5 вводит осложнения, поскольку математика может быть не точной. LONG_MAXможет превышать doubleточность для точного преобразования. Кроме того, вероятно, требуется assert(x < LONG_MAX+0.5); (<vs <=), поскольку LONG_MAX+0.5может быть точно представимым и (x)+0.5может иметь точный результат, LONG_MAX+1который не longприведен. Другие угловые вопросы тоже.
chux - Восстановить Монику

Не вызывайте вашу функцию round(double), уже есть стандартная математическая библиотечная функция с таким именем (в C ++ 11), так что это сбивает с толку. Используйте, std::lrint(x)если это доступно.
Питер Кордес

11

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

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


2
Boost также предлагает набор простых функций округления; смотри мой ответ.
Даниэль Вольф

Вы также можете использовать boost:numeric::RoundEven< double >::nearbyintнапрямую, если вы не хотите, чтобы целое число. @DanielWolf, обратите внимание, что простая функция реализована с использованием +0.5, что имеет проблемы, изложенные в aka.nice
stijn

6

Вы можете округлить до 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;
}

4
Если размер компилятора int по умолчанию не равен 1024 битам, это не будет точным для огромного двойного ...
aka.nice

Я думаю, что это приемлемо, учитывая, когда оно будет использоваться: если ваше двойное значение равно 1,0 e + 19, округление до 3 мест не имеет смысла.
Карл

3
конечно, но вопрос для общего раунда, и вы не можете контролировать, как он будет использоваться. Там нет причин для раунда, чтобы потерпеть неудачу там, где потолок и пол не будет.
aka.nice

Это имеет неопределенное поведение для аргументов за пределами диапазона int. (На практике на x86 значения FP вне допустимого диапазона будут CVTTSD2SIпроизводить0x80000000 как целочисленный битовый шаблон, то есть INT_MIN, который затем будет преобразован обратно double.
Питер Кордес

5

В наши дни не должно быть проблемой использовать компилятор C ++ 11, который включает математическую библиотеку C99 / C ++ 11. Но тогда возникает вопрос: какую функцию округления вы выбираете?

C99 / C ++ 11 round()часто не является нужной вам функцией округления . Он использует режим фанки округления, который округляется от 0 в качестве тай-брейка на полпути ( +-xxx.5000). Если вам действительно нужен этот режим округления, или вы нацеливаетесь на реализацию C ++, где round()это быстрее, чем rint(), то используйте его (или эмулируйте его поведение с одним из других ответов на этот вопрос, который принял его за чистую монету и тщательно воспроизвел этот конкретный поведение округления.)

round()Округление отличается от стандартного раунда IEEE754 до ближайшего режима даже в виде тай-брейка . Ближайший - даже избегает статистического смещения средней величины чисел, но делает смещение в сторону четных чисел.

Есть две функции округления математической библиотеки, которые используют текущий режим округления по умолчанию: std::nearbyint() и std::rint()обе они добавлены в C99 / C ++ 11, поэтому они доступны в любое время std::round(). Разница лишь в том, что nearbyintFE_INEXACT никогда не поднимается.

Предпочитаю rint()по причинам производительности : gcc и clang легче встроить его, но gcc никогда не вставляет nearbyint()(даже с -ffast-math)


gcc / clang для x86-64 и AArch64

Я поместил некоторые тестовые функции в проводник компилятора Мэтта Годболта , где вы можете увидеть исходные тексты + asm (для нескольких компиляторов). Подробнее о чтении выходных данных компилятора читайте в разделе « Вопросы и ответы». раздел , а также доклад Мэтта CppCon2017: «Что мой компилятор сделал для меня в последнее время? Откручиваем крышку компилятора » ,

В FP-коде это обычно большой выигрыш для встроенных маленьких функций. Особенно в не Windows, где стандартное соглашение о вызовах не имеет регистров, сохраняющих вызовы, поэтому компилятор не может хранить значения FP в регистрах XMM черезcall . Поэтому, даже если вы действительно не знаете asm, вы все равно можете легко увидеть, является ли это просто хвостовым вызовом библиотечной функции или она встроена в одну или две математические инструкции. Все, что связано с одной или двумя инструкциями, лучше, чем вызов функции (для этой конкретной задачи на x86 или ARM).

На x86 все, что встроено в SSE4.1, roundsdможет автоматически векторизоваться с SSE4.1 roundpd(или AVX vroundpd). (FP-> целочисленные преобразования также доступны в упакованном виде SIMD, за исключением FP-> 64-битного целого числа, которое требует AVX512.)

  • std::nearbyint():

    • x86 clang: встраивает в один insn с -msse4.1 .
    • x86 gcc: встраивает в единственный insn только с -msse4.1 -ffast-mathи только на gcc 5.4 и более ранних версиях . Позже gcc никогда не указывает на это (возможно, они не понимали, что один из непосредственных битов может подавить неточное исключение? Это то, что использует clang, но более старый gcc использует то же самое, что и дляrint когда он встроен)
    • AArch64 gcc6.3: по умолчанию встроен в один insn.
  • std::rint:

    • x86 clang: встраивает в один insn с -msse4.1
    • x86 gcc7: встраивает в один insn с -msse4.1 . (Без SSE4.1, содержит несколько инструкций)
    • x86 gcc6.x и более ранние: в единый insn вставляется -ffast-math -msse4.1 .
    • AArch64 gcc: по умолчанию встроен в один insn
  • std::round:

    • x86 clang: не встроен
    • x86 gcc: встраивает в несколько инструкций -ffast-math -msse4.1, требуя двух векторных констант.
    • AArch64 gcc: встраивает в одну инструкцию (HW поддерживает этот режим округления, а также IEEE по умолчанию и большинство других.)
  • std::floor/ std::ceil/std::trunc

    • x86 clang: встраивает в один insn с -msse4.1
    • x86 gcc7.x: встраивает в единый insn с -msse4.1
    • x86 gcc6.x и более ранние: в единый insn вставляется -ffast-math -msse4.1
    • AArch64 gcc: по умолчанию вставляет одну инструкцию

Округление до int/ long/ long long:

У вас есть два варианта: использовать lrint(например, rintно возвращает longили long longдля llrint) или использовать функцию округления FP-> FP, а затем преобразовать в целочисленный тип обычным способом (с усечением). Некоторые компиляторы оптимизируют один путь лучше, чем другой.

long l = lrint(x);

int  i = (int)rint(x);

Обратите внимание, что сначала int i = lrint(x)преобразуется floatили double-> long, а затем усекает целое число доint . Это имеет значение для целых чисел вне диапазона: неопределенное поведение в C ++, но четко определенное для инструкций x86 FP -> int (которые компилятор будет выдавать, если он не увидит UB во время компиляции во время постоянного распространения, тогда это разрешено создавать код, который ломается, если он когда-либо выполняется).

На x86 преобразование целых чисел FP->, которое переполняет целое число, производит INT_MINили LLONG_MIN(битовый шаблон 0x8000000или 64-битный эквивалент, только с установленным битом знака). Intel называет это «целочисленным неопределенным» значением. (См на cvttsd2siручной ввод , инструкция SSE2 , который преобразует (с обрезанием) скаляр двойной знаковое целое число. Это доступно с 32-битной или 64-разрядное целое число назначения (только в 64-битном режиме). Там же cvtsd2si(новообращенный с текущим округлением mode), это то, что мы хотели бы, чтобы компилятор испускал, но, к сожалению, gcc и clang не справятся с этим -ffast-math.

Также помните, что FP в / из unsignedint / long менее эффективен на x86 (без AVX512). Преобразование в 32-битный unsigned на 64-битной машине довольно дешево; просто конвертировать в 64-битную подпись и обрезать. Но в остальном это значительно медленнее.

  • x86 clang с / без -ffast-math -msse4.1: (int/long)rintвставляет в roundsd/ cvttsd2si. (пропустил оптимизацию до cvtsd2si). lrintне встроен вообще.

  • x86 gcc6.x и более ранние версии без -ffast-mathвстроенных символов

  • x86 gcc7 без -ffast-math: (int/long)rintокругляет и преобразует отдельно (с двумя полными инструкциями SSE4.1 включено, в противном случае с кучей кода, встроенного rintбез roundsd). lrintне встроенный
  • x86 gcc с -ffast-math : все способы встроены cvtsd2si(оптимально) , нет необходимости в SSE4.1.

  • AArch64 gcc6.3 без -ffast-math: (int/long)rintсодержит 2 инструкции. lrintне встроенный

  • AArch64 gcc6.3 with -ffast-math: (int/long)rintкомпилирует вызов lrint. lrintне встроенный Это может быть пропущенной оптимизацией, если только две инструкции, которые мы получаем, не -ffast-mathочень медленные.

TODO: ICC и MSVC также доступны на Godbolt, но я не смотрел на их результаты для этого. редактирование приветствуется ... Кроме того: было бы более полезно разбить сначала по компилятору / версии, а затем по функции внутри этого? Большинство людей не собираются переключать компиляторы, основываясь на том, насколько хорошо они компилируют FP-> FP или FP-> целочисленное округление.
Питер Кордес

2
+1 за рекомендацию, rint()где это выполнимый выбор, который обычно имеет место. Я предполагаю, что название round()подразумевает для некоторых программистов, что это то, что они хотят, хотя rint()кажется загадочным. Обратите внимание, что round()не используется «фанковый» режим округления: округление до ближайших связей является официальным режимом округления IEEE-754 (2008). Любопытно, что nearbyint()это не вписывается, учитывая, что это в значительной степени то же самое rint(), и должно быть идентичным в -ffast-mathусловиях. Это выглядит ошибочно для меня.
нюффа

4

Остерегайтесь floor(x+0.5). Вот что может произойти для нечетных чисел в диапазоне [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

Это http://bugs.squeak.org/view.php?id=7134 . Используйте решение, подобное @konik.

Моя собственная надежная версия будет выглядеть примерно так:

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

Другая причина избегать пола (х + 0,5) приведена здесь .


2
Мне интересно знать о понижениях. Это потому, что связь разрешена от нуля, а не до ближайшего четного?
aka.nice

1
Примечание: спецификация C гласит «округление на полпути от нуля, независимо от текущего направления округления», поэтому округление без учета нечетного / четного соответствует.
chux - Восстановить Монику

4

Если вы в конечном итоге захотите преобразовать doubleвывод вашей round()функции в a 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;
}

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


Это имеет неопределенное поведение для аргументов за пределами диапазона int. (На практике на x86 значения FP вне допустимого диапазона приводят к CVTTSD2SIполучению в0x80000000 виде целочисленной битовой комбинации, т. INT_MINdouble
Питер Кордес

2

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

В C99

У нас есть следующий заголовок и и <tgmath.h> для макросов общего типа.

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

Если вы не можете скомпилировать это, вы, вероятно, не учли математическую библиотеку. Команда, подобная этой, работает на каждом компиляторе C, который у меня есть (несколько).

gcc -lm -std=c99 ...

В С ++ 11

У нас есть следующие и дополнительные перегрузки в #include <cmath>, которые полагаются на IEEE с плавающей запятой двойной точности.

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

Есть эквиваленты и в пространстве имен std .

Если вы не можете скомпилировать это, вы можете использовать компиляцию C вместо C ++. Следующая базовая команда не выдает ни ошибок, ни предупреждений с g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 и Visual C ++ 2015 Community.

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

С Порядковым Подразделением

При делении двух порядковых чисел, где T - это короткий, int, long или другой порядковый номер, выражение округления таково.

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

точность

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

Источником является не просто число значащих цифр в мантиссе представления IEEE числа с плавающей запятой, оно связано с нашим десятичным мышлением как людей.

Десять - это произведение пяти и двух, а 5 и 2 относительно простые. Поэтому стандарты IEEE с плавающей запятой не могут быть идеально представлены в виде десятичных чисел для всех двоичных цифровых представлений.

Это не проблема с алгоритмами округления. Это математическая реальность, которую следует учитывать при выборе типов и разработке вычислений, вводе данных и отображении чисел. Если приложение отображает цифры, которые показывают эти десятичные двоичные проблемы преобразования, то приложение визуально выражает точность, которая не существует в цифровой реальности и должна быть изменена.


1
«Я не уверен, почему так много ответов связаны с определениями, функциями или методами». Посмотрите, когда его спросили - C ++ 11 еще не вышел. ;)
jaggedSpire

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

2

функция double round(double) с использованием modfфункции:

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

Чтобы быть чистым при компиляции, необходимо включить «math.h» и «limit». Функция работает в соответствии со следующей схемой округления:

  • раунд 5,0 5,0
  • раунд 3,8 - 4,0
  • раунд 2,3 - 2,0
  • раунд 1,5 это 2,0
  • раунд 0,501 составляет 1,0
  • раунд 0,5 равен 1,0
  • раунд 0,499 составляет 0,0
  • раунд 0,01 - 0,0
  • раунд 0.0 равен 0.0
  • раунд -0,01 это -0,0
  • раунд -0,499 это -0,0
  • раунд -0,5 -0,0
  • раунд -0,501 -1,0
  • раунд -1,5 -1,0
  • раунд -2,3 -2,0
  • раунд -3,8 -4,0
  • раунд -5,0 это -5,0

2
Это хорошее решение. Я не уверен, что округление от -1,5 до -1,0 является стандартным, хотя я бы ожидал -2,0 по симметрии. Также я не вижу смысла ведущей охраны, первых двух, если можно было бы убрать.
aka.nice

2
Я проверил в стандарте ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf и из приложения B.5.2.4, функция округления должна быть действительно симметричной, rounding_F (x) = neg_F (rounding_F (neg_F (x)))
хорошо

Это будет медленно по сравнению с C ++ 11 rint()или nearbyint(), но если вы действительно не можете использовать компилятор, который обеспечивает правильную функцию округления, и вам нужна точность больше, чем производительность ...
Питер Кордес

1

Если вам нужно иметь возможность компилировать код в средах, которые поддерживают стандарт C ++ 11, но также нужно иметь возможность компилировать этот же код в средах, которые его не поддерживают, вы можете использовать функциональный макрос для выбора между std :: round () и пользовательская функция для каждой системы. Просто передайте -DCPP11или /DCPP11на C ++ 11-совместимый компилятор (или использовать его встроенные версии макросов), и сделать заголовок , как это:

// 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 */

Для быстрого примера см. Http://ideone.com/zal709 .

Это приблизительно соответствует std :: round () в средах, не совместимых с C ++ 11, включая сохранение знака знака для -0.0. Однако это может привести к незначительному снижению производительности и, вероятно, к проблемам с округлением определенных известных «проблемных» значений с плавающей запятой, таких как 0,49999999999999994 или аналогичных значений.

В качестве альтернативы, если у вас есть доступ к C ++ 11-совместимому компилятору, вы можете просто взять std :: round () из его <cmath>заголовка и использовать его для создания собственного заголовка, который определяет функцию, если она еще не определена. Обратите внимание, что это может быть не оптимальным решением, особенно, если вам нужно компилировать для нескольких платформ.


1

Основываясь на ответе Kalaxy, следующее представляет собой шаблонное решение, которое округляет любое число с плавающей запятой до ближайшего целочисленного типа на основе естественного округления. Он также выдает ошибку в режиме отладки, если значение выходит за пределы диапазона целочисленного типа, тем самым служа примерно как жизнеспособная библиотечная функция.

    // 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
Как я указал в своем ответе, добавление 0.5не работает во всех случаях. Хотя, по крайней мере, вы имеете дело с проблемой переполнения, поэтому избегаете неопределенного поведения.
Шафик Ягмур

1

Как указано в комментариях и других ответах, стандартная библиотека ISO C ++ не добавляла round() до ISO C ++ 11, когда эта функция была задействована посредством ссылки на стандартную математическую библиотеку ISO C99.

Для положительных операндов в [½, ub ] round(x) == floor (x + 0.5), где ub равно 2 23 для floatотображения на IEEE-754 (2008) binary32и 2 52 для doubleотображения на IEEE-754 (2008) binary64. Числа 23 и 52 соответствуют количеству сохраненных битов мантиссы в этих двух форматах с плавающей запятой. Для положительных операндов в [+0, ½) round(x) == 0и для положительных операндов в ( ub , + ∞) round(x) == x. Поскольку функция симметрична относительно оси x, отрицательные аргументы xмогут обрабатываться в соответствии сround(-x) == -round(x) .

Это приводит к компактному коду ниже. Он компилируется в разумное количество машинных инструкций на разных платформах. Я наблюдал самый компактный код на графических процессорах, где my_roundf()требуется около десятка инструкций. В зависимости от архитектуры процессора и цепочки инструментов этот подход на основе чисел с плавающей запятой может быть быстрее или медленнее, чем целочисленная реализация из newlib, на которую ссылается другой ответ .

Я полностью протестировал реализацию my_roundf()newlib, roundf()используя компилятор Intel версии 13, с обоими /fp:strictи /fp:fast. Я также проверил, что версия newlib соответствует roundf()в mathimfбиблиотеке компилятора Intel. Исчерпывающее тестирование невозможно для двойной точности round(), однако код структурно идентичен реализации с одинарной точностью.

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

Я сделал редактирование, чтобы избежать предположения, что intего ширина превышает 16 бит. Это все еще, конечно, предполагает, что floatэто 4-байтовый двоичный код IEEE75432. C ++ 11 static_assertили, может быть, макрос #ifdef/ #errorможет это проверить. (Но, конечно, если доступен C ++ 11, вы должны использовать std::roundили для текущего режима округления, std::rintкоторый прекрасно сочетается с gcc и clang).
Питер Кордес

Кстати, gcc -ffast-math -msse4.1inlines std::round()to add( AND(x, L1), OR(x,L2), а затем roundsd. то есть он довольно эффективно реализуется roundс точки зрения rint. Но нет причин делать это вручную в исходном коде C ++, потому что, если у вас есть std::rint()или у std::nearbyint()вас также есть std::round(). Смотрите мой ответ для ссылки Godbolt и краткое изложение того, что встраивается или нет с различными версиями gcc / clang.
Питер Кордес

@PeterCordes Я хорошо знаю, как round()эффективно реализовать с точки зрения rint()(когда последний работает в режиме округления до ближайшего или даже): я реализовал это для стандартной математической библиотеки CUDA. Тем не менее, этот вопрос, похоже, задает вопрос о том, как реализовать round()с C ++ до C ++ 11, поэтому rint()также не будет доступен, только floor()и ceil().
njuffa

@PeterCordes Извините, я ошибся. round()легко синтезируется из rint()в режиме округления до нуля , иначе trunc(). Не должен был отвечать до первого кофе.
нюффа

1
@PeterCordes Я согласен, что, скорее всего, OP не нуждается в особом поведении округления round(); большинство программистов просто не знают о различии между round()против rint()с круглым к ближайшим даже, когда последний, как правило , предоставляется непосредственно аппаратными средствами и , следовательно , более эффективным; Я изложил это в Руководстве по программированию CUDA, чтобы программисты знали: «Рекомендуемый способ округлять операнд с плавающей запятой одинарной точности до целого числа, в результате чего число с плавающей запятой одинарной точности равно rintf(), а не roundf()».
нюффа

0

Я использую следующую реализацию раунда в asm для архитектуры x86 и MS VS для C ++:

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

UPD: вернуть двойное значение

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

Вывод:

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

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

@ истина: Да, я должен был увидеть требуемый тип возвращаемого значения. ОК, смотрите «UPD».
Алексей Федорович

Надеемся, что компилятор будет встроен rint()либо nearbyint()в roundsdинструкцию SSE4.1, либо в frndintинструкцию x87 , которая будет намного быстрее, чем две циклические операции сохранения / перезагрузки, необходимые для использования этого встроенного ассемблера для данных в регистре. Встроенный ассемблер MSVC - это много, чтобы обернуть отдельные инструкции, например, frndintпотому что нет способа получить ввод в регистр. Использование его в конце функции с результатом st(0)может быть надежным способом возврата результата; по-видимому, это безопасно для eaxцелых чисел, даже если оно содержит функцию, содержащую asm.
Питер Кордес

@PeterCordes Современные оптимизации приветствуются. Однако я не смог использовать SSE4.1, так как он не существовал на тот момент. Моей целью было обеспечить минимальную реализацию раунда, которая могла бы работать даже на старых семействах Intel P3 или P4 с 2000-х годов.
Алексей Федорович

P3 даже не имеет SSE2, поэтому компилятор уже будет использовать x87 для double, и, следовательно, должен иметь возможность генерировать frndintсебя rint(). Если ваш компилятор использует SSE2, возврат doubleиз регистра XMM в x87 и обратно может не стоить этого.
Питер Кордес

0

Лучший способ округлить плавающее значение с помощью «n» десятичных разрядов, как показано в O (1) раз: -

Мы должны округлить значение на 3 места, т.е. n = 3. Так что

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

Это может быть неэффективный грязный способ преобразования, но, черт возьми, это работает, лол. И это хорошо, потому что это относится к фактическому плаванию. Не только визуально влияет на результат.


Это крайне неэффективно, а также усекает (всегда отбрасывая конечные цифры) вместо округления до ближайшего.
Питер Кордес

-6

Я сделал это:

#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
Разве вы не имели в виду pow (10, place), а не бинарный оператор ^ в 10 ^ месте? 10 ^ 2 на моей машине дает мне 8 !! Тем не менее на моем Mac 10.7.4 и gcc код не работает, возвращая исходное значение.
Pete855217
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.