web-dev-qa-db-fra.com

De toute façon plus rapide que pow () pour calculer une puissance entière de 10 en C ++?

Je sais que la puissance de 2 peut être implémentée en utilisant l'opérateur <<. Et la puissance de 10? Comme 10 ^ 5? Existe-t-il un moyen plus rapide que pow (10,5) en C++? C'est un calcul assez simple à la main. Mais cela ne semble pas facile pour les ordinateurs en raison de la représentation binaire des nombres ... Supposons que je ne m'intéresse qu'aux puissances entières, 10 ^ n, où n est un entier.

29
szli

Quelque chose comme ça:

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

    return pow10[n]; 
}

Évidemment, peut faire la même chose pour long long.

Cela devrait être plusieurs fois plus rapide que toute méthode concurrente. Cependant, il est assez limité si vous avez beaucoup de bases (bien que le nombre de valeurs diminue considérablement avec des bases plus grandes), donc s'il n'y a pas un grand nombre de combinaisons, c'est toujours faisable.

A titre de comparaison:

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

Compilé avec g ++ 4.6.3, en utilisant -Wall -O2 -std=c++0x, donne les résultats suivants:

$ 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

(J'avais également une option pour utiliser pow, mais cela m'a pris 1m22.56s quand je l'ai essayé pour la première fois, donc je l'ai retiré quand j'ai décidé d'avoir une variante de boucle optimisée)

27
Mats Petersson

Il existe certainement des moyens de calculer des puissances intégrales de 10 plus rapidement qu'en utilisant std::pow()! La première réalisation est que pow(x, n) peut être implémentée en temps O (log n). La prochaine réalisation est que pow(x, 10) est identique à (x << 3) * (x << 1). Bien sûr, le compilateur connaît ce dernier, c'est-à-dire que lorsque vous multipliez un entier par la constante entière 10, le compilateur fera ce qui est le plus rapide pour se multiplier par 10. Sur la base de ces deux règles, il est facile de créer des calculs rapides, même si x est un grand type entier.

Si vous êtes intéressé par des jeux comme celui-ci:

  1. Une version générique O (log n) de la puissance est discutée dans Éléments de programmation .
  2. Beaucoup de "trucs" intéressants avec des nombres entiers sont discutés dans Hacker's Delight .
12
Dietmar Kühl

Une solution pour toute base utilisant la méta-programmation de modèles:

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

Ensuite, il peut être utilisé pour générer une table de recherche qui peut être utilisée au moment de l'exécution:

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

Ceci doit être utilisé avec les bons drapeaux du compilateur afin de détecter les débordements possibles.

Exemple d'utilisation:

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

Une fonction de puissance entière (qui n'implique pas de conversions et de calculs en virgule flottante) peut très bien être plus rapide que pow():

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

    return r; 
}

Edit: benchmarked - la méthode d'exponentiation naïve en nombre entier semble surpasser celle à virgule flottante d'environ un facteur deux:

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

Vous pouvez utiliser la table de recherche qui sera de loin la plus rapide

Vous pouvez également envisager d'utiliser 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

Cette fonction calculera x ^ y beaucoup plus rapidement que pow. En cas de valeurs entières.

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

}

1
Marko Gregurovic

Pas de multiplication et pas de version de table:

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

Basé sur l'approche Mats Petersson , mais compile la génération de temps du cache.

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

Le code ne contient pas quick_pow10, integer_pow, opt_int_pow pour une meilleure lisibilité, mais des tests effectués avec eux dans le code.

Compilé avec gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5), en utilisant -Wall -O2 -std = c ++ 0x, donne les résultats suivants:

$ 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

Maintenant, avec constexpr, vous pouvez faire comme ça:

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 sera calculé au moment de la compilation. ASM généré pour x86-64 gcc 9.2:

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