C++ 获取sqrt(n)整数部分的最快方法?
我们知道如果C++ 获取sqrt(n)整数部分的最快方法?,c++,c,algorithm,math,performance,C++,C,Algorithm,Math,Performance,我们知道如果n不是一个完美的平方,那么sqrt(n)就不是一个整数。因为我只需要整数部分,我觉得调用sqrt(n)不会那么快,因为计算小数部分也需要时间 所以我的问题是, 我们可以只得到sqrt(n)的整数部分而不计算sqrt(n)的实际值吗?算法应该比sqrt(n)(在或中定义)更快 如果可能的话,您也可以在asm块中编写代码。虽然我怀疑您可以通过搜索“快速整数平方根”找到大量选项,但以下是一些可能会很好工作的新想法(每个都是独立的,或者您可以将它们组合起来): 制作一个包含你想要支持的域中所
n
不是一个完美的平方,那么sqrt(n)
就不是一个整数。因为我只需要整数部分,我觉得调用sqrt(n)
不会那么快,因为计算小数部分也需要时间
所以我的问题是,
我们可以只得到sqrt(n)的整数部分而不计算sqrt(n)
的实际值吗?算法应该比sqrt(n)
(在
或
中定义)更快
如果可能的话,您也可以在
asm
块中编写代码。虽然我怀疑您可以通过搜索“快速整数平方根”找到大量选项,但以下是一些可能会很好工作的新想法(每个都是独立的,或者您可以将它们组合起来):
static const
数组,并对其执行快速无分支二进制搜索。数组中的结果索引是平方根1/sqrt(n)
非常好的近似值的方法,没有任何分支,基于一些位旋转,因此不可移植(特别是在32位和64位平台之间)
一旦你得到它,你只需要对结果求逆,然后取整数部分
当然,可能会有更快的技巧,因为这一个有点绕圈子
编辑:我们开始吧
首先是一个小助手:
// benchmark.h
#include <sys/time.h>
template <typename Func>
double benchmark(Func f, size_t iterations)
{
f();
timeval a, b;
gettimeofday(&a, 0);
for (; iterations --> 0;)
{
f();
}
gettimeofday(&b, 0);
return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -
(a.tv_sec * (unsigned int)1e6 + a.tv_usec);
}
其中,正如预期的那样,快速计算的性能要比Int计算好得多
哦,顺便说一下,
sqrt
更快:)如果你需要计算平方根的性能,我想你会计算很多。
那为什么不缓存答案呢?我不知道在你的例子中N的范围,也不知道你是否会多次计算同一个整数的平方根,但是如果是的话,那么你可以在每次调用你的方法时缓存结果(在一个数组中,如果不是太大的话,效率最高)。编辑:这个答案很愚蠢-使用(int)sqrt(I)
使用正确的设置(-march=native-m64-O3
)进行评测后,上述速度要快得多
好吧,有点老问题,但“最快”的答案还没有给出。最快的(我认为)是二进制平方根算法,在中有详细解释 基本上可以归结为:
unsigned short isqrt(unsigned long a) {
unsigned long rem = 0;
int root = 0;
int i;
for (i = 0; i < 16; i++) {
root <<= 1;
rem <<= 2;
rem += a >> 30;
a <<= 2;
if (root < rem) {
root++;
rem -= root;
root++;
}
}
return (unsigned short) (root >> 1);
}
可以在此处下载32位版本:要进行整数sqrt,可以使用牛顿方法的这种特殊化:
Def isqrt(N):
a = 1
b = N
while |a-b| > 1
b = N / a
a = (a + b) / 2
return a
基本上,对于任何x,sqrt都在范围(x…N/x)内,所以我们只需在每个循环中将该间隔平分,就可以得到新的猜测。有点像二进制搜索,但收敛速度必须更快
这在O(loglog(N))中收敛,这是非常快的。它也不使用浮点运算,而且对任意精度的整数也能很好地工作。为什么没有人推荐最快的方法 如果:
int[MAX_X]
填充sqrt(X)
(不需要使用函数sqrt()
)
所有这些条件都非常适合我的计划。
特别是int[10000000]
数组将消耗40MB
您对此有何想法?在许多情况下,甚至不需要精确的整数sqrt值,只要具有良好的近似值就足够了。(例如,在DSP优化中经常会发生这种情况,32位信号应压缩到16位,或16位压缩到8位,而不会在零附近失去太多精度) 我发现了这个有用的等式:
k = ceil(MSB(n)/2); - MSB(n) is the most significant bit of "n"
此方程生成平滑曲线(n,sqrt(n)),其值与实际sqrt(n)相差不大,因此在近似精度足够时非常有用。在我的计算机上,使用gcc,使用-ffast math,将32位整数转换为浮点,使用sqrtf每10^9次运算需要1.2秒(不使用-ffast math,需要3.54秒) 以下算法每10^9使用0.87秒,但会牺牲一些精度:误差可能高达-7或+1,尽管RMS误差仅为0.79:
uint16_t SQRTTAB[65536];
inline uint16_t approxsqrt(uint32_t x) {
const uint32_t m1 = 0xff000000;
const uint32_t m2 = 0x00ff0000;
if (x&m1) {
return SQRTTAB[x>>16];
} else if (x&m2) {
return SQRTTAB[x>>8]>>4;
} else {
return SQRTTAB[x]>>8;
}
}
该表使用以下方法构建:
void maketable() {
for (int x=0; x<65536; x++) {
double v = x/65535.0;
v = sqrt(v);
int y = int(v*65535.0+0.999);
SQRTTAB[x] = y;
}
}
void maketable(){
对于(intx=0;x如果你不介意近似值,我拼凑的这个整数sqrt函数怎么样
int sqrti(int x)
{
union { float f; int x; } v;
// convert to float
v.f = (float)x;
// fast aprox sqrt
// assumes float is in IEEE 754 single precision format
// assumes int is 32 bits
// b = exponent bias
// m = number of mantissa bits
v.x -= 1 << 23; // subtract 2^m
v.x >>= 1; // divide by 2
v.x += 1 << 29; // add ((b + 1) / 2) * 2^m
// convert to int
return (int)v.f;
}
intsqrti(intx)
{
并集{float f;int x;}v;
//转换为浮点数
v、 f=(浮点数)x;
//快速aprox sqrt
//假设浮点为IEEE 754单精度格式
//假定int为32位
//b=指数偏差
//m=尾数位数
v、 x-=1>=1;//除以2
v、 x+=1这是如此之短,以至于它99%内联:
static inline int sqrtn(int num) {
int i = 0;
__asm__ (
"pxor %%xmm0, %%xmm0\n\t" // clean xmm0 for cvtsi2ss
"cvtsi2ss %1, %%xmm0\n\t" // convert num to float, put it to xmm0
"sqrtss %%xmm0, %%xmm0\n\t" // square root xmm0
"cvttss2si %%xmm0, %0" // float to int
:"=r"(i):"r"(num):"%xmm0"); // i: result, num: input, xmm0: scratch register
return i;
}
为什么要清理xmm0
?的文档
目标操作数是XMM寄存器。结果存储在目标操作数的低位双字中,高位三个双字保持不变
GCC内部版本(仅在GCC上运行):
或
#包括
int sqrtn2(int num){
寄存器_v2df xmm0={0,0};
xmm0=uuu内置ia32_cvtsi2sd(xmm0,num);
xmm0=内置ia32 sqrtsd(xmm0);
返回内置ia32 cvttsd2si(xmm0);
}
以下解决方案精确计算整数部分,即地板(sqrt(x))
没有舍入错误
其他方法的问题
- 使用
float
或double
既不便于携带,也不够精确
- @orlp的
isqrt
给出了像isqrt这样疯狂的结果
uint16_t SQRTTAB[65536];
inline uint16_t approxsqrt(uint32_t x) {
const uint32_t m1 = 0xff000000;
const uint32_t m2 = 0x00ff0000;
if (x&m1) {
return SQRTTAB[x>>16];
} else if (x&m2) {
return SQRTTAB[x>>8]>>4;
} else {
return SQRTTAB[x]>>8;
}
}
void maketable() {
for (int x=0; x<65536; x++) {
double v = x/65535.0;
v = sqrt(v);
int y = int(v*65535.0+0.999);
SQRTTAB[x] = y;
}
}
int sqrti(int x)
{
union { float f; int x; } v;
// convert to float
v.f = (float)x;
// fast aprox sqrt
// assumes float is in IEEE 754 single precision format
// assumes int is 32 bits
// b = exponent bias
// m = number of mantissa bits
v.x -= 1 << 23; // subtract 2^m
v.x >>= 1; // divide by 2
v.x += 1 << 29; // add ((b + 1) / 2) * 2^m
// convert to int
return (int)v.f;
}
static inline int sqrtn(int num) {
int i = 0;
__asm__ (
"pxor %%xmm0, %%xmm0\n\t" // clean xmm0 for cvtsi2ss
"cvtsi2ss %1, %%xmm0\n\t" // convert num to float, put it to xmm0
"sqrtss %%xmm0, %%xmm0\n\t" // square root xmm0
"cvttss2si %%xmm0, %0" // float to int
:"=r"(i):"r"(num):"%xmm0"); // i: result, num: input, xmm0: scratch register
return i;
}
#include <xmmintrin.h>
int sqrtn2(int num) {
register __v4sf xmm0 = {0, 0, 0, 0};
xmm0 = __builtin_ia32_cvtsi2ss(xmm0, num);
xmm0 = __builtin_ia32_sqrtss(xmm0);
return __builtin_ia32_cvttss2si(xmm0);
}
#include <xmmintrin.h>
int sqrtn2(int num) {
register __m128 xmm0 = _mm_setzero_ps();
xmm0 = _mm_cvt_si2ss(xmm0, num);
xmm0 = _mm_sqrt_ss(xmm0);
return _mm_cvtt_ss2si(xmm0);
}
static inline int sqrtn(int num) {
int i = 0;
__asm__ (
"pxor %%xmm0, %%xmm0\n\t"
"cvtsi2sd %1, %%xmm0\n\t"
"sqrtsd %%xmm0, %%xmm0\n\t"
"cvttsd2si %%xmm0, %0"
:"=r"(i):"r"(num):"%xmm0");
return i;
}
#include <xmmintrin.h>
int sqrtn2(int num) {
register __v2df xmm0 = {0, 0};
xmm0 = __builtin_ia32_cvtsi2sd(xmm0, num);
xmm0 = __builtin_ia32_sqrtsd(xmm0);
return __builtin_ia32_cvttsd2si(xmm0);
}
uint16_t sqrti(uint32_t num)
{
uint16_t ret = 0;
for(int32_t i = 15; i >= 0; i--)
{
uint16_t temp = ret | (1 << i);
if(temp * temp <= num)
{
ret = temp;
}
}
return ret;
}