A maneira mais eficiente de implementar uma função de potência baseada em número inteiro pow (int, int)


249

Qual é a maneira mais eficiente dada para elevar um número inteiro à potência de outro número inteiro em C?

// 2^3
pow(2,3) == 8

// 5^5
pow(5,5) == 3125

3
Quando você diz "eficiência", precisa especificar eficiente em relação a quê. Rapidez? Uso de memória? Tamanho do código? Manutenção?
Andy Lester

C não possui uma função pow ()?
jalf

16
sim, mas que funciona em carros alegóricos ou duplos, e não em ints
Nathan Fellman

1
Se você está aderindo ao ints real (e não a uma classe int enorme), muitas chamadas para o ipow transbordam. Isso me faz pensar se há uma maneira inteligente de pré-calcular uma tabela e reduzir todas as combinações que não transbordam para uma simples pesquisa de tabela. Isso exigiria mais memória do que a maioria das respostas gerais, mas talvez seja mais eficiente em termos de velocidade.
Adrian McCarthy

pow()não é uma função segura
EsmaeelE

Respostas:


391

Exponenciação ao quadrado.

int ipow(int base, int exp)
{
    int result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

Este é o método padrão para realizar exponenciação modular para grandes números em criptografia assimétrica.


38
Você provavelmente deve adicionar uma verificação de que "exp" não é negativo. Atualmente, essa função fornece uma resposta errada ou faz um loop para sempre. (Dependendo se >> = em um int assinado, o preenchimento de zero ou a extensão de sinal - os compiladores C podem escolher um dos comportamentos).
user9876

23
Eu escrevi uma versão mais otimizada disso, disponível gratuitamente para download aqui: gist.github.com/3551590 Na minha máquina, era cerca de 2,5x mais rápido.
orlp 31/08/2012

10
@AkhilJain: É perfeitamente bom C; para torná-lo válido também em Java, substitua while (exp)e if (exp & 1)por while (exp != 0)e if ((exp & 1) != 0)respectivamente.
Ilmari Karonen

3
Sua função provavelmente deve ter unsigned exp, ou então lidar com o negativo expcorretamente.
Craig McQueen

5
@ZinanXing Multiplicar n vezes resulta em mais multiplicações e é mais lento. Esse método salva multiplicações reutilizando-as efetivamente. Por exemplo, para calcular n ^ 8, o método ingênuo n*n*n*n*n*n*n*nusa 7 multiplicações. Em vez disso m=n*n, esse algoritmo calcula , então o=m*m, p=o*oonde p= n ^ 8, com apenas três multiplicações. Com grandes expoentes, a diferença no desempenho é significativa.
bames53

69

Observe que a exponenciação ao quadrado não é o método mais ideal. Provavelmente é o melhor que você pode fazer como método geral que funciona para todos os valores de expoente, mas para um valor específico de expoente pode haver uma sequência melhor que precise de menos multiplicações.

Por exemplo, se você quiser calcular x ^ 15, o método de exponenciação ao quadrado fornecerá:

x^15 = (x^7)*(x^7)*x 
x^7 = (x^3)*(x^3)*x 
x^3 = x*x*x

Este é um total de 6 multiplicações.

Acontece que isso pode ser feito usando "apenas" 5 multiplicações via exponenciação da cadeia de adição .

n*n = n^2
n^2*n = n^3
n^3*n^3 = n^6
n^6*n^6 = n^12
n^12*n^3 = n^15

Não há algoritmos eficientes para encontrar essa sequência ótima de multiplicações. Da Wikipedia :

O problema de encontrar a cadeia de adição mais curta não pode ser resolvido pela programação dinâmica, porque não satisfaz a suposição de subestrutura ideal. Ou seja, não é suficiente decompor a energia em potências menores, cada uma das quais é calculada minimamente, pois as cadeias de adição das potências menores podem estar relacionadas (para compartilhar cálculos). Por exemplo, na cadeia de adição mais curta para a¹⁵ acima, o subproblema para a⁶ deve ser calculado como (a³) ² uma vez que a³ é reutilizado (em oposição a, digamos, a⁶ = a² (a²) ², que também requer três multiplicações )


4
@ JeremySalwen: Como esta resposta indica, a exponenciação binária não é, em geral, o método mais ideal. Atualmente, não existem algoritmos eficientes para encontrar a sequência mínima de multiplicações.
Eric Postpischil

2
@ EricPostpischil, isso depende da sua aplicação. Normalmente, não precisamos de um algoritmo geral para trabalhar com todos os números. Veja The Art of Computer Programming, vol. 2: Algoritmos
seminuméricos

3
Há uma boa exposição desse problema exato em De matemática para programação genérica, de Alexander Stepanov e Daniel Rose. Este livro deve estar na prateleira de todo profissional de software, IMHO.
perfil completo de Toby Speight


Isso pode ser otimizado para números inteiros, porque existem bem abaixo de 255 potências inteiras que não causarão excesso para números inteiros de 32 bits. Você pode armazenar em cache a estrutura de multiplicação ideal para cada int. Imagino o código + dados ainda seria menor do que simplesmente cache todos os poderes ...
Josias Yoder

22

Se você precisar aumentar 2 para uma potência. A maneira mais rápida de fazer isso é mudar a potência.

2 ** 3 == 1 << 3 == 8
2 ** 30 == 1 << 30 == 1073741824 (A Gigabyte)

Existe uma maneira elegante de fazer isso para que 2 ** 0 == 1?
Rob Smallshire

16
2 ** 0 == 1 << 0 == 1
Jake #

14

Aqui está o método em Java

private int ipow(int base, int exp)
{
    int result = 1;
    while (exp != 0)
    {
        if ((exp & 1) == 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}

não funciona para números grandes, por exemplo, pow (71045970,41535484)
Anushree Acharjee

16
@AnushreeAcharjee, claro que não. Computar esse número exigiria aritmética de precisão arbitrária.
David Etler 4/15

Use BigInteger # modPow ou BigInteger # pow para números grandes, algoritmos apropriados com base no tamanho de argumentos já são implementadas
Raman Yelianevich

Esta não é uma pergunta sobre Java!
Cacahuete Frito

7
int pow( int base, int exponent)

{   // Does not work for negative exponents. (But that would be leaving the range of int) 
    if (exponent == 0) return 1;  // base case;
    int temp = pow(base, exponent/2);
    if (exponent % 2 == 0)
        return temp * temp; 
    else
        return (base * temp * temp);
}

Não é o meu voto, mas pow(1, -1)não sai do intervalo de int, apesar de um expoente negativo. Agora que alguém trabalha por acidente, como faz pow(-1, -1).
MSalters 13/08/2015

O único expoente negativo que pode não fazer você sair do intervalo de int é -1. E só funciona se a base for 1 ou -1. Portanto, existem apenas dois pares (base, exp) com exp <0 que não levariam a potências não inteiras. Embora eu sou um matematician e eu como os quantificadores, eu acho que neste caso, na prática, é ok para dizer que um expoente negativo faz você deixar o reino inteiro ...
bartgol

6

Se você deseja obter o valor de um número inteiro para 2 elevado ao poder de algo, é sempre melhor usar a opção shift:

pow(2,5) pode ser substituído por 1<<5

Isso é muito mais eficiente.


6

power()função para trabalhar somente com números inteiros

int power(int base, unsigned int exp){

    if (exp == 0)
        return 1;
    int temp = power(base, exp/2);
    if (exp%2 == 0)
        return temp*temp;
    else
        return base*temp*temp;

}

Complexidade = O (log (exp))

power()função para trabalhar para exp negativa e base flutuante .

float power(float base, int exp) {

    if( exp == 0)
       return 1;
    float temp = power(base, exp/2);       
    if (exp%2 == 0)
        return temp*temp;
    else {
        if(exp > 0)
            return base*temp*temp;
        else
            return (temp*temp)/base; //negative exponent computation 
    }

} 

Complexidade = O (log (exp))


Como isso é diferente das respostas de Abhijit Gaikwad e chux ? Por favor, discuta o uso floatno segundo bloco de código apresentado (considere mostrar como power(2.0, -3)é computado).
7896

@greybeard Eu mencionei algum comentário. Pode ser que pode resolver sua consulta
roottraveller

1
GNU Scientific Library já tem a sua segunda função: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller você poderia explicar a negative exp and float basesolução? por que usamos temp, separamos exp por 2 e verificamos exp (par / ímpar)? obrigado!
Lev

6

Um caso extremamente especializado é que, quando você precisa dizer 2 ^ (- x para y), onde x, é claro que é negativo e y é muito grande para alterar um int. Você ainda pode fazer 2 ^ x em tempo constante apertando com um flutuador.

struct IeeeFloat
{

    unsigned int base : 23;
    unsigned int exponent : 8;
    unsigned int signBit : 1;
};


union IeeeFloatUnion
{
    IeeeFloat brokenOut;
    float f;
};

inline float twoToThe(char exponent)
{
    // notice how the range checking is already done on the exponent var 
    static IeeeFloatUnion u;
    u.f = 2.0;
    // Change the exponent part of the float
    u.brokenOut.exponent += (exponent - 1);
    return (u.f);
}

Você pode obter mais potências de 2 usando um duplo como o tipo base. (Muito obrigado aos comentaristas por ajudarem a corrigir esta postagem).

Há também a possibilidade de que, aprendendo mais sobre flutuadores IEEE , outros casos especiais de exponenciação possam se apresentar.


Solução bacana, mas sem compromisso ??
paxdiablo

Um flutuador IEEE é base x 2 ^ exp, alterar o valor do expoente não levará a nada além de uma multiplicação por uma potência de dois, e as chances são altas de desnormalizar o flutuador ... sua solução está errada IMHO
Drealmer

Você está correto, lembrei-me de que minha solução foi originalmente escrita, há tanto tempo, para potências de 2 explicitamente. Reescrevi minha resposta para ser uma solução de caso especial para o problema.
Doug T.

Em primeiro lugar, o código é quebrado conforme citado e requer edição para compilar. Em segundo lugar, o código está quebrado em um core2d usando o gcc. veja esse lixão Talvez eu tenha feito algo errado. I no entanto não acho que isso vai funcionar, pois o expoente IEEE float é base 10.
freespace

3
Base 10? Uh não, é base 2, a menos que você significou 10 em binário :)
Drealmer

4

Apenas como acompanhamento de comentários sobre a eficiência da exponenciação por quadratura.

A vantagem dessa abordagem é que ela é executada no tempo de log (n). Por exemplo, se você fosse calcular algo enorme, como x ^ 1048575 (2 ^ 20 - 1), você só precisa passar pelo loop 20 vezes, e não 1 milhão + usando a abordagem ingênua.

Além disso, em termos de complexidade do código, é mais simples do que tentar encontrar a seqüência mais ótima de multiplicações, como a sugestão de Pramod.

Editar:

Acho que devo esclarecer antes que alguém me marque o potencial de estouro. Essa abordagem pressupõe que você tenha algum tipo de biblioteca enorme.


2

Atrasado para a festa:

Abaixo está uma solução que também lida com y < 0o melhor possível.

  1. Ele usa um resultado de intmax_tpara o alcance máximo. Não há provisão para respostas que não se encaixem intmax_t.
  2. powjii(0, 0) --> 1o que é um resultado comum para este caso.
  3. pow(0,negative), outro resultado indefinido, retorna INTMAX_MAX

    intmax_t powjii(int x, int y) {
      if (y < 0) {
        switch (x) {
          case 0:
            return INTMAX_MAX;
          case 1:
            return 1;
          case -1:
            return y % 2 ? -1 : 1;
        }
        return 0;
      }
      intmax_t z = 1;
      intmax_t base = x;
      for (;;) {
        if (y % 2) {
          z *= base;
        }
        y /= 2;
        if (y == 0) {
          break; 
        }
        base *= base;
      }
      return z;
    }

Esse código usa um loop for(;;)para sempre para evitar o base *= basecomum final em outras soluções em loop. Essa multiplicação é 1) não necessária e 2) pode estar int*intexcedendo, o que é UB.


