我需要一个简单的浮点舍入函数,因此:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
我可以在math.h中找到ceil()
和floor()
- 但不是round()
。
它是以其他名称存在于标准C ++库中,还是缺少?
答案 0 :(得分:139)
C ++ 98标准库中没有round()。你可以自己写一个。以下是round-half-up的实现:
double round(double d)
{
return floor(d + 0.5);
}
C ++ 98标准库中没有循环函数的可能原因是它实际上可以以不同的方式实现。以上是一种常见的方式,但还有其他一些方法,例如round-to-even,如果你要进行大量的舍入,那么它就会有较少的偏见并且通常会更好。但实施起来有点复杂。
答案 1 :(得分:94)
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 documentation。
编辑:从C ++ 11开始,有std::round
, std::lround
, and std::llround
。
答案 2 :(得分:77)
C ++ 03标准依赖于C90标准,该标准用于标准称为标准C库的内容,该标准涵盖在C ++ 03标准草案中( closest publicly available draft standard to C++03 is N1804 )部分1.2
规范性参考:
ISO / IEC 9899:1990第7条和第7条第7款所述的库 ISO / IEC 9899 / Amd.1:1995在下文中称为标准C. 库。 1)
如果我们转到C documentation for round, lround, llround on cppreference,我们可以看到 round 和相关函数是 C99 的一部分,因此无法在C中使用++ 03或之前。
在C ++ 11中,这种情况发生了变化,因为C ++ 11依赖于 C标准库的C99草案标准,因此提供了std::round and for integral return types std::lround, std::llround:
#include <iostream>
#include <cmath>
int main()
{
std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}
C99的另一个选项是std::trunc,其中:
计算幅度不大于arg的最接近整数。
#include <iostream>
#include <cmath>
int main()
{
std::cout << std::trunc( 0.4 ) << std::endl ;
std::cout << std::trunc( 0.9 ) << std::endl ;
std::cout << std::trunc( 1.1 ) << std::endl ;
}
如果您需要支持非C ++ 11应用程序,最好的方法是使用boost round, iround, lround, llround或boost trunc。
滚动你自己的回合版本很难
滚动自己可能不值得,Harder than it looks: rounding float to nearest integer, part 1,Rounding float to nearest integer, part 2和Rounding float to nearest integer, part 3解释:
例如,使用std::floor
并添加0.5
的常规动作不适用于所有输入:
double myround(double d)
{
return std::floor(d + 0.5);
}
这个失败的输入是0.49999999999999994
,( see it live )。
另一个常见的实现涉及将浮点类型转换为整数类型,在整数部分无法在目标类型中表示的情况下,可以调用未定义的行为。我们可以从草案C ++标准部分4.9
浮动积分转换中看到这一点(强调我的):
浮点类型的prvalue可以转换为a的prvalue 整数类型。转换截断;也就是说,小数部分 被丢弃了。 如果截断值不能,则行为未定义 用目的地类型表示。 [...]
例如:
float myround(float f)
{
return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}
鉴于std::numeric_limits<unsigned int>::max()
为4294967295
,则接下来的电话:
myround( 4294967296.5f )
会导致溢出,( see it live )。
通过查看引用 newlibs 版本的单精度浮点数的Concise way to implement round() in 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;
}
另一方面,如果没有其他解决方案可用, newlib 可能是一个选项,因为它是一个经过良好测试的实现。
答案 3 :(得分:71)
值得注意的是,如果你想要一个舍入的整数结果,你不需要通过ceil或floor。即,
int round_int( double r ) {
return (r > 0.0) ? (r + 0.5) : (r - 0.5);
}
答案 4 :(得分:41)
自C ++ 11以来可以在cmath中使用(根据http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf)
#include <cmath>
#include <iostream>
int main(int argc, char** argv) {
std::cout << "round(0.5):\t" << round(0.5) << std::endl;
std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
std::cout << "round(1.4):\t" << round(1.4) << std::endl;
std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
std::cout << "round(1.6):\t" << round(1.6) << std::endl;
std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
return 0;
}
输出:
round(0.5): 1
round(-0.5): -1
round(1.4): 1
round(-1.4): -1
round(1.6): 2
round(-1.6): -2
答案 5 :(得分:27)
它通常以floor(value + 0.5)
实现。
编辑:它可能不会被调用,因为我知道至少有三种舍入算法:舍入到零,舍入到最接近的整数,以及银行家的舍入。你要求舍入到最接近的整数。
答案 6 :(得分:13)
我们正在考虑两个问题:
舍入转换意味着舍入±float / double到最近的floor / ceil 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))
答案 7 :(得分: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;
}
请注意,这仅适用于进行整数转换。
答案 8 :(得分: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;
}
答案 9 :(得分:5)
如果您最终希望将double
函数的round()
输出转换为int
,则此问题的已接受解决方案将如下所示:
int roundint(double r) {
return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}
当传入统一随机值时,我的机器上的时钟大约 8.88 ns 。
据我所知,以下功能相同,但在我的机器上以 2.48 ns 时钟,具有显着的性能优势:
int roundint (double r) {
int tmp = static_cast<int> (r);
tmp += (r-tmp>=.5) - (r-tmp<=-.5);
return tmp;
}
性能更好的原因之一是跳过分支。
答案 10 :(得分: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;
}
避免楼层(x + 0.5)的另一个原因是here。
答案 11 :(得分:3)
现在使用包含C99 / C ++ 11数学库的C ++ 11编译器不应该成为问题。但问题就变成了:你选择哪种舍入函数?
C99 / C ++ 11 round()
通常不是您想要的舍入功能。它采用了一种时髦的舍入模式,在中途情况下(0 +-xxx.5000
)作为抢劫时间从0开始。如果您确实需要这种舍入模式,或者您正在定位round()
比rint()
更快的C ++实现,那么请使用它(或使用其中一个其他答案模拟其行为)问题是从表面上看,并仔细地复制了特定的舍入行为。)
round()
的舍入与IEEE754默认round to nearest mode with even as a tie-break不同。最近 - 甚至避免了数字平均值的统计偏差,但偏向偶数。
有两个数学库舍入函数使用当前的默认舍入模式:std::nearbyint()
和std::rint()
,两者都在C99 / C ++ 11中添加,因此它们随时都可用{ {1}}是。唯一的区别是std::round()
永远不会引发FE_INEXACT。
出于性能原因首选nearbyint
:gcc和clang都更容易内联,但gcc从不内联rint()
(即使使用nearbyint()
)
I put some test functions on Matt Godbolt's Compiler Explorer,您可以在其中查看source + asm输出(适用于多个编译器)。有关阅读编译器输出的更多信息,请参阅this Q&A和Matt的CppCon2017演讲:“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”,
在FP代码中,它通常是内联小功能的一大胜利。特别是在非Windows上,标准调用约定没有调用保留的寄存器,因此编译器不能在-ffast-math
的XMM寄存器中保留任何FP值。所以,即使你真的不知道asm,你仍然可以很容易地看到它是否只是对函数库的尾调用,或者它是否内联到一个或两个数学指令。任何内联到一个或两个指令的内容都比函数调用更好(对于x86或ARM上的此特定任务)。
在x86上,任何内联到SSE4.1 call
的内容都可以使用SSE4.1 roundpd
(或AVX roundsd
)进行自动向量化。 (FP->整数转换也以压缩SIMD形式提供,但FP-> 64位整数除外,它需要AVX512。)
vroundpd
:
std::nearbyint()
内联到单个insn。-msse4.1
内联到单个insn,并且仅在gcc 5.4和之前。后来gcc从未介绍它(也许他们没有意识到其中一个直接位可以抑制不精确的异常?这是clang使用的,但是较旧的gcc使用与-msse4.1 -ffast-math
相同的立即数。它内联它) rint
:
std::rint
-msse4.1
内联到单个insn。 (没有SSE4.1,内联几条指令)-msse4.1
内联到单个insn。 -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
),或使用FP-&gt ; FP舍入函数然后以正常方式转换为整数类型(带截断)。有些编译器比其他编译器优化一种方式。
llrint
请注意,long l = lrint(x);
int i = (int)rint(x);
会转换int i = lrint(x)
或float
- &gt;首先double
,然后将整数截断为long
。这对于超出范围的整数有所不同:C ++中的未定义行为,但是对于x86 FP定义良好 - &gt; int指令(编译器将发出它,除非它在编译时在进行常量传播时看到UB,然后它允许使代码在执行时中断)。
在x86上,溢出整数的FP->整数转换产生int
或INT_MIN
(LLONG_MIN
的位模式或64位等价,只有符号集)。英特尔将此称为&#34;整数无限期&#34;值。 (参见the cvttsd2si
manual entry,将标量双精度转换(带截断)的SSE2指令转换为有符号整数。它可用于32位或64位整数目的地(仅限64位模式)。有&# 39; s也是0x8000000
(转换为当前舍入模式),这是我们希望编译器发出的,但遗憾的是gcc和clang在没有cvtsd2si
的情况下不会这样做
还要注意FP到/ -ffast-math
int / long的效率在x86上效率较低(没有AVX512)。在64位计算机上转换为32位无符号非常便宜;只需转换为64位signed和truncate。但是否则它会明显变慢。
x86 clang带/不带unsigned
:-ffast-math -msse4.1
内联到(int/long)rint
/ roundsd
。 (错过了cvttsd2si
的优化)。 cvtsd2si
根本没有内联。
x86 gcc6.x及更早版本没有lrint
:既没有内联方式
-ffast-math
:-ffast-math
轮次并单独转换(启用了SSE4.1的2条总指令,否则为(int/long)rint
内联的一堆代码没有{{1 }})。 rint
并非内联。 x86 gcc roundsd
:内联到lrint
(最佳)的所有方式,不需要SSE4.1。< / p>
没有-ffast-math
的AArch64 gcc6.3:cvtsd2si
内联到2条指令。 -ffast-math
没有内联
(int/long)rint
:lrint
编译为-ffast-math
。 (int/long)rint
并非内联。这可能是错过的优化,除非我们在没有lrint
的情况下获得的两条指令非常慢。答案 12 :(得分: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”和“限制”是必要的。该函数根据以下舍入模式工作:
答案 13 :(得分:2)
没有必要实现任何东西,所以我不确定为什么这么多答案涉及定义,函数或方法。
在C99
我们有以下内容和标题&lt; tgmath.h&gt;对于类型通用宏。
#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&lt; cmath&gt;中有以下和额外的重载这依赖于IEEE双精度浮点。
#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);
也有equivalents in the std namespace。
如果你无法编译它,你可能正在使用C编译而不是C ++。以下基本命令既不会产生错误也不会产生警告:g ++ 6.3.1,x86_64-w64-mingw32-g ++ 6.3.0,clang-x86_64 ++ 3.8.0和Visual C ++ 2015社区。 p>
g++ -std=c++11 -Wall
有序分部
当划分两个序数时,其中T为short,int,long或其他序数,舍入表达式为此。
T roundedQuotient = (2 * integerNumerator + 1)
/ (2 * integerDenominator);
<强>精度强>
毫无疑问,在浮点运算中出现奇怪的不准确性,但这仅在数字出现时才会出现,与舍入无关。
源不仅仅是浮点数的IEEE表示的尾数中的有效位数,它与我们作为人类的十进制思维有关。
Ten是5和2的乘积,5和2是相对素数。因此,IEEE浮点标准不可能完美地表示为所有二进制数字表示的十进制数。
这不是舍入算法的问题。在选择类型以及计算,数据输入和数字显示的设计时应考虑数学现实。如果应用程序显示显示这些十进制二进制转换问题的数字,那么应用程序将直观地表达数字现实中不存在的准确性,并应进行更改。
答案 14 :(得分:1)
根据Kalaxy的响应,以下是一个模板化解决方案,它根据自然舍入将任何浮点数舍入为最接近的整数类型。如果该值超出整数类型的范围,它也会在调试模式中抛出错误,从而大致用作可行的库函数。
// round a floating point number to the nearest integer
template <typename Arg>
int Round(Arg arg)
{
#ifndef NDEBUG
// check that the argument can be rounded given the return type:
if (
(Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
(Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
)
{
throw std::overflow_error("out of bounds");
}
#endif
return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
}
答案 15 :(得分:1)
正如评论和其他答案所指出的那样,ISO C ++标准库在ISO C ++ 11之前没有添加round()
,当通过引用ISO C99标准数学库引入此函数时。
对于[½, ub ] round(x) == floor (x + 0.5)
中的正操作数,其中 ub 为float
的2 23 当映射到IEEE-754(2008)binary32
时,double
映射到IEEE-754(2008)binary64
和2 52 时。数字23和52对应于这两种浮点格式中存储的尾数位的数量。对于[+ 0,½)round(x) == 0
中的正操作数,以及( ub ,+∞] round(x) == x
中的正操作数。由于函数关于x轴对称,负面论据x
可以根据round(-x) == -round(x)
处理。
这导致下面的紧凑代码。它在各种平台上编译成合理数量的机器指令。我观察到GPU上最紧凑的代码,其中my_roundf()
需要大约十几条指令。根据处理器体系结构和工具链,这种基于浮点的方法可能比different answer中引用的newlib基于整数的实现更快或更慢。
我使用英特尔编译器版本13({1}}和my_roundf()
对使用newlib roundf()
实现进行详尽测试/fp:strict
。我还检查了newlib版本是否与英特尔编译器的/fp:fast
库中的roundf()
匹配。对于双精度mathimf
,不可能进行详尽的测试,但是代码在结构上与单精度实现完全相同。
round()
答案 16 :(得分: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。
这在不符合C ++ 11的环境中近似std :: round(),包括保留-0.0的符号位。然而,这可能会导致轻微的性能损失,并且可能会出现绕过某些已知问题的问题。浮点值,如0.49999999999999994或类似值。
或者,如果您可以访问符合C ++ 11的编译器,则可以从其<cmath>
标头中获取std :: round(),并使用它来创建自定义函数头来定义函数如果它还没有定义。请注意,这可能不是最佳解决方案,特别是如果您需要为多个平台进行编译。
答案 17 :(得分:0)
我在x86体系结构和MS VS特定C ++的asm中使用了round的以下实现:
__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
答案 18 :(得分:0)
将浮点值四舍五入到小数点后第n位的最佳方法如下:O(1)时间:-
我们必须将值四舍五入为n = 3。所以,
float a=47.8732355;
printf("%.3f",a);
答案 19 :(得分:0)
从C ++ 11开始:
#include <cmath>
std::round(1.1)
或获取诠释
static_cast<int>(std::round(1.1))
答案 20 :(得分:-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");
这可能是一种低效的肮脏转换方式,但是,它很有效。它很好,因为它适用于实际浮动。不只是视觉上影响输出。
答案 21 :(得分:-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;
}
}