如何轻松计算C中无符号长long的平方根?

时间:2013-08-28 22:44:17

标签: c unsigned-long-long-int long-double

我正在查看另一个问题(here),其中有人正在寻找一种方法来获取x86汇编中64位整数的平方根。

事实证明这很简单。解决方案是转换为浮点数,计算sqrt然后转换回来。

我需要在C中做一些非常相似的事情但是当我看到等价物时,我会有点卡住。我只能找到一个接收双打的sqrt函数。双精度不具有存储大64位整数的精度,而不会引入明显的舍入误差。

我是否可以使用具有long double sqrt函数的公共数学库?

6 个答案:

答案 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)

在这里,我们收集了几个观察结果,以便找到解决方案:

  1. 在标准C&gt; = 1999中,保证非虚拟整数的位数表示为任何基数为2的数字。
    ----&GT;因此,我们可以信任这种数字的位操作。
  2. 如果x是无符号整数类型,则为x >> 1 == x / 2x << 1 == x * 2
    (!)但是:比特操作很可能比它们的算术对象更快。
  3. sqrt(x)在数学上等同于exp(log(x)/2.0)
  4. 如果我们考虑截断的对数和整数的基数2指数,我们可以得到一个公平的估计:IntExp2( IntLog2(x) / 2) "==" IntSqrtDn(x),其中"="是非正式符号,意思是几乎等于(在一个很好的近似意义上)。
  5. 如果我们写IntExp2( IntLog2(x) / 2 + 1) "==" IntSqrtUp(x),我们得到整数平方根的“上面”近似值。
  6. (4)和(5)中得到的近似值有点粗略(它们包含两个连续2次幂之间的sqrt(x)的真值),但它们可能是任何一个非常好的起点。搜索x的平方值的算法。
  7. 如果我们对真实解决方案有一个很好的初步近似,那么平方根的牛顿算法可以很好地用于整数。
  8. 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 机器码指令进行优化。