powjii(INT_MAX, 63)causa UB em base *= base. Considere verificar se é possível multiplicar ou vá para não assinado e deixe-o passar.
Cacahuete Frito

Não há razão para ter expassinado. Isso complica o código devido à situação ímpar em que (-1) ** (-N)é válido, e qualquer abs(base) > 1será 0para valores negativos de exp, portanto, é melhor deixar de assinar e salvar esse código.
Cacahuete Frito

1
@CacahueteFrito É verdade que, ycomo assinado, não é realmente necessário e traz as complicações que você comentou, mas a solicitação da OP foi específica pow(int, int). Portanto, esses bons comentários pertencem à pergunta do OP. Como o OP não especificou o que fazer no estouro, uma resposta errada bem definida é apenas marginalmente melhor que o UB. Dada a "maneira mais eficiente", duvido que o OP se preocupe com o OF.
chux - Restabelece Monica 17/03/19

1

solução mais genérica considerando exponenet negativo

private static int pow(int base, int exponent) {

    int result = 1;
    if (exponent == 0)
        return result; // base case;

    if (exponent < 0)
        return 1 / pow(base, -exponent);
    int temp = pow(base, exponent / 2);
    if (exponent % 2 == 0)
        return temp * temp;
    else
        return (base * temp * temp);
}

