круглий () для плавання в C ++


232

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

double round(double);

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

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

Він присутній у стандартній бібліотеці C ++ під іншою назвою, або він відсутній ??


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

43
Захист цього ... Нові користувачі з новими геніальними схемами округлення повинні спочатку прочитати існуючі відповіді.
Shog9

12
roundдоступний з C ++ 11 дюймів <cmath>. На жаль, якщо ви перебуваєте в Microsoft Visual Studio, його все одно немає: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Alessandro Jacopson

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

2
@uvts_cvs, здається, це не проблема з останньою версією візуальної студії, дивіться це в прямому ефірі .
Шафік Ягмур

Відповіді:


144

У стандартній бібліотеці C ++ 98 немає круглого (). Ви можете написати це самостійно. Далі йде реалізація круглої половини :

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

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


53
Це неправильно обробляє негативні числа. Відповідь лампа є правильною.
Зареєстрований користувач

39
@ InnerJoin: Так, він відповідає негативним числам по-різному, щоб відповісти лабб, але це не робить його "неправильним".
Родді

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

10
@ Sergi0: Немає "правильних" і "неправильних", тому що існує декілька визначень округлення, які вирішують, що відбувається на півдорозі. Перевірте факти, перш ніж приймати рішення.
Jon

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 для того, що стандарт називає бібліотекою Standard C, яка охоплена в проекті стандарту C ++ 03 ( найближчий загальнодоступний проект стандарту до C ++ 03 - це N1804 ) 1.2 Нормативні посилання :

Бібліотека, описана в пункті 7 ISO / IEC 9899: 1990 та пункт 7 ISO / IEC 9899 / Amd.1: 1995, далі називається "Бібліотека стандарту С". 1)

Якщо ми переходимо до документації на C для круглого, lround, llround на cppreference, ми можемо побачити, що круглі та пов'язані з ними функції є частиною 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 потенційно може бути варіантом, оскільки це добре перевірена реалізація.


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

1
Приємна повна відповідь - особливо трохи нижче 0,5 частини. Ще одна ніша: round(-0.0). Не відображається специфікація C. Я б очікував -0.0в результаті.
chux

3
@chux цікаво, а стандарт IEEE 754-2008 вказує, що округлення зберігає ознаки нулів та нескінченностей (див. 5.9).
Руслан

1
@Shafik це чудова відповідь. Я ніколи не думав, що навіть округлення - це нетривіальна операція.
Руслан

