我正在查看另一个问题(here),其中有人正在寻找一种方法来获取x86汇编中64位整数的平方根。
事实证明这很简单。解决方案是转换为浮点数,计算sqrt然后转换回来。
我需要在C中做一些非常相似的事情但是当我看到等价物时,我会有点卡住。我只能找到一个接收双打的sqrt函数。双精度不具有存储大64位整数的精度,而不会引入明显的舍入误差。
我是否可以使用具有long double
sqrt函数的公共数学库?
答案 0 :(得分:11)
不需要long double
;平方根可以用double
计算(如果它是IEEE-754 64位二进制)。将64位整数转换为double
时的舍入误差几乎与此问题无关。
舍入误差最多为2 53 中的一部分。这会导致2 54 中最多一个部分的平方根出错。由于将数学结果四舍五入为sqrt
格式,double
本身在2 53 中的舍入误差小于一部分。这些错误的总和很小; 64位整数的最大可能平方根(舍入为53位)为2 32 ,因此2 54 中的三个部分的误差小于.00000072。
对于uint64_t x
,请考虑sqrt(x)
。我们知道这个值在x
的确切平方根的.00000072范围内,但我们不知道它的方向。如果我们将其调整为sqrt(x) - 0x1p-20
,那么我们知道我们的值小于x
的平方根,但非常接近。
然后此代码计算x
的平方根,截断为整数,前提是操作符合IEEE 754:
uint64_t y = sqrt(x) - 0x1p-20;
if (2*y < x - y*y)
++y;
(2*y < x - y*y
等同于(y+1)*(y+1) <= x
,但如果y+1
为2 32 ,它会避免包装64位整数。)
答案 1 :(得分:4)
功能sqrtl()
,取long double
,是C99的一部分。
请注意,您的编译平台不必将long double
实现为80位扩展精度。它只需要与double
一样宽,Visual Studio实现就像普通double
一样。 GCC和Clang在英特尔处理器上将long double
编译为80位扩展精度。
答案 2 :(得分:2)
是的,标准库有sqrtl()
(自C99起)。
答案 3 :(得分:1)
如果你只想计算整数的sqrt,使用除法和征服应该在最多32次迭代中找到结果:
uint64_t mysqrt (uint64_t a)
{
uint64_t min=0;
//uint64_t max=1<<32;
uint64_t max=((uint64_t) 1) << 32; //chux' bugfix
while(1)
{
if (max <= 1 + min)
return min;
uint64_t sqt = min + (max - min)/2;
uint64_t sq = sqt*sqt;
if (sq == a)
return sqt;
if (sq > a)
max = sqt;
else
min = sqt;
}
调试留给读者练习。
答案 4 :(得分:1)
在这里,我们收集了几个观察结果,以便找到解决方案:
x
是无符号整数类型,则为x >> 1 == x / 2
和x << 1 == x * 2
。sqrt(x)
在数学上等同于exp(log(x)/2.0)
。 IntExp2( IntLog2(x) / 2) "==" IntSqrtDn(x)
,其中"="
是非正式符号,意思是几乎等于(在一个很好的近似意义上)。 IntExp2( IntLog2(x) / 2 + 1) "==" IntSqrtUp(x)
,我们得到整数平方根的“上面”近似值。 x
的平方值的算法。 http://en.wikipedia.org/wiki/Integer_square_root
最终的算法需要一些数学合成才能确保始终正常工作,但我现在不会这样做......我会告诉你最终的程序,而不是:
#include <stdio.h> /* For printf()... */
#include <stdint.h> /* For uintmax_t... */
#include <math.h> /* For sqrt() .... */
int IntLog2(uintmax_t n) {
if (n == 0) return -1; /* Error */
int L;
for (L = 0; n >>= 1; L++)
;
return L; /* It takes < 64 steps for long long */
}
uintmax_t IntExp2(int n) {
if (n < 0)
return 0; /* Error */
uintmax_t E;
for (E = 1; n-- > 0; E <<= 1)
;
return E; /* It takes < 64 steps for long long */
}
uintmax_t IntSqrtDn(uintmax_t n) { return IntExp2(IntLog2(n) / 2); }
uintmax_t IntSqrtUp(uintmax_t n) { return IntExp2(IntLog2(n) / 2 + 1); }
int main(void) {
uintmax_t N = 947612934; /* Try here your number! */
uintmax_t sqrtn = IntSqrtDn(N), /* 1st approx. to sqrt(N) by below */
sqrtn0 = IntSqrtUp(N); /* 1st approx. to sqrt(N) by above */
/* The following means while( abs(sqrt-sqrt0) > 1) { stuff... } */
/* However, we take care of subtractions on unsigned arithmetic, just in case... */
while ( (sqrtn > sqrtn0 + 1) || (sqrtn0 > sqrtn+1) )
sqrtn0 = sqrtn, sqrtn = (sqrtn0 + N/sqrtn0) / 2; /* Newton iteration */
printf("N==%llu, sqrt(N)==%g, IntSqrtDn(N)==%llu, IntSqrtUp(N)==%llu, sqrtn==%llu, sqrtn*sqrtn==%llu\n\n",
N, sqrt(N), IntSqrtDn(N), IntSqrtUp(N), sqrtn, sqrtn*sqrtn);
return 0;
}
sqrtn
中存储的最后一个值是N
的整数平方根
程序的最后一行只是显示所有值,具有复杂的目的
因此,您可以尝试N
的不同值,看看会发生什么。
如果我们在while循环中添加一个计数器,我们将看到不会发生几次迭代。
备注:在整数设置中工作时,必须验证是否始终达到条件abs(sqrtn-sqrtn0)<=1
。如果没有,我们将不得不修复算法。
备注2:在初始化句子中,请注意sqrtn0 == sqrtn * 2 == sqrtn << 1
。这避免了我们的一些计算。
答案 5 :(得分:0)
// sqrt_i64 returns the integer square root of v.
int64_t sqrt_i64(int64_t v) {
uint64_t q = 0, b = 1, r = v;
for( b <<= 62; b > 0 && b > r; b >>= 2);
while( b > 0 ) {
uint64_t t = q + b;
q >>= 1;
if( r >= t ) {
r -= t;
q += b;
}
b >>= 2;
}
return q;
}
for 循环可以使用 clz
机器码指令进行优化。