Rod*_*ddy 227 c++ floating-point rounding
我需要一个简单的浮点舍入函数,因此:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
Run Code Online (Sandbox Code Playgroud)
我能找到ceil()和floor()在math.h中-但不是round().
它是以另一个名称存在于标准C++库中,还是缺少?
And*_*son 143
C++ 98标准库中没有round().你可以自己写一个.以下是圆形上半部分的实现:
double round(double d)
{
return floor(d + 0.5);
}
Run Code Online (Sandbox Code Playgroud)
C++ 98标准库中没有循环函数的可能原因是它实际上可以以不同的方式实现.以上是一种常见的方式,但还有其他一些方法,例如圆形到偶数,如果你要进行大量的四舍五入,那么偏差较小,通常会更好; 但实施起来有点复杂.
Dan*_*olf 95
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
Run Code Online (Sandbox Code Playgroud)
有关更多信息,请参阅Boost文档.
编辑:既然C++ 11,还有std::round,std::lround和std::llround.
Sha*_*our 81
C++ 03标准依赖于C90标准,标准称为标准C库,C++ 03标准草案(最接近 C++ 03的公开标准草案是N1804)部分1.2 规范性引用:
ISO/IEC 9899:1990的第7节中描述的库和ISO/IEC 9899/Amd.1:1995的第7节在下文中称为标准C库.1)
如果我们在cppreference上找到round,lround,llround的C文档,我们可以看到round和相关函数是C99的一部分,因此在C++ 03或之前不可用.
在C++ 11中,这种情况发生了变化,因为C++ 11依赖于C标准库的C99草案标准,因此提供了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 ;
}
Run Code Online (Sandbox Code Playgroud)
来自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 ;
}
Run Code Online (Sandbox Code Playgroud)
如果你需要支持非C++ 11应用程序,你最好的选择是使用boost round,iround,lround,llround或boost trunc.
滚动你自己的版本很难
滚动你自己可能不值得努力,比起看起来更难:将浮动舍入到最接近的整数,第1部分,舍入浮点数到最接近的整数,第2部分和舍入浮点数到最接近的整数,第3部分解释:
例如,使用std::floor和添加实现的常见滚动0.5不适用于所有输入:
double myround(double d)
{
return std::floor(d + 0.5);
}
Run Code Online (Sandbox Code Playgroud)
这将失败的一个输入是0.49999999999999994,(见它直播).
另一个常见的实现涉及将浮点类型转换为整数类型,在整数部分无法在目标类型中表示的情况下,可以调用未定义的行为.我们可以从C++标准部分的4.9 浮动积分转换中看到这一点,它说(强调我的):
可以将浮点类型的prvalue转换为整数类型的prvalue.转换截断; 也就是说,丢弃小数部分.如果截断的值无法在目标类型中表示,则行为未定义.[...]
例如:
float myround(float f)
{
return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}
Run Code Online (Sandbox Code Playgroud)
由于std::numeric_limits<unsigned int>::max()是4294967295那么下面的电话:
myround( 4294967296.5f )
Run Code Online (Sandbox Code Playgroud)
会导致溢出,(见它直播).
通过查看在C中实现round()的简洁方法的答案,我们可以看出这是多么困难?引用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;
}
Run Code Online (Sandbox Code Playgroud)
另一方面,如果其他解决方案都不可用,则newlib可能是一个选项,因为它是一个经过良好测试的实现.
kal*_*axy 71
值得注意的是,如果你想要一个舍入的整数结果,你不需要通过ceil或floor.也就是说,
int round_int( double r ) {
return (r > 0.0) ? (r + 0.5) : (r - 0.5);
}
Run Code Online (Sandbox Code Playgroud)
小智 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;
}
Run Code Online (Sandbox Code Playgroud)
输出:
round(0.5): 1
round(-0.5): -1
round(1.4): 1
round(-1.4): -1
round(1.6): 2
round(-1.6): -2
Run Code Online (Sandbox Code Playgroud)
MSN*_*MSN 27
它通常被实现为floor(value + 0.5).
编辑:它可能不会被调用,因为我知道至少有三种舍入算法:舍入到零,舍入到最接近的整数,以及银行家的舍入.你要求舍入到最接近的整数.
小智 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))
Run Code Online (Sandbox Code Playgroud)
来自:http://www.cs.tut.fi/~jkorpela/round.html
Phi*_*ipp 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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,这仅适用于进行整数转换.
如今,使用包含 C99/C++11 数学库的 C++11 编译器应该不是问题。但接下来的问题就变成了:您选择哪个舍入函数?
C99/C++11round()实际上通常不是您想要的舍入函数。它使用一种时髦的舍入模式,从 0 舍入作为中途情况下的抢七局 ( +-xxx.5000)。如果您确实特别想要这种舍入模式,或者您的目标round()是比 快的 C++ 实现rint(),则使用它(或使用该问题的其他答案之一模拟其行为,该答案以表面价值并仔细复制该特定舍入行为。)
round()的舍入与 IEEE754 默认舍入到最近模式不同,甚至作为 tie-break。Nearest-even 避免了数字平均大小的统计偏差,但会偏向偶数。
有两个数学库舍入函数使用当前的默认舍入模式:std::nearbyint()and std::rint(),它们都是在 C99/C++11 中添加的,因此它们随时可用std::round()。唯一的区别是nearbyint从不引发 FE_INEXACT。
rint()出于性能原因更喜欢:gcc 和 clang 都更容易内联它,但 gcc 从不内联nearbyint()(即使有-ffast-math)
我在 Matt Godbolt 的 Compiler Explorer 上放了一些测试函数,在那里你可以看到 source + asm 输出(对于多个编译器)。有关阅读编译器输出的更多信息,请参阅此问答以及 Matt 的 CppCon2017 演讲:“我的编译器最近为我做了什么?打开编译器的盖子”,
在 FP 代码中,内联小函数通常是一个很大的胜利。特别是在非 Windows 上,标准调用约定没有调用保留寄存器,因此编译器无法在call. 因此,即使您并不真正了解 asm,您仍然可以轻松看出它是否只是对库函数的尾调用,还是内联到一两个数学指令。任何内联到一两条指令的东西都比函数调用要好(对于 x86 或 ARM 上的这个特定任务)。
在 x86 上,任何内联到 SSE4.1 的东西都roundsd可以使用 SSE4.1 roundpd(或 AVX vroundpd)自动矢量化。(FP-> 整数转换也可以压缩 SIMD 形式使用,但需要 AVX512 的 FP->64 位整数除外。)
std::nearbyint():
-msse4.1。-msse4.1 -ffast-math仅在 gcc 5.4 及更早版本上使用 内联到单个 insn 。后来 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.1int/ long/ long long:您在这里有两个选择:使用lrint(例如rint但返回long或long longfor llrint),或使用 FP->FP 舍入函数,然后以正常方式(使用截断)转换为整数类型。一些编译器以一种方式优化比另一种更好。
long l = lrint(x);
int i = (int)rint(x);
Run Code Online (Sandbox Code Playgroud)
请注意,首先int i = lrint(x)转换float或double-> long,然后将整数截断为int。这对超出范围的整数有所不同:C++ 中的未定义行为,但为 x86 FP -> int 指令定义良好(编译器将发出该指令,除非它在编译时在进行常量传播时看到 UB,那么它是允许编写在执行时会中断的代码)。
在 x86 上,溢出整数的 FP->integer 转换会产生INT_MINor LLONG_MIN(0x8000000或 64 位等效的位模式,仅设置符号位)。英特尔将此称为“整数不定”值。(见的cvttsd2si手动输入时,SSE2指令转换(与截断)标量双到符号整数,它是可用的32位或64位的整数的目的地(在仅64位模式)。也有一个cvtsd2si(转换与当前的舍入模式),这是我们希望编译器发出的,但不幸的是,如果没有-ffast-math.
还要注意 FP 到/从unsignedint/long 在 x86(没有 AVX512)上效率较低。在 64 位机器上转换为 32 位无符号非常便宜;只需转换为 64 位签名并截断即可。但否则它会明显变慢。
x86 clang with/without -ffast-math -msse4.1: 内(int/long)rint联到roundsd/ cvttsd2si。(错过了优化cvtsd2si)。 lrint根本不内联。
x86 gcc6.x 及更早版本没有-ffast-math: 两种方式内联
-ffast-math: 的x86 gcc7 分别进行舍入(int/long)rint和转换(启用 SSE4.1 的总共 2 条指令,否则会为rint不带内联一堆代码roundsd)。 lrint不内联。x86 gcc with -ffast-math : all way inline to cvtsd2si(optimal),不需要SSE4.1。
AArch64 gcc6.3 没有-ffast-math: 内(int/long)rint联到 2 条指令。 lrint不内联
-ffast-math:(int/long)rint编译为对lrint. lrint不内联。这可能是一个错过的优化,除非我们得到的两条指令-ffast-math非常慢。您可以使用以下方法舍入到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;
}
Run Code Online (Sandbox Code Playgroud)
如果您最终想要将函数的double输出转换round()为a int,那么此问题的可接受解决方案将类似于:
int roundint(double r) {
return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}
Run Code Online (Sandbox Code Playgroud)
当以均匀随机值传递时,我的机器上的时钟大约为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;
}
Run Code Online (Sandbox Code Playgroud)
性能更好的原因之一是跳过分支.
| 归档时间: |
|
| 查看次数: |
362609 次 |
| 最近记录: |