1
integer resultados divisão em um inteiro, para que o seu expoente negativo poderia ser muito mais eficiente, uma vez que isso só vai retornar 0, 1 ou -1 ...
jswolf19

pow(i, INT_MIN)poderia ser um loop infinito.
chux - Restabelece Monica

1
@chux: Poderia formatar o seu disco rígido: o excesso de número inteiro é UB.
MSalters 13/08/2015

@MSalters pow(i, INT_MIN)não excede o número inteiro. A atribuição desse resultado tempcertamente pode transbordar, potencial causando o fim dos tempos , mas vou me contentar com um valor aparentemente aleatório. :-)
chux - Reinstala Monica 13/08

0

Mais uma implementação (em Java). Pode não ser a solução mais eficiente, mas o número de iterações é o mesmo da solução exponencial.

public static long pow(long base, long exp){        
    if(exp ==0){
        return 1;
    }
    if(exp ==1){
        return base;
    }

    if(exp % 2 == 0){
        long half = pow(base, exp/2);
        return half * half;
    }else{
        long half = pow(base, (exp -1)/2);
        return base * half * half;
    }       
}

Não é uma pergunta sobre Java!
Cacahuete Frito

0

Eu uso recursivo, se o exp for par, 5 ^ 10 = 25 ^ 5.

