web-dev-qa-db-ja.com

C ++で10の整数乗を計算するためにpow()よりも速い方法はありますか?

<<演算子を使用して2の累乗を実装できることを知っています。 10の累乗はどうですか? 10 ^ 5が好きですか? C++でpow(10,5)よりも速い方法はありますか?これは、手作業による非常に単純な計算です。しかし、数値のバイナリ表現のため、コンピューターにとっては簡単ではないようです...私は、整数の累乗、10 ^ n(nは整数)のみに興味があると仮定しましょう。

29
szli

このようなもの:

int quick_pow10(int n)
{
    static int pow10[10] = {
        1, 10, 100, 1000, 10000, 
        100000, 1000000, 10000000, 100000000, 1000000000
    };

    return pow10[n]; 
}

明らかに、long longについても同じことができます。

これは、競合するどの方法よりも数倍速いはずです。ただし、多数のベースがある場合はかなり制限されます(ただし、ベースが大きくなると値の数は大幅に減少します)。したがって、組み合わせの数が多くなければ、それでも実行可能です。

比較として:

#include <iostream>
#include <cstdlib>
#include <cmath>

static int quick_pow10(int n)
{
    static int pow10[10] = {
        1, 10, 100, 1000, 10000, 
        100000, 1000000, 10000000, 100000000, 1000000000
    };

    return pow10[n]; 
}

static int integer_pow(int x, int n)
{
    int r = 1;
    while (n--)
       r *= x;

    return r; 
}

static int opt_int_pow(int n)
{
    int r = 1;
    const int x = 10;
    while (n)
    {
        if (n & 1) 
        {
           r *= x;
           n--;
        }
        else
        {
            r *= x * x;
            n -= 2;
        }
    }

    return r; 
}


int main(int argc, char **argv)
{
    long long sum = 0;
    int n = strtol(argv[1], 0, 0);
    const long outer_loops = 1000000000;

    if (argv[2][0] == 'a')
    {
        for(long i = 0; i < outer_loops / n; i++)
        {
            for(int j = 1; j < n+1; j++)
            {
                sum += quick_pow10(n);
            }
        }
    }
    if (argv[2][0] == 'b')
    {
        for(long i = 0; i < outer_loops / n; i++)
        {
            for(int j = 1; j < n+1; j++)
            {
                sum += integer_pow(10,n);
            }
        }
    }

    if (argv[2][0] == 'c')
    {
        for(long i = 0; i < outer_loops / n; i++)
        {
            for(int j = 1; j < n+1; j++)
            {
                sum += opt_int_pow(n);
            }
        }
    }

    std::cout << "sum=" << sum << std::endl;
    return 0;
}

-Wall -O2 -std=c++0xを使用してg ++ 4.6.3でコンパイルすると、次の結果が得られます。

$ g++ -Wall -O2 -std=c++0x pow.cpp
$ time ./a.out 8 a
sum=100000000000000000

real    0m0.124s
user    0m0.119s
sys 0m0.004s
$ time ./a.out 8 b
sum=100000000000000000

real    0m7.502s
user    0m7.482s
sys 0m0.003s

$ time ./a.out 8 c
sum=100000000000000000

real    0m6.098s
user    0m6.077s
sys 0m0.002s

powを使用するオプションもありましたが、最初に試したときは1分22秒56秒かかったため、ループバリアントを最適化することにしたときに削除しました)

27
Mats Petersson

std::pow()を使用するよりも10のべき乗をより速く計算する方法は確かにあります。最初の認識は、pow(x, n)をO(log n)時間で実装できることです。次の認識は、pow(x, 10)(x << 3) * (x << 1)と同じであるということです。もちろん、コンパイラーは後者を知っています。つまり、整数に整数定数10を掛けると、コンパイラーは10を掛けるのに最も速いものを実行します。これらの2つのルールに基づいて、 xは長整数型です。

このようなゲームに興味がある場合:

  1. Powerの一般的なO(log n)バージョンについては、 プログラミングの要素 で説明しています。
  2. 整数を使用した多くの興味深い「トリック」については、 ハッカーの喜び で説明しています。
12
Dietmar Kühl

テンプレートメタプログラミングを使用したベースのソリューション:

template<int E, int N>
struct pow {
    enum { value = E * pow<E, N - 1>::value };
};

template <int E>
struct pow<E, 0> {
    enum { value = 1 };
};

次に、実行時に使用できる検索テーブルを生成するために使用できます。

template<int E>
long long quick_pow(unsigned int n) {
    static long long lookupTable[] = {
        pow<E, 0>::value, pow<E, 1>::value, pow<E, 2>::value,
        pow<E, 3>::value, pow<E, 4>::value, pow<E, 5>::value,
        pow<E, 6>::value, pow<E, 7>::value, pow<E, 8>::value,
        pow<E, 9>::value
    };

    return lookupTable[n];
}

オーバーフローの可能性を検出するには、これを正しいコンパイラフラグと共に使用する必要があります。

使用例:

for(unsigned int n = 0; n < 10; ++n) {
    std::cout << quick_pow<10>(n) << std::endl;
}
10
Vincent

整数のべき関数(浮動小数点の変換と計算を伴わない)は、pow()よりも非常に高速です。

int integer_pow(int x, int n)
{
    int r = 1;
    while (n--)
        r *= x;

    return r; 
}

編集:ベンチマーク-単純な整数累乗法は、浮動小数点よりも約2倍優れているようです。

