web-dev-qa-db-ja.com

sqrt(n)の整数部分を取得する最も速い方法は?

nが完全な正方形でない場合はわかっているので、sqrt(n)は整数ではありません。整数部分だけが必要なので、sqrt(n)の呼び出しは、小数部分の計算にも時間がかかるため、それほど高速ではないと感じます。

私の質問は、

sqrt(n)の実際の値を計算せずに、sqrt(n)の整数部分のみを取得できますか?アルゴリズムは、sqrt(n)<math.h>または<cmath>)?

可能であれば、asmブロックにもコードを記述できます。

63
Nawaz

Fast Inverse Square Root トリックを試してみます。

いくつかのビットトゥイドリングに基づいて(特に32ビットプラットフォームと64ビットプラットフォーム間で)ブランチを使用せずに、1/sqrt(n)の非常に優れた近似値を取得する方法です。

取得したら、結果を逆にするだけで、整数部分を取得できます。

もちろん、これはちょっとしたラウンドなので、より速いトリックがあるかもしれません。

[〜#〜] edit [〜#〜]:やりましょう!

最初の小さなヘルパー:

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

次に、本体:

#include <iostream>

#include <cmath>

#include "benchmark.h"

class Sqrt
{
public:
  Sqrt(int n): _number(n) {}

  int operator()() const
  {
    double d = _number;
    return static_cast<int>(std::sqrt(d) + 0.5);
  }

private:
  int _number;
};

// http://www.codecodex.com/wiki/Calculate_an_integer_square_root
class IntSqrt
{
public:
  IntSqrt(int n): _number(n) {}

  int operator()() const 
  {
    int remainder = _number;
    if (remainder < 0) { return 0; }

    int place = 1 <<(sizeof(int)*8 -2);

    while (place > remainder) { place /= 4; }

    int root = 0;
    while (place)
    {
      if (remainder >= root + place)
      {
        remainder -= root + place;
        root += place*2;
      }
      root /= 2;
      place /= 4;
    }
    return root;
  }

private:
  int _number;
};

// http://en.wikipedia.org/wiki/Fast_inverse_square_root
class FastSqrt
{
public:
  FastSqrt(int n): _number(n) {}

  int operator()() const
  {
    float number = _number;

    float x2 = number * 0.5F;
    float y = number;
    long i = *(long*)&y;
    //i = (long)0x5fe6ec85e7de30da - (i >> 1);
    i = 0x5f3759df - (i >> 1);
    y = *(float*)&i;

    y = y * (1.5F - (x2*y*y));
    y = y * (1.5F - (x2*y*y)); // let's be precise

    return static_cast<int>(1/y + 0.5f);
  }

private:
  int _number;
};


int main(int argc, char* argv[])
{
  if (argc != 3) {
    std::cerr << "Usage: %prog integer iterations\n";
    return 1;
  }

  int n = atoi(argv[1]);
  int it = atoi(argv[2]);

  assert(Sqrt(n)() == IntSqrt(n)() &&
          Sqrt(n)() == FastSqrt(n)() && "Different Roots!");
  std::cout << "sqrt(" << n << ") = " << Sqrt(n)() << "\n";

  double time = benchmark(Sqrt(n), it);
  double intTime = benchmark(IntSqrt(n), it);
  double fastTime = benchmark(FastSqrt(n), it);

  std::cout << "Number iterations: " << it << "\n"
               "Sqrt computation : " << time << "\n"
               "Int computation  : " << intTime << "\n"
               "Fast computation : " << fastTime << "\n";

  return 0;
}

そして結果:

sqrt(82) = 9
Number iterations: 4096
Sqrt computation : 56
Int computation  : 217
Fast computation : 119

// Note had to Tweak the program here as Int here returns -1 :/
sqrt(2147483647) = 46341 // real answer sqrt(2 147 483 647) = 46 340.95
Number iterations: 4096
Sqrt computation : 57
Int computation  : 313
Fast computation : 119

予想どおり、Fast計算はInt計算よりもはるかに優れたパフォーマンスを発揮します。

ああ、ところで、sqrtは高速です:)

21
Matthieu M.

編集:この答えは愚かです-_(int) sqrt(i)_を使用してください

proper設定(_-march=native -m64 -O3_)でプロファイリングした後、上記はlot もっと早く。


少し古い質問ですが、「最速」の答えはまだ出ていません。最速(と思う)は このEmbedded.comの記事 で完全に説明されているBinary Square Rootアルゴリズムです。

基本的には次のようになります。

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

私のマシン(Q6600、Ubuntu 10.10)では、1から1000000の数字の平方根を取ることでプロファイルを作成しました。 iqsrt(i)の使用には2750ミリ秒かかりました。 _(unsigned short) sqrt((float) i)_の使用には3600msかかりました。これは_g++ -O3_を使用して行われました。 _-ffast-math_コンパイルオプションを使用すると、時間はそれぞれ2100ミリ秒と3100ミリ秒でした。これは、1行のアセンブラーさえ使用しないため、おそらくはるかに高速であることに注意してください。

上記のコードはCとC++の両方で機能し、Javaでもわずかな構文変更があります。

限られた範囲でさらにうまく機能するのは、バイナリ検索です。私のマシンでは、これにより、上のバージョンが4倍になります。悲しいことに、範囲が非常に制限されています。

_#include <stdint.h>

const uint16_t squares[] = {
    0, 1, 4, 9,
    16, 25, 36, 49,
    64, 81, 100, 121,
    144, 169, 196, 225,
    256, 289, 324, 361,
    400, 441, 484, 529,
    576, 625, 676, 729,
    784, 841, 900, 961,
    1024, 1089, 1156, 1225,
    1296, 1369, 1444, 1521,
    1600, 1681, 1764, 1849,
    1936, 2025, 2116, 2209,
    2304, 2401, 2500, 2601,
    2704, 2809, 2916, 3025,
    3136, 3249, 3364, 3481,
    3600, 3721, 3844, 3969,
    4096, 4225, 4356, 4489,
    4624, 4761, 4900, 5041,
    5184, 5329, 5476, 5625,
    5776, 5929, 6084, 6241,
    6400, 6561, 6724, 6889,
    7056, 7225, 7396, 7569,
    7744, 7921, 8100, 8281,
    8464, 8649, 8836, 9025,
    9216, 9409, 9604, 9801,
    10000, 10201, 10404, 10609,
    10816, 11025, 11236, 11449,
    11664, 11881, 12100, 12321,
    12544, 12769, 12996, 13225,
    13456, 13689, 13924, 14161,
    14400, 14641, 14884, 15129,
    15376, 15625, 15876, 16129,
    16384, 16641, 16900, 17161,
    17424, 17689, 17956, 18225,
    18496, 18769, 19044, 19321,
    19600, 19881, 20164, 20449,
    20736, 21025, 21316, 21609,
    21904, 22201, 22500, 22801,
    23104, 23409, 23716, 24025,
    24336, 24649, 24964, 25281,
    25600, 25921, 26244, 26569,
    26896, 27225, 27556, 27889,
    28224, 28561, 28900, 29241,
    29584, 29929, 30276, 30625,
    30976, 31329, 31684, 32041,
    32400, 32761, 33124, 33489,
    33856, 34225, 34596, 34969,
    35344, 35721, 36100, 36481,
    36864, 37249, 37636, 38025,
    38416, 38809, 39204, 39601,
    40000, 40401, 40804, 41209,
    41616, 42025, 42436, 42849,
    43264, 43681, 44100, 44521,
    44944, 45369, 45796, 46225,
    46656, 47089, 47524, 47961,
    48400, 48841, 49284, 49729,
    50176, 50625, 51076, 51529,
    51984, 52441, 52900, 53361,
    53824, 54289, 54756, 55225,
    55696, 56169, 56644, 57121,
    57600, 58081, 58564, 59049,
    59536, 60025, 60516, 61009,
    61504, 62001, 62500, 63001,
    63504, 64009, 64516, 65025
};

inline int isqrt(uint16_t x) {
    const uint16_t *p = squares;

    if (p[128] <= x) p += 128;
    if (p[ 64] <= x) p +=  64;
    if (p[ 32] <= x) p +=  32;
    if (p[ 16] <= x) p +=  16;
    if (p[  8] <= x) p +=   8;
    if (p[  4] <= x) p +=   4;
    if (p[  2] <= x) p +=   2;
    if (p[  1] <= x) p +=   1;

    return p - squares;
}
_

32ビットバージョンは、ここからダウンロードできます。 https://Gist.github.com/348177

16
orlp

近似を気にしないのであれば、この整数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;
}

この Wikipedia の記事で説明されているアルゴリズムを使用します。私のマシンでは、sqrtのほぼ2倍の速度です。

6
Shmo

「高速整数平方根」を検索することで多くのオプションを見つけることができると思われますが、ここではうまく機能する可能性のあるいくつかの新しい可能性のあるアイデアを示します(それぞれ独立しているか、またはそれらを組み合わせることができます):

  1. 作る static constサポートするドメイン内のすべての完全な正方形の配列で、高速なブランチレスバイナリ検索を実行します。配列の結果のインデックスは平方根です。
  2. 数値を浮動小数点に変換し、仮数と指数に分割します。指数を半分にし、仮数に何らかの魔法の係数を掛けます(それを見つけるのはあなたの仕事です)。これにより、非常に近い近似が得られるはずです。正確でない場合は調整するための最終ステップを含めます(または、上記のバイナリ検索の開始点として使用します)。
6
R..

おもう - Google searchCalculate an integer square root 高速計算の可能性のある方法について説明しすぎて、良い参考記事がありますが、ここで誰も彼らよりも優れたものを提供できないと思いますそしてそれらにあいまいさがあります、それから私達はあなたをよく助けることができるかもしれません。

6
Saeed Amiri

整数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))に収束します。これは非常に高速です。また、浮動小数点をまったく使用せず、任意の精度の整数に対しても機能します。

4
Andrew Tomazos

これは非常に短いため、99%インラインになります。

static inline int sqrtn(int num) {
    int i;
    __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;
}

きれいにする理由xmm0cvtsi2ss

デスティネーションオペランドはXMMレジスタです。結果は宛先オペランドの下位ダブルワードに格納され、上位3つのダブルワードは変更されません。

GCC組み込みバージョン(GCCでのみ実行):

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

Intel Intrinsicバージョン(GCC、Clang、ICCでテスト済み):

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

^^^^にはすべてSSE 1(SSE 2ではなく)が必要です。

3
MCCCS

誰も最速の方法を提案しないのはなぜですか?

次の場合:

  1. 数値の範囲は限られています
  2. メモリ消費は重要ではありません
  3. アプリケーションの起動時間は重要ではありません

次に、(起動時に)sqrt(x)で満たされた_int[MAX_X]_を作成します(関数sqrt()を使用する必要はありません)。

これらの条件はすべて、私のプログラムに非常によく適合しています。特に、_int[10000000]_配列は_40MB_を消費します。

これについてどう思いますか?

3
flybot1

多くの場合、正確な整数のsqrt値でさえ必要ではなく、十分に近似できます。 (たとえば、DSP最適化でよく発生します。32ビット信号を16ビットまたは8ビットに圧縮する必要がある場合、ゼロ付近の精度をあまり失うことはありません)。

私はこの便利な方程式を見つけました:

k = ceil(MSB(n)/2); - MSB(n) is the most significant bit of "n"


sqrt(n) ~= 2^(k-2)+(2^(k-1))*n/(2^(2*k))); - all multiplications and divisions here are very DSP-friendly, as they are only 2^k.

この方程式は滑らかな曲線(n、sqrt(n))を生成し、その値は実際のsqrt(n)とあまり変わらないため、おおよその精度で十分な場合に役立ちます。

2
Andrey

平方根の計算でパフォーマンスが必要な場合、それらの多くを計算すると思います。次に、答えをキャッシュしないのはなぜですか?あなたの場合のNの範囲はわかりませんが、同じ整数の平方根を何度も計算する場合もありますが、はいの場合、メソッドが呼び出されるたびに結果をキャッシュできます(配列では大きすぎない場合は最も効率的です)。

1
Benoit Thiery

Gccを使用し、-ffast-mathを使用するコンピューターでは、32ビット整数を浮動小数点数に変換してsqrtfを使用すると、10 ^ 9 opsあたり1.2秒かかります(-ffast-mathを使用しない場合は3.54秒かかります)。

次のアルゴリズムは、精度を犠牲にして10 ^ 9あたり0.87秒を使用します。RMSエラーは0.79だけですが、エラーは-7または+1になります。

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

さらにifステートメントを使用して二分法を改良すると精度が向上しますが、少なくとも-ffast-mathを使用すると、sqrtfが高速になるまで速度が低下します。

1
DanielW