Вопрос

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

double round(double);

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

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

Если мы отправимся в Документация C для round, lround, llround на cppreference мы можем видеть, что круглый и связанные с этим функции являются частью С99 и, следовательно, не будет доступен в 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:: транк который:

Вычисляет ближайшее целое число, не большее по величине, чем 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 Преобразования с плавающим целым числом в котором говорится (акцент мой):

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

Например:

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

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

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

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

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

Он доступен начиная с 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

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

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

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

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

Преобразования округления означают округление ± float / double до ближайшего float /double этажа / потолка.Возможно, на этом ваша проблема заканчивается.Но если ожидается, что вы вернете 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

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

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

Вы могли бы округлить до точности 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;
}

Если вы в конечном счете хотите преобразовать 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;
}

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

В наши дни не должно быть проблемой использовать компилятор 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 в call.Таким образом, даже если вы на самом деле не знакомы с asm, вы все равно можете легко увидеть, является ли это просто конечным вызовом библиотечной функции или оно встроено в одну или две математические инструкции.Все, что соответствует одной или двум инструкциям, лучше, чем вызов функции (для этой конкретной задачи на x86 или ARM).

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

  • std::nearbyint():

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

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

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

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

Округление до 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-> integer, которое переполняет целое число, приводит к INT_MIN или LLONG_MIN (бит-шаблон из 0x8000000 или 64-битный эквивалент, с установленным только знаковым битом).Intel называет это "целочисленным неопределенным" значением.(См. в cvttsd2si ручной ввод, инструкция SSE2, которая преобразует (с усечением) скалярное значение double в целое число со знаком.Он доступен с 32-разрядным или 64-разрядным целочисленным назначением (только в 64-разрядном режиме).Там также есть cvtsd2si (преобразовать с текущим режимом округления), который мы хотели бы, чтобы компилятор выдавал, но, к сожалению, gcc и clang не будут этого делать без -ffast-math.

Также остерегайтесь, что FP в / из unsigned int / long менее эффективен на x86 (без AVX512).Преобразование в 32-разрядную версию без знака на 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 округляет и преобразует отдельно (с включенными всего 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 действуют очень медленно.

Остерегайтесь 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;
}

Приводится еще одна причина избегать floor(x+0.5) здесь.

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

В C99

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

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

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

gcc -lm -std=c99 ...

В C++ 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 с плавающей запятой не могут быть представлены идеально в виде десятичных чисел для всех двоичных цифровых представлений.

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

Функция 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" и "limits".Функция работает в соответствии со следующей схемой округления:

  • раунд 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

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

Основываясь на ответе 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);
    }

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

Для положительных операндов в [½, уб] round(x) == floor (x + 0.5), где уб равно 223 для float при сопоставлении с IEEE-754 (2008) binary32, и 252 для double когда он сопоставлен с IEEE-754 (2008) binary64.Числа 23 и 52 соответствуют количеству сохраненный биты мантиссы в этих двух форматах с плавающей запятой.Для положительных операндов в [+0, ½) round(x) == 0, и для положительных операндов в (уб, +∞] 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;
}

Я использую следующую реализацию round в 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

Лучший способ округления плавающего значения на "n" знаков после запятой - это использовать за O (1) раз:-

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

float a=47.8732355;
printf("%.3f",a);
// 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");

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

Я сделал это:

#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;
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top