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;
      }