int pow(float base,float exp){
   if (exp==0)return 1;
   else if(exp>0&&exp%2==0){
      return pow(base*base,exp/2);
   }else if (exp>0&&exp%2!=0){
      return base*pow(base,exp-1);
   }
}

0

Além da resposta de Elias, que causa Comportamento indefinido quando implementado com números inteiros assinados e valores incorretos para entrada alta quando implementada com números inteiros não assinados,

aqui está uma versão modificada da exponenciação por esquadro que também funciona com tipos inteiros assinados e não fornece valores incorretos:

#include <stdint.h>

#define SQRT_INT64_MAX (INT64_C(0xB504F333))

int64_t alx_pow_s64 (int64_t base, uint8_t exp)
{
    int_fast64_t    base_;
    int_fast64_t    result;

    base_   = base;

    if (base_ == 1)
        return  1;
    if (!exp)
        return  1;
    if (!base_)
        return  0;

    result  = 1;
    if (exp & 1)
        result *= base_;
    exp >>= 1;
    while (exp) {
        if (base_ > SQRT_INT64_MAX)
            return  0;
        base_ *= base_;
        if (exp & 1)
            result *= base_;
        exp >>= 1;
    }

    return  result;
}

Considerações para esta função:

(1 ** N) == 1
(N ** 0) == 1
(0 ** 0) == 1
(0 ** N) == 0

