Мне нужна простая функция округления с плавающей точкой, таким образом:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
Я могу найти ceil()
и floor()
в математике - но нет round()
.
Он присутствует в стандартной библиотеке C ++ под другим именем или отсутствует?
c++
floating-point
rounding
Родди
источник
источник
std::cout << std::fixed << std::setprecision(0) << -0.9
, например.round
доступен с C ++ 11 в<cmath>
. К сожалению, если вы находитесь в Microsoft Visual Studio, он все еще отсутствует: connect.microsoft.com/VisualStudio/feedback/details/775474/…round
есть много предостережений. До C ++ 11 стандарт опирался на C90, который не включалround
. C ++ 11 опирается на C99, который имеет,round
но, как я уже отметил, включает в себя,trunc
который имеет различные свойства и может быть более подходящим в зависимости от приложения. В большинстве ответов также игнорируется тот факт, что пользователь может захотеть вернуть целочисленный тип, который имеет еще больше проблем.Ответы:
В стандартной библиотеке C ++ 98 нет функции round (). Вы можете написать один самостоятельно, хотя. Ниже приведена реализация круглого с половиной :
Вероятная причина того, что в стандартной библиотеке C ++ 98 нет функции округления, заключается в том, что она может быть реализована различными способами. Выше приведен один из распространенных способов, но есть и другие, такие как округление до четности , которое является менее предвзятым и, как правило, лучше, если вы собираетесь делать много округлений; это немного сложнее реализовать, хотя.
источник
Boost предлагает простой набор функций округления.
Для получения дополнительной информации см. Документацию Boost .
Edit : Так как C ++ 11, есть
std::round
,std::lround
иstd::llround
.источник
floor(value + 0.5)
подхода!floor(value + 0.5)
.floor(value + 0.5)
это вовсе не наивно, а скорее зависит от контекста и характера значений, которые вы хотите округлить!Стандарт C ++ 03 опирается на стандарт C90 для того, что стандарт называет стандартной библиотекой C, которая описана в разделе проекта стандарта C ++ 03 ( ближайший общедоступный проект стандарта C ++ 03 - N1804 ).
1.2
Нормативные ссылки :Если мы перейдем к документации C для round, lround, llround по cppreference, мы увидим, что round и связанные функции являются частью C99 и, следовательно, не будут доступны в C ++ 03 или более ранних версиях.
В C ++ 11 это меняется, поскольку C ++ 11 использует черновой стандарт C99 для стандартной библиотеки C и, следовательно, предоставляет std :: round и для целочисленных возвращаемых типов std :: lround, std :: llround :
Другой вариант также из C99 будет std :: trunc, который:
Если вам необходимо поддерживать приложения, не относящиеся к C ++ 11, лучше всего использовать усиление раунда, округления, округления, округления или усиления .
Раскатать свою версию раунда сложно
Свернуть свое собственное, вероятно, не стоит таких усилий, как сложнее, чем кажется: округление числа с плавающей точкой до ближайшего целого числа, часть 1 , округление числа с плавающей точкой до ближайшего целого числа, часть 2 и округление числа с плавающей точкой до ближайшего целого числа, часть 3 объясняют:
Например, общий ролл, в котором ваша реализация использует
std::floor
и добавляет0.5
, не работает для всех входовОдин вход, для которого это не удастся
0.49999999999999994
, ( посмотреть его вживую ).Другая распространенная реализация включает приведение типа с плавающей запятой к интегральному типу, который может вызывать неопределенное поведение в случае, когда интегральная часть не может быть представлена в типе назначения. Мы можем видеть это из черновика стандартного раздела C ++
4.9
преобразования с плавающей запятой, который гласит ( выделение мое ):Например:
Учитывая
std::numeric_limits<unsigned int>::max()
это ,4294967295
то следующий вызов:вызовет переполнение, ( смотрите его вживую ).
Мы можем увидеть, насколько это действительно сложно, посмотрев на этот ответ в Кратком способе реализации round () в C? которая ссылается на версию newlibs одинарной точности float round. Это очень длинная функция для чего-то, что кажется простым. Представляется маловероятным, чтобы кто-либо, не имеющий глубоких знаний о реализации с плавающей запятой, мог правильно реализовать эту функцию:
С другой стороны, если ни одно из других решений не является пригодным для использования, newlib потенциально может быть вариантом, поскольку это хорошо проверенная реализация.
источник
round(-0.0)
. C spec не указывается, чтобы указать. Я ожидаю-0.0
в результате.std::rint()
часто предпочтительнее,std::round()
когда C ++ 11 доступен по численным причинам и по соображениям производительности. Он использует текущий режим округления, в отличие отround()
специального режима. Это может быть гораздо более эффективным на x86, гдеrint
может быть встроена в одну инструкцию. (gcc и clang делают это даже без-ffast-math
godbolt.org/g/5UsL2e , в то время как только clang указывает на почти эквивалентныйnearbyint()
) ARM имеет поддержку для одной инструкцииround()
, но на x86 он может быть встроенным только с несколькими инструкциями и только с-ffast-math
Возможно, стоит отметить, что если вы хотите получить целочисленный результат округления, вам не нужно пропускать его через потолок или пол. То есть,
источник
Он доступен с C ++ 11 в cmath (согласно http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ).
Вывод:
источник
lround
иllround
для интегральных результатовlrint
чтобы использовать текущий режим округления вместоround
фанк-тай-брейка.Обычно это реализуется как
floor(value + 0.5)
.Редактировать: и, вероятно, он не называется округлением, поскольку есть по крайней мере три известных мне алгоритма округления: округление до нуля, округление до ближайшего целого и округление банкира. Вы запрашиваете округление до ближайшего целого числа.
источник
Есть 2 проблемы, на которые мы смотрим:
Округление конверсий означает округление ± число с плавающей запятой / двойное значение до ближайшего этажа / число с плавающей запятой / двойное число. Может быть, ваша проблема заканчивается здесь. Но если ожидается, что вы вернете Int / Long, вам необходимо выполнить преобразование типов, и, таким образом, проблема «переполнения» может поразить ваше решение. ТАК, сделайте проверку на наличие ошибок в вашей функции
от: 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
приведен. Другие угловые вопросы тоже.round(double)
, уже есть стандартная математическая библиотечная функция с таким именем (в C ++ 11), так что это сбивает с толку. Используйте,std::lrint(x)
если это доступно.Определенный тип округления также реализован в Boost:
Обратите внимание, что это работает, только если вы делаете целочисленное преобразование.
источник
boost:numeric::RoundEven< double >::nearbyint
напрямую, если вы не хотите, чтобы целое число. @DanielWolf, обратите внимание, что простая функция реализована с использованием +0.5, что имеет проблемы, изложенные в aka.niceВы можете округлить до n цифр с точностью:
источник
int
. (На практике на x86 значения FP вне допустимого диапазона будутCVTTSD2SI
производить0x80000000
как целочисленный битовый шаблон, то естьINT_MIN
, который затем будет преобразован обратноdouble
.В наши дни не должно быть проблемой использовать компилятор 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.1roundpd
(или AVXvroundpd
). (FP-> целочисленные преобразования также доступны в упакованном виде SIMD, за исключением FP-> 64-битного целого числа, которое требует AVX512.)std::nearbyint()
:-msse4.1
.-msse4.1 -ffast-math
и только на gcc 5.4 и более ранних версиях . Позже gcc никогда не указывает на это (возможно, они не понимали, что один из непосредственных битов может подавить неточное исключение? Это то, что использует clang, но более старый gcc использует то же самое, что и дляrint
когда он встроен)std::rint
:-msse4.1
-msse4.1
. (Без SSE4.1, содержит несколько инструкций)-ffast-math -msse4.1
.std::round
:-ffast-math -msse4.1
, требуя двух векторных констант.std::floor
/std::ceil
/std::trunc
-msse4.1
-msse4.1
-ffast-math -msse4.1
Округление до
int
/long
/long long
:У вас есть два варианта: использовать
lrint
(например,rint
но возвращаетlong
илиlong long
дляllrint
) или использовать функцию округления FP-> FP, а затем преобразовать в целочисленный тип обычным способом (с усечением). Некоторые компиляторы оптимизируют один путь лучше, чем другой.Обратите внимание, что сначала
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 в / из
unsigned
int / 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
встроенных символов-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
не встроенный-ffast-math
:(int/long)rint
компилирует вызовlrint
.lrint
не встроенный Это может быть пропущенной оптимизацией, если только две инструкции, которые мы получаем, не-ffast-math
очень медленные.источник
rint()
где это выполнимый выбор, который обычно имеет место. Я предполагаю, что названиеround()
подразумевает для некоторых программистов, что это то, что они хотят, хотяrint()
кажется загадочным. Обратите внимание, чтоround()
не используется «фанковый» режим округления: округление до ближайших связей является официальным режимом округления IEEE-754 (2008). Любопытно, чтоnearbyint()
это не вписывается, учитывая, что это в значительной степени то же самоеrint()
, и должно быть идентичным в-ffast-math
условиях. Это выглядит ошибочно для меня.Остерегайтесь
floor(x+0.5)
. Вот что может произойти для нечетных чисел в диапазоне [2 ^ 52,2 ^ 53]:Это http://bugs.squeak.org/view.php?id=7134 . Используйте решение, подобное @konik.
Моя собственная надежная версия будет выглядеть примерно так:
Другая причина избегать пола (х + 0,5) приведена здесь .
источник
Если вы в конечном итоге захотите преобразовать
double
вывод вашейround()
функции в aint
, то принятые решения этого вопроса будут выглядеть примерно так:Это время составляет около 8,88 нс на моей машине, когда передается в равномерно случайных значениях.
Ниже, насколько я могу судить, функционально эквивалентен, но на моей машине тактовая частота составляет 2,48 нс , что значительно повышает производительность:
Среди причин лучшей производительности - пропущенное ветвление.
источник
int
. (На практике на x86 значения FP вне допустимого диапазона приводят кCVTTSD2SI
получению в0x80000000
виде целочисленной битовой комбинации, т.INT_MIN
double
Не нужно ничего реализовывать, поэтому я не уверен, почему так много ответов связаны с определениями, функциями или методами.
В C99
У нас есть следующий заголовок и и <tgmath.h> для макросов общего типа.
Если вы не можете скомпилировать это, вы, вероятно, не учли математическую библиотеку. Команда, подобная этой, работает на каждом компиляторе C, который у меня есть (несколько).
В С ++ 11
У нас есть следующие и дополнительные перегрузки в #include <cmath>, которые полагаются на IEEE с плавающей запятой двойной точности.
Есть эквиваленты и в пространстве имен 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.
С Порядковым Подразделением
При делении двух порядковых чисел, где T - это короткий, int, long или другой порядковый номер, выражение округления таково.
точность
Нет сомнений в том, что странные неточности появляются в операциях с плавающей запятой, но это только тогда, когда появляются числа, и имеет мало общего с округлением.
Источником является не просто число значащих цифр в мантиссе представления IEEE числа с плавающей запятой, оно связано с нашим десятичным мышлением как людей.
Десять - это произведение пяти и двух, а 5 и 2 относительно простые. Поэтому стандарты IEEE с плавающей запятой не могут быть идеально представлены в виде десятичных чисел для всех двоичных цифровых представлений.
Это не проблема с алгоритмами округления. Это математическая реальность, которую следует учитывать при выборе типов и разработке вычислений, вводе данных и отображении чисел. Если приложение отображает цифры, которые показывают эти десятичные двоичные проблемы преобразования, то приложение визуально выражает точность, которая не существует в цифровой реальности и должна быть изменена.
источник
функция
double round(double)
с использованиемmodf
функции:Чтобы быть чистым при компиляции, необходимо включить «math.h» и «limit». Функция работает в соответствии со следующей схемой округления:
источник
rint()
илиnearbyint()
, но если вы действительно не можете использовать компилятор, который обеспечивает правильную функцию округления, и вам нужна точность больше, чем производительность ...Если вам нужно иметь возможность компилировать код в средах, которые поддерживают стандарт C ++ 11, но также нужно иметь возможность компилировать этот же код в средах, которые его не поддерживают, вы можете использовать функциональный макрос для выбора между std :: round () и пользовательская функция для каждой системы. Просто передайте
-DCPP11
или/DCPP11
на C ++ 11-совместимый компилятор (или использовать его встроенные версии макросов), и сделать заголовок , как это:Для быстрого примера см. Http://ideone.com/zal709 .
Это приблизительно соответствует std :: round () в средах, не совместимых с C ++ 11, включая сохранение знака знака для -0.0. Однако это может привести к незначительному снижению производительности и, вероятно, к проблемам с округлением определенных известных «проблемных» значений с плавающей запятой, таких как 0,49999999999999994 или аналогичных значений.
В качестве альтернативы, если у вас есть доступ к C ++ 11-совместимому компилятору, вы можете просто взять std :: round () из его
<cmath>
заголовка и использовать его для создания собственного заголовка, который определяет функцию, если она еще не определена. Обратите внимание, что это может быть не оптимальным решением, особенно, если вам нужно компилировать для нескольких платформ.источник
Основываясь на ответе Kalaxy, следующее представляет собой шаблонное решение, которое округляет любое число с плавающей запятой до ближайшего целочисленного типа на основе естественного округления. Он также выдает ошибку в режиме отладки, если значение выходит за пределы диапазона целочисленного типа, тем самым служа примерно как жизнеспособная библиотечная функция.
источник
0.5
не работает во всех случаях. Хотя, по крайней мере, вы имеете дело с проблемой переполнения, поэтому избегаете неопределенного поведения.Как указано в комментариях и других ответах, стандартная библиотека 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()
, однако код структурно идентичен реализации с одинарной точностью.источник
int
его ширина превышает 16 бит. Это все еще, конечно, предполагает, чтоfloat
это 4-байтовый двоичный код IEEE75432. C ++ 11static_assert
или, может быть, макрос#ifdef
/#error
может это проверить. (Но, конечно, если доступен C ++ 11, вы должны использоватьstd::round
или для текущего режима округления,std::rint
который прекрасно сочетается с gcc и clang).gcc -ffast-math -msse4.1
inlinesstd::round()
toadd( AND(x, L1), OR(x,L2)
, а затемroundsd
. то есть он довольно эффективно реализуетсяround
с точки зренияrint
. Но нет причин делать это вручную в исходном коде C ++, потому что, если у вас естьstd::rint()
или уstd::nearbyint()
вас также естьstd::round()
. Смотрите мой ответ для ссылки Godbolt и краткое изложение того, что встраивается или нет с различными версиями gcc / clang.round()
эффективно реализовать с точки зренияrint()
(когда последний работает в режиме округления до ближайшего или даже): я реализовал это для стандартной математической библиотеки CUDA. Тем не менее, этот вопрос, похоже, задает вопрос о том, как реализоватьround()
с C ++ до C ++ 11, поэтомуrint()
также не будет доступен, толькоfloor()
иceil()
.round()
легко синтезируется изrint()
в режиме округления до нуля , иначеtrunc()
. Не должен был отвечать до первого кофе.round()
; большинство программистов просто не знают о различии междуround()
противrint()
с круглым к ближайшим даже, когда последний, как правило , предоставляется непосредственно аппаратными средствами и , следовательно , более эффективным; Я изложил это в Руководстве по программированию CUDA, чтобы программисты знали: «Рекомендуемый способ округлять операнд с плавающей запятой одинарной точности до целого числа, в результате чего число с плавающей запятой одинарной точности равноrintf()
, а неroundf()
».Я использую следующую реализацию раунда в asm для архитектуры x86 и MS VS для C ++:
UPD: вернуть двойное значение
Вывод:
источник
rint()
либоnearbyint()
вroundsd
инструкцию SSE4.1, либо вfrndint
инструкцию x87 , которая будет намного быстрее, чем две циклические операции сохранения / перезагрузки, необходимые для использования этого встроенного ассемблера для данных в регистре. Встроенный ассемблер MSVC - это много, чтобы обернуть отдельные инструкции, например,frndint
потому что нет способа получить ввод в регистр. Использование его в конце функции с результатомst(0)
может быть надежным способом возврата результата; по-видимому, это безопасно дляeax
целых чисел, даже если оно содержит функцию, содержащую asm.double
, и, следовательно, должен иметь возможность генерироватьfrndint
себяrint()
. Если ваш компилятор использует SSE2, возвратdouble
из регистра XMM в x87 и обратно может не стоить этого.Лучший способ округлить плавающее значение с помощью «n» десятичных разрядов, как показано в O (1) раз: -
Мы должны округлить значение на 3 места, т.е. n = 3. Так что
источник
Это может быть неэффективный грязный способ преобразования, но, черт возьми, это работает, лол. И это хорошо, потому что это относится к фактическому плаванию. Не только визуально влияет на результат.
источник
Я сделал это:
источник