1
Можливо, варто згадати, що std::rint()часто краще, ніж std::round()коли C ++ 11 є доступним для чисельних чи ефективних причин. Він використовує поточний режим округлення, на відміну від round()спеціального режиму. Це може бути набагато ефективніше на x86, де rintможна вкласти в одну інструкцію. (gcc і clang роблять це навіть без -ffast-math godbolt.org/g/5UsL2e , тоді як лише кланг вказує майже еквівалентний 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 Хороший улов. Я виявив, що додавання довгого подвійного буквального суфікса до моїх констант вирішило вашу прикладну проблему, але я не знаю, чи є інші приклади точності, які вона не сприймає.
kalaxy

1
btw, якщо ви додасте 0,49999999999999994 замість 0,5, він працює нормально як для введення 0,49999999999999994, так і для 5000000000000001.0. Не впевнений, чи все нормально для всіх значень, і я не зміг знайти жодної посилання, яка б сказала, що це остаточне виправлення.
stijn

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

3
Згідно 4.9 [conv.fpint], "Поведінка не визначена, якщо усічене значення не може бути представлене у типі призначення." , тож це трохи небезпечно. Інші відповіді ТА описують, як це зробити надійно.
Тоні Делрой

41

Він доступний з C ++ 11 дюймами (згідно 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

Ми розглядаємо дві проблеми:

  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
Якщо ваш компілятор за замовчуванням не встановить розмір 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(). Єдина відмінність полягає в тому, що nearbyintніколи не підвищується FE_INEXACT.

Віддайте перевагу rint()причин продуктивності : і gcc, і clang обидва вбудовують його легше, але gcc ніколи не вбудовується nearbyint()(навіть із -ffast-math)


gcc / clang для x86-64 та AArch64

Я поклав кілька тестових функцій у Провідник компілятора Метта Годбольта , де ви можете бачити вихідний вихідний вихідний (asm) вихід (для декількох компіляторів). Більше про читання результатів компілятора див. У цьому запитанні та запитаннях та розмові Метта про CppCon2017: «Що зробив мій компілятор останнім часом? Зняття кришки компілятора " ,

У FP-коді, як правило, великий виграш вбудовує невеликі функції. Особливо у не-Windows, де стандартний режим викликів не має збережених викликів регістрів, тому компілятор не може зберігати жодних значень FP в регістрах XMM через a call. Тож навіть якщо ви насправді не знаєте ASM, ви все одно можете легко зрозуміти, чи це лише хвостовий виклик функції бібліотеки чи чи він накреслений однією чи двома математичними інструкціями. Все, що вказує на одну чи дві інструкції, краще, ніж виклик функції (для цієї конкретної задачі на x86 або ARM).

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

  • std::nearbyint():

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

    • x86 clang: вказує на один inn з -msse4.1
    • x86 gcc7: рядки до одного інсн з -msse4.1. (Без SSE4.1 вказується на кілька інструкцій)
    • x86 gcc6.x та новіші версії: вказується на один inn з -ffast-math -msse4.1.
    • AArch64 gcc: за замовчуванням вказується на один inn
  • std::round:

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

    • x86 clang: вказує на один inn з -msse4.1
    • x86 gcc7.x: вказує на один inn з -msse4.1
    • x86 gcc6.x та новіші версії: вказується на один inn з -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(новонавернений з поточним округленням режим), що ми хотіли б, щоб компілятор випускав, але, на жаль, gcc і clang не обійдуться -ffast-math.

Також майте на увазі, що FP до / з unsignedint / long є менш ефективним на x86 (без AVX512). Перетворення на 32-розрядну непідписану на 64-бітній машині досить дешево; просто перетворити на 64-бітні підписані та усічені. Але в іншому випадку це значно повільніше.

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

  • x86 gcc6.x і раніше без -ffast-math: жодного способу в рядках

  • x86 gcc7 без -ffast-math: (int/long)rintокругляє та конвертує окремо (з 2 загальними інструкціями 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 з -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умов. Мені це здається помилковим.
njuffa

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()функції в 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_MIN, який буде потім бути перетворений назад в double.
Пітер Кордес

2

Не потрібно нічого реалізовувати, тому я не впевнений, чому так багато відповідей передбачає визначення, функції чи методи.

У C99

У нас є наступне і і заголовок <tgmath.h> для макросів типу "generic".

#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, довгий або інший порядковий, вираз округлення такий.

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

Точність

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

Джерело - це не просто кількість значущих цифр у мантісі представлення IEEE числа з плаваючою комою, воно пов'язане з нашим десятковим мисленням як люди.

Десять - це п’ять і два, а 5 і 2 - відносно прості. Отже стандарти IEEE з плаваючою комою неможливо представляти ідеально як десяткові числа для всіх двійкових цифрових зображень.

Це не проблема з алгоритмами округлення. Саме математичну реальність слід враховувати під час вибору типів та проектування обчислень, введення даних та відображення чисел. Якщо програма відображає цифри, які відображають ці десятково-двійкові проблеми перетворення, то програма візуально виражає точність, яка не існує в цифровій реальності і повинна бути змінена.


1
"Я не впевнений, чому так багато відповідей передбачає визначення, функції чи методи." Погляньте, коли його запитали - C ++ 11 ще не вийшов. ;)
зазубрений шпиль

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

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,550 - 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, функція округлення дійсно повинна бути симетричною, округлення_F (x) = neg_F (округлення_F (neg_F (x)))
aka.nice

Це буде повільно порівняно з 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

На основі відповіді Калаксі подано шаблонне рішення, яке округляє будь-який номер плаваючої точки до найближчого цілого типу на основі природного округлення. Він також видає помилку в режимі налагодження, якщо значення виходить за межі цілого типу, тим самим виконуючи функцію життєздатної бібліотеки.

    // 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()вичерпно перевірив roundf()реалізацію newlib, використовуючи компілятор 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-байт IEEE754 бінарний32. C ++ 11 static_assertабо, можливо, макрос #ifdef/ #errorмогли це перевірити. (Але, звичайно, якщо доступний C ++ 11, ви повинні використовувати std::roundабо для поточного режиму округлення, std::rintякий добре поєднується з gcc та clang).
Пітер Кордес

BTW, gcc -ffast-math -msse4.1вказує std::round()на add( AND(x, L1), OR(x,L2), а потім a 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(). Не слід було відповідати перед першою кавою.
njuffa

1
@PeterCordes Я погоджуюся, що, ймовірно, ОП не потребує конкретної поведінки округлення round(); більшість програмістів просто не знають про різницю між round()vs rint()з "круглим" до "найближчим рівним", коли останні зазвичай надаються безпосередньо апаратними засобами і, отже, більш ефективними; Я це прописав у Посібнику з програмування CUDA, щоб зрозуміти програмістам: "Рекомендований спосіб округлення одноточного операнда з плаваючою комою до цілого числа, в результаті чого одноточне число з плаваючою комою є rintf(), ні roundf()".
njuffa

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

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

@ trueseeker: Так, мені довелося побачити необхідний тип повернутого значення. ОК, див. "UPD".
Олексій Ф.

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

@PeterCordes Вітаються сучасні оптимізації. Однак я не зміг використати SSE4.1, оскільки його не було на той момент. Моєю метою було забезпечити мінімальну реалізацію раунду, який міг би функціонувати навіть у старих сімействах Intel P3 або P4 з 2000-х років.
Олексій Ф.

У P3 навіть немає SSE2, тому компілятор вже використовуватиме x87 for 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, місце), а не двійковий оператор ^ на 10 ^ місці? 10 ^ 2 на моїй машині дає мені 8 !! Тим не менш, на моїх Mac 10.7.4 та gcc код не працює, повертаючи початкове значення.
Pete855217
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.