我需要一个简单的浮点数舍入函数,例如:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
我可以在math.h中找到ceil()
和floor()
函数,但是没有找到round()
函数。
它是否以另一个名称存在于标准C++库中,还是缺失了?
我需要一个简单的浮点数舍入函数,例如:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
我可以在math.h中找到ceil()
和floor()
函数,但是没有找到round()
函数。
它是否以另一个名称存在于标准C++库中,还是缺失了?
编辑注:以下答案提供了一个简单的解决方案,但包含多个实现缺陷(请参阅Shafik Yaghmour's answer获取完整说明)。请注意,C++11已经将
std::round
、std::lround
和std::llround
作为内置函数包含在标准库中。
C++98标准库中没有round()函数。但你可以自己编写一个。以下是一个round-half-up的实现:
double round(double d)
{
return floor(d + 0.5);
}
如果我们查看cppreference上关于round、lround、llround的C文档,我们可以看到,round和相关函数属于C99,因此在C++03或之前的版本中不可用。ISO/IEC 9899:1990和ISO/IEC 9899/Amd.1:1995第7条所描述的库以下简称标准C库。1)
#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 ;
}
#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 ;
}
我建议您拒绝自己编写相关内容,因为难度超乎想象:四舍五入浮点数到最接近的整数,第1部分, 四舍五入浮点数到最接近的整数,第2部分和四舍五入浮点数到最接近的整数,第3部分都有所解释:
例如,常见的使用std::floor
并添加0.5
的实现并不能适用于所有输入:
double myround(double d)
{
return std::floor(d + 0.5);
}
其中一个会失败的输入是0.49999999999999994
(在此查看实时演示)。
另一种常见的实现涉及将浮点类型转换为整数类型,如果整数部分无法在目标类型中表示,则可能引发未定义的行为。我们可以从C++标准草案第4.9
节浮点-整数转换中看到这一点,该节说道(我强调):
浮点类型的prvalue可以转换为整数类型的prvalue。 转换截断; 也就是说,小数部分被丢弃。如果截断的值不能在目标类型中表示,则行为未定义。[...]
例如:
float myround(float f)
{
return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}
假设std::numeric_limits<unsigned int>::max()
的值为4294967295
,则下列调用:
myround( 4294967296.5f )
会导致溢出,(在此查看实际效果)。
通过查看在C中实现round()的简洁方法的答案,我们可以看到这真的很困难,它引用了newlibs版本的单精度浮点数round函数。对于似乎很简单的东西来说,这是一个非常长的函数。似乎没有人除了对浮点数实现有深入了解的人能够正确地实现这个函数:
float roundf(x)
{
int signbit;
__uint32_t w;
/* Most significant word, least significant word. */
int exponent_less_127;
GET_FLOAT_WORD(w, x);
/* Extract sign bit. */
signbit = w & 0x80000000;
/* Extract exponent field. */
exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
if (exponent_less_127 < 23)
{
if (exponent_less_127 < 0)
{
w &= 0x80000000;
if (exponent_less_127 == -1)
/* Result is +1.0 or -1.0. */
w |= ((__uint32_t)127 << 23);
}
else
{
unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
if ((w & exponent_mask) == 0)
/* x has an integral value. */
return x;
w += 0x00400000 >> exponent_less_127;
w &= ~exponent_mask;
}
}
else
{
if (exponent_less_127 == 128)
/* x is NaN or infinite. */
return x + x;
else
return x;
}
SET_FLOAT_WORD(x, w);
return x;
}
round(-0.0)
。C规范似乎没有指定。我期望结果为-0.0
。 - chux - Reinstate Monicastd::rint()
在数字和性能方面通常优于std::round()
。它使用当前的舍入模式,而不像round()
的特殊模式。在x86上,它可以更加高效,其中rint
可以内联为单个指令。(即使没有-ffast-math
,gcc和clang也会这样做https://godbolt.org/g/5UsL2e,而只有clang内联了几乎相同的`nearbyint()`) ARM支持round()
的单指令,但在x86上,它只能通过多个指令进行内联,并且只能使用-ffast-math
。 - Peter Cordes#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
和 std::llround
。
floor(value + 0.5)
方法好多了!+1 - Gustavo Macielfloor(value + 0.5)
。 - n. m.floor(value + 0.5)
并不幼稚,而是取决于你想要舍入的值的上下文和性质! - Gustavo Maciel值得注意的是,如果你想要一个整数结果,你不需要将它通过 ceil 或 floor 函数取整。也就是说:
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
lround
和llround
可用于整数结果。 - sp2danny这通常被实现为 floor(value + 0.5)
。
编辑:由于至少有三种取整算法:向零取整、四舍五入和银行家舍入,因此它可能不被称为round。你正在寻找的是四舍五入到最接近的整数。
我们正在考虑两个问题:
四舍五入转换意味着将±浮点数/双精度浮点数舍入到最近的地板/天花板浮点数/双精度浮点数。 也许你的问题就在这里解决了。 但是如果您需要返回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))
LONG_MIN-0.5
和 LONG_MAX+0.5
会引入复杂性,因为数学计算可能不精确。LONG_MAX
可能超出了 double
精度的精确转换范围。进一步地,可能需要 assert(x < LONG_MAX+0.5);
(使用 <
而非 <=
),因为 LONG_MAX+0.5
可能是可精确表示的,而 (x)+0.5
可能具有精确结果 LONG_MAX+1
,这将导致 long
强制转换失败。还有其他一些边角问题。 - chux - Reinstate Monicaround(double)
,因为在标准的 math 库中已经有同名的函数(在 C++11 中),这会造成混淆。如果可用,应使用 std::lrint(x)
。 - Peter Cordes#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;
}
boost:numeric::RoundEven< double >::nearbyint
。请注意,简单函数是使用 +0.5 实现的,这可能会出现 aka.nice 所述的问题。 - stijncall
之间保持任何FP值在XMM寄存器中。因此,即使您不真正了解汇编语言,您仍然可以轻松地看出它是否只是一个尾调用库函数,还是内联到一个或两个数学指令。任何内联到一个或两个指令的内容都比函数调用更好(对于x86或ARM上的这个特定任务)。roundsd
的内容都可以自动向量化为SSE4.1 roundpd
(或AVX vroundpd
)。 (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 转换函数,然后以正常方式将其转换为整数类型(通过截断)。一些编译器比另一些更好地优化其中的一种方法。
long l = lrint(x);
int i = (int)rint(x);
int i = lrint(x)
将 float
或 double
转换为 long
,然后将整数截断为 int
。这对于超出范围的整数有所区别:在C++中未定义行为,但对于x86 FP-> int指令是明确定义的(编译器会发出这些指令,除非它在编译时看到UB并进行常量传播,然后它可以生成在执行时会出错的代码)。INT_MIN
或 LLONG_MIN
(一个位模式为 0x8000000
或其64位等效项,只有符号位被设置)。英特尔将此称为“整数不定值”。 (请参见 cvttsd2si
手动输入,SSE2指令将标量双精度转换为有符号整数(截断)。它可用于32位或64位整数目标(仅在64位模式下)。还有一个cvtsd2si
(使用当前舍入模式转换),这是我们希望编译器发出的,但不幸的是,gcc和clang不会在没有-ffast-math
的情况下这样做。unsigned
int / long之间的转换(没有AVX512)效率较低。在64位机器上将其转换为32位无符号整数相当便宜;只需将其转换为64位有符号整数并截断即可。但是,否则它会明显变慢。
带/不带-ffast-math -msse4.1
的x86 clang编译器:(int/long)rint
内联到roundsd
/ cvttsd2si
(cvtsd2si
的优化被忽略了)。lrint
完全没有内联。
没有-ffast-math
的x86 gcc6.x及之前版本:两种方式都不会内联。
-ffast-math
: (int/long)rint
分别进行舍入和转换(如果启用了2个SSE4.1总指令,则为2个总指令,否则会对rint
进行大量代码内联而没有roundsd
)。lrint
不内联。x86 gcc 使用 -ffast-math
:所有方式都内联到cvtsd2si
(最佳),不需要SSE4.1。
没有-ffast-math
的AArch64 gcc6.3: (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
条件下应该是相同的。这看起来像是一个错误。 - njuffa您可以使用以下方法将数字保留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;
}
int
范围的参数,这将产生未定义行为。(在 x86 上实际上,超出范围的 FP 值 将使 CVTTSD2SI
生成 0x80000000
作为整数位模式,即 INT_MIN
,然后将被转换回 double
。 - Peter Cordes
std::cout << std::fixed << std::setprecision(0) << -0.9
。 - Frankround
函数可以在<cmath>
头文件中使用。不幸的是,如果你在 Microsoft Visual Studio 中使用,该函数仍然缺失:https://connect.microsoft.com/VisualStudio/feedback/details/775474/missing-round-functions-in-standard-library。 - Alessandro Jacopson