Se ocorrer algum transbordamento ou encapsulamento, return 0;

Eu usei int64_t, mas qualquer largura (assinada ou não) pode ser usada com poucas modificações. No entanto, se você precisa usar um tipo inteiro não de largura fixa, você terá de mudar SQRT_INT64_MAXpor (int)sqrt(INT_MAX)(no caso de usar int) ou algo semelhante, que deve ser otimizado, mas é mais feio, e não uma expressão C constante. Também converter o resultado de sqrt()para um intnão é muito bom por causa da precissão de ponto flutuante no caso de um quadrado perfeito, mas como não conheço nenhuma implementação em que INT_MAX- ou o máximo de qualquer tipo - seja um quadrado perfeito, você pode viver com isso.


0

Eu implementei um algoritmo que memoriza todos os poderes computados e os utiliza quando necessário. Assim, por exemplo, x ^ 13 é igual a (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x onde x ^ 2 ^ 2 foi retirado da tabela em vez de computá-lo novamente. Isso é basicamente a implementação da resposta @Pramod (mas em C #). O número de multiplicação necessário é Ceil (Log n)

public static int Power(int base, int exp)
{
    int tab[] = new int[exp + 1];
    tab[0] = 1;
    tab[1] = base;
    return Power(base, exp, tab);
}

public static int Power(int base, int exp, int tab[])
    {
         if(exp == 0) return 1;
         if(exp == 1) return base;
         int i = 1;
         while(i < exp/2)
         {  
            if(tab[2 * i] <= 0)
                tab[2 * i] = tab[i] * tab[i];
            i = i << 1;
          }
    if(exp <=  i)
        return tab[i];
     else return tab[i] * Power(base, exp - i, tab);
}

public? 2 funções com o mesmo nome? Esta é uma pergunta C.
Cacahuete Frito

-1

Meu caso é um pouco diferente, estou tentando criar uma máscara a partir de uma fonte, mas pensei em compartilhar a solução que encontrei de qualquer maneira.

Obviamente, ele só funciona para potências de 2.

Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;

Eu tentei isso, ele não funciona para 64 bits, foi desativado para nunca mais retornar e, nesse caso específico, estou tentando definir todos os bits abaixo do X, inclusive.
MarcusJ

Isso foi para 1 << 64? Isso é um estouro. O maior número inteiro está logo abaixo disso: (1 << 64) - 1.
Michaël Roy 16/06

1 << 64 == 0, é por isso. Talvez sua representação seja melhor para seu aplicativo. Eu prefiro coisas que podem ser colocados em uma macro, sem uma variável extra, como #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)), de modo que pode ser calculado em tempo de compilação
Michaël Roy

Sim, eu sei o que é um estouro. Só porque eu não usei essa palavra não é um convite para ser desnecessariamente condescendente. Como eu disse, isso funciona para mim e foi preciso um pouco de esforço para descobrir, portanto, compartilhá-lo. É simples assim.
MarcusJ

Me desculpe se eu te ofendi. Eu realmente não quis.
Michaël Roy

-1

Caso você saiba o expoente (e é um número inteiro) no tempo de compilação, você pode usar modelos para desenrolar o loop. Isso pode ser mais eficiente, mas eu queria demonstrar o princípio básico aqui:

#include <iostream>

template<unsigned long N>
unsigned long inline exp_unroll(unsigned base) {
    return base * exp_unroll<N-1>(base);
}

Encerramos a recursão usando uma especialização de modelo:

template<>
unsigned long inline exp_unroll<1>(unsigned base) {
    return base;
}

O expoente precisa ser conhecido em tempo de execução,

int main(int argc, char * argv[]) {
    std::cout << argv[1] <<"**5= " << exp_unroll<5>(atoi(argv[1])) << ;std::endl;
}

1
Claramente, essa não é uma questão de C ++. (c != c++) == 1
Cacahuete Frito
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.