h2co3-macbook:~ h2co3$ cat quirk.c
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <math.h>

int integer_pow(int x, int n)
{
    int r = 1;
    while (n--)
    r *= x;

    return r; 
}

int main(int argc, char *argv[])
{
    int x = 0;

    for (int i = 0; i < 100000000; i++) {
        x += powerfunc(i, 5);
    }

    printf("x = %d\n", x);

    return 0;
}
h2co3-macbook:~ h2co3$ clang -Wall -o quirk quirk.c -Dpowerfunc=integer_pow
h2co3-macbook:~ h2co3$ time ./quirk
x = -1945812992

real    0m1.169s
user    0m1.164s
sys 0m0.003s
h2co3-macbook:~ h2co3$ clang -Wall -o quirk quirk.c -Dpowerfunc=pow
h2co3-macbook:~ h2co3$ time ./quirk
x = -2147483648

real    0m2.898s
user    0m2.891s
sys 0m0.004s
h2co3-macbook:~ h2co3$ 
4
user529758

ルックアップテーブルを使用できます。

this :-の使用も検討できます

template <typename T>
T expt(T p, unsigned q)
{
    T r(1);

    while (q != 0) {
        if (q % 2 == 1) {    // q is odd
            r *= p;
            q--;
        }
        p *= p;
        q /= 2;
    }

    return r;
}
1
Rahul Tripathi

この関数は、powよりもはるかに高速にx ^ yを計算します。整数値の場合。

int pot(int x, int y){
int solution = 1;
while(y){
    if(y&1)
        solution*= x;
    x *= x;
    y >>= 1;
}
return solution;

}

1

乗算なし、テーブルバージョンなし:

//Nx10^n
int Npow10(int N, int n){
  N <<= n;
  while(n--) N += N << 2;
  return N;
}
1
acegs

Mats Petersson アプローチに基づきますが、キャッシュのコンパイル時間生成。

#include <iostream>
#include <limits>
#include <array>

// digits

template <typename T>
constexpr T digits(T number) {    
  return number == 0 ? 0 
                     : 1 + digits<T>(number / 10);
}

// pow

// https://stackoverflow.com/questions/24656212/why-does-gcc-complain-error-type-intt-of-template-argument-0-depends-on-a
// unfortunatly we can't write `template <typename T, T N>` because of partial specialization `PowerOfTen<T, 1>`

template <typename T, uintmax_t N>
struct PowerOfTen {
  enum { value = 10 * PowerOfTen<T, N - 1>::value };
};

template <typename T>
struct PowerOfTen<T, 1> {
  enum { value = 1 };
};

// sequence

template<typename T, T...>
struct pow10_sequence { };

template<typename T, T From, T N, T... Is>
struct make_pow10_sequence_from 
: make_pow10_sequence_from<T, From, N - 1, N - 1, Is...> { 
  //  
};

template<typename T, T From, T... Is>
struct make_pow10_sequence_from<T, From, From, Is...> 
: pow10_sequence<T, Is...> { 
  //
};

// base10list

template <typename T, T N, T... Is>
constexpr std::array<T, N> base10list(pow10_sequence<T, Is...>) {
  return {{ PowerOfTen<T, Is>::value... }};
}

template <typename T, T N>
constexpr std::array<T, N> base10list() {    
  return base10list<T, N>(make_pow10_sequence_from<T, 1, N+1>());
}

template <typename T>
constexpr std::array<T, digits(std::numeric_limits<T>::max())> base10list() {    
  return base10list<T, digits(std::numeric_limits<T>::max())>();    
};

// main pow function

template <typename T>
static T template_quick_pow10(T n) {

  static auto values = base10list<T>();
  return values[n]; 
}

// client code

int main(int argc, char **argv) {

  long long sum = 0;
  int n = strtol(argv[1], 0, 0);
  const long outer_loops = 1000000000;

  if (argv[2][0] == 't') {

    for(long i = 0; i < outer_loops / n; i++) {

      for(int j = 1; j < n+1; j++) {

        sum += template_quick_pow10(n);
      }
    }
  }

  std::cout << "sum=" << sum << std::endl;
  return 0;
}

コードには、読みやすくするためにquick_pow10、integer_pow、opt_int_powが含まれていませんが、コード内でそれらを使用してテストを行います。

-Wall -O2 -std = c ++ 0xを使用してgccバージョン4.6.3(Ubuntu/Linaro 4.6.3-1ubuntu5)でコンパイルすると、次の結果が得られます。

$ g++ -Wall -O2 -std=c++0x main.cpp

$ time ./a.out  8 a
sum=100000000000000000

real  0m0.438s
user  0m0.432s
sys 0m0.008s

$ time ./a.out  8 b
sum=100000000000000000

real  0m8.783s
user  0m8.777s
sys 0m0.004s

$ time ./a.out  8 c
sum=100000000000000000

real  0m6.708s
user  0m6.700s
sys 0m0.004s

$ time ./a.out  8 t
sum=100000000000000000

real  0m0.439s
user  0m0.436s
sys 0m0.000s
0
Nine

constexprを使用すると、次のようにできます。

constexpr int pow10(int n) {
    int result = 1;
    for (int i = 1; i<=n; ++i)
        result *= 10;
    return result;
}

int main () {
    int i = pow10(5);
}

iはコンパイル時に計算されます。 x86-64 gcc 9.2用に生成されたASM:

main:
        Push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 100000
        mov     eax, 0
        pop     rbp
        ret
0
João Paulo