Il modo più efficiente per implementare una funzione di potenza basata su numeri interi pow (int, int)


249

Qual è il modo più efficiente dato per elevare un numero intero alla potenza di un altro numero intero in C?

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

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

3
Quando dici "efficienza", devi specificare efficiente in relazione a cosa. Velocità? Utilizzo della memoria? Dimensione del codice? Manutenibilità?
Andy Lester,

C non ha una funzione pow ()?
jalf

16
si, ma funziona su float o double, non su ints
Nathan Fellman,

1
Se ti attieni alle ints effettive (e non a qualche classe enorme-int), molte chiamate a ipow trabocceranno. Mi chiedo se esiste un modo intelligente per pre-calcolare una tabella e ridurre tutte le combinazioni non traboccanti a una semplice ricerca della tabella. Ciò richiederebbe più memoria della maggior parte delle risposte generali, ma forse sarebbe più efficiente in termini di velocità.
Adrian McCarthy,

pow()funzione non sicura
EsmaeelE

Risposte:


391

Esponenziazione per quadratura.

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

    return result;
}

Questo è il metodo standard per eseguire esponenziazione modulare per grandi numeri nella crittografia asimmetrica.


38
Probabilmente dovresti aggiungere un segno di spunta che "exp" non è negativo. Attualmente, questa funzione darà una risposta sbagliata o ripeterà per sempre. (A seconda che >> = su un int firmato esegua zero-padding o sign-extension - i compilatori C possono scegliere entrambi i comportamenti).
user9876,

23
Ho scritto una versione più ottimizzata di questo, scaricabile gratuitamente qui: gist.github.com/3551590 Sulla mia macchina era circa 2,5 volte più veloce.
orlp

10
@AkhilJain: è perfettamente buono C; per renderlo valido anche in Java, sostituire while (exp)e if (exp & 1)con while (exp != 0)e if ((exp & 1) != 0)rispettivamente.
Ilmari Karonen,

3
La tua funzione dovrebbe probabilmente avere unsigned exp, oppure gestire il negativo expcorrettamente.
Craig McQueen,

5
@ZinanXing La moltiplicazione n volte provoca più moltiplicazioni ed è più lenta. Questo metodo salva le moltiplicazioni riutilizzandole in modo efficace. Ad esempio, per calcolare n ^ 8 il metodo ingenuo n*n*n*n*n*n*n*nutilizza 7 moltiplicazioni. Questo algoritmo invece calcolam=n*n , quindi o=m*m, p=o*odove p= n ^ 8, con solo tre moltiplicazioni. Con esponenti di grandi dimensioni la differenza di prestazioni è significativa.
bames53,

69

Si noti che l' esponenziazione mediante quadratura non è il metodo più ottimale. È probabilmente il meglio che puoi fare come metodo generale che funziona per tutti i valori di esponente, ma per un valore di esponente specifico potrebbe esserci una sequenza migliore che richiede meno moltiplicazioni.

Ad esempio, se vuoi calcolare x ^ 15, il metodo di esponenziazione mediante quadratura ti darà:

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

Questo è un totale di 6 moltiplicazioni.

Si scopre che ciò può essere fatto usando "solo" 5 moltiplicazioni tramite esponenziazione della catena di addizione .

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

Non ci sono algoritmi efficienti per trovare questa sequenza ottimale di moltiplicazioni. Da Wikipedia :

Il problema di trovare la catena di addizione più breve non può essere risolto dalla programmazione dinamica, poiché non soddisfa il presupposto di una sottostruttura ottimale. Cioè, non è sufficiente scomporre il potere in poteri più piccoli, ognuno dei quali è calcolato minimamente, poiché le catene di addizione per i poteri più piccoli possono essere correlate (per condividere i calcoli). Ad esempio, nella catena di addizione più breve per a¹⁵ sopra, il sottoproblema di a⁶ deve essere calcolato come (a³) ² poiché a³ viene riutilizzato (al contrario, diciamo, a⁶ = a² (a²) ², che richiede anche tre moltiplicazioni ).


4
@JeremySalwen: Come afferma questa risposta, l'esponenziazione binaria non è in generale il metodo più ottimale. Non ci sono algoritmi efficienti attualmente noti per trovare la sequenza minima di moltiplicazioni.
Eric Postpischil,

2
@EricPostpischil, dipende dalla tua applicazione. Di solito non abbiamo bisogno di un algoritmo generale per funzionare con tutti i numeri. Vedi L'arte della programmazione per computer, vol. 2: Algoritmi seminumerici
Pacerier,

3
C'è una buona esposizione di questo esatto problema in Dalla matematica alla programmazione generica di Alexander Stepanov e Daniel Rose. Questo libro dovrebbe essere sullo scaffale di ogni professionista del software, IMHO.
Toby Speight,


Questo potrebbe essere ottimizzato per i numeri interi perché ci sono ben meno di 255 valori interi che non causeranno un overflow per i numeri interi a 32 bit. È possibile memorizzare nella cache la struttura di moltiplicazione ottimale per ciascun int. Immagino che il codice + i dati sarebbero ancora più piccoli della semplice memorizzazione nella cache di tutti i poteri ...
Josiah Yoder

22

Se hai bisogno di aumentare 2 a una potenza. Il modo più veloce per farlo è spostarsi un po 'dal potere.

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

Esiste un modo elegante per farlo in modo che 2 ** 0 == 1?
Rob Smallshire,

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

14

Ecco il metodo in 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;
}

non funziona per grandi numeri, ad es. pow (71045970,41535484)
Anushree Acharjee,

16
@AnushreeAcharjee ovviamente no. Il calcolo di un tale numero richiederebbe un'aritmetica di precisione arbitraria.
David Etler,

Usa BigInteger # modPow o Biginteger # pow per grandi numeri, sono già implementati algoritmi appropriati basati sulla dimensione degli argomenti
Raman Yelianevich

Questa NON è una domanda 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);
}

Non è il mio voto, ma pow(1, -1)non lascia il range di int nonostante un esponente negativo. Ora quello funziona per caso, così come pow(-1, -1).
Salteri il

L'unico esponente negativo che potrebbe non farti lasciare l'intervallo di int è -1. E funziona solo se base è 1 o -1. Quindi ci sono solo due coppie (base, exp) con exp <0 che non porterebbe a poteri non interi. Anche se sono un matematico e mi piacciono i quantificatori, penso che in questo caso, in pratica, va bene dire che un esponente negativo ti fa lasciare il regno intero ...
Bartgol

6

Se vuoi ottenere il valore di un numero intero per 2 elevato alla potenza di qualcosa, è sempre meglio usare l'opzione shift:

pow(2,5) può essere sostituito da 1<<5

Questo è molto più efficiente.


6

power()funzione per funzionare solo per numeri interi

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;

}

Complessità = O (log (exp))

power()funzione per lavorare con exp negativo e float base .

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 
    }

} 

Complessità = O (log (exp))


In che cosa differisce dalle risposte di Abhijit Gaikwad e chux ? Si prega di discutere l'uso di floatnel secondo blocco di codice presentato (considerare la possibilità di mostrare come power(2.0, -3)viene calcolato).
Greybeard

@greybeard Ho citato qualche commento. potrebbe essere che può risolvere la tua richiesta
roottraveller

1
GNU Scientific Library ha già la tua seconda funzione: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller potresti spiegare la negative exp and float basesoluzione? perché usiamo temp, separiamo exp per 2 e controlliamo exp (pari / dispari)? Grazie!
Lev

6

Un caso estremamente specializzato è, quando hai bisogno di dire 2 ^ (- x a y), dove x, ovviamente, è negativo e y è troppo grande per fare lo spostamento su un int. Puoi ancora fare 2 ^ x in tempo costante avvitando con un galleggiante.

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

Puoi ottenere più poteri di 2 usando un doppio come tipo base. (Grazie mille ai commentatori per aver contribuito a far quadrare questo post).

C'è anche la possibilità che apprendendo di più sui galleggianti IEEE , possano presentarsi altri casi speciali di esponenziazione.


Soluzione ingegnosa, ma non credente ??
paxdiablo,

Un float IEEE è base x 2 ^ exp, cambiare il valore dell'esponente non porterà a nient'altro che una moltiplicazione per una potenza di due, e le probabilità sono alte che denormalizzerà il float ... la tua soluzione è sbagliata IMHO
Drealmer

Hai ragione, ho ricordato male che la mia soluzione era stata originariamente scritta, oh tanto tempo fa, per poteri espliciti di 2. Ho riscritto la mia risposta come soluzione per un caso speciale al problema.
Doug T.,

Innanzitutto, il codice è rotto come indicato e richiede la modifica per poterlo compilare. In secondo luogo il codice è rotto su un core2d usando gcc. vedere questa discarica Forse ho fatto qualcosa di sbagliato. Tuttavia, non credo che funzionerà, poiché l'esponente float IEEE è di base 10.
freespace,

3
Base 10? No, è base 2, a meno che tu non intendessi 10 in binario :)
Drealmer,

4

Proprio come seguito ai commenti sull'efficienza dell'espiazione mediante quadratura.

Il vantaggio di questo approccio è che viene eseguito in log (n) time. Ad esempio, se stavi per calcolare qualcosa di enorme, come x ^ 1048575 (2 ^ 20 - 1), devi solo passare attraverso il ciclo 20 volte, non 1 milione + utilizzando l'approccio ingenuo.

Inoltre, in termini di complessità del codice, è più semplice che cercare di trovare la sequenza ottimale di moltiplicazioni, come suggerisce la Pramod.

Modificare:

Immagino che dovrei chiarire prima che qualcuno mi tagghi per il potenziale di overflow. Questo approccio presuppone che tu abbia una sorta di libreria vastissima.


2

In ritardo alla festa:

Di seguito è una soluzione che si occupa anche y < 0il meglio possibile.

  1. Utilizza un risultato di intmax_tper la portata massima. Non sono previste risposte che non rientrano intmax_t.
  2. powjii(0, 0) --> 1che è un risultato comune per questo caso.
  3. pow(0,negative), un altro risultato indefinito, ritorna 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;
    }

Questo codice utilizza un ciclo for(;;)per sempre per evitare il base *= basecomune finale in altre soluzioni in loop. Quella moltiplicazione è 1) non necessaria e 2) potrebbe essere un int*intoverflow che è UB.


powjii(INT_MAX, 63)causa UB in base *= base. Considera di poter moltiplicare o passare a unsigned e lasciarlo avvolgere.
Cacahuete Frito,

Non c'è motivo di expessere firmato. Ciò complica il codice a causa della strana situazione in cui (-1) ** (-N)è valido, e qualsiasi abs(base) > 1sarà 0per valori negativi di exp, quindi è meglio averlo senza segno e salvare quel codice.
Cacahuete Frito,

1
@CacahueteFrito Vero che ycome firmato non è davvero necessario e porta le complicazioni che hai commentato, eppure la richiesta di OP era specifica pow(int, int). Quindi questi buoni commenti appartengono alla domanda del PO. Poiché OP non ha specificato cosa fare in caso di overflow, una risposta errata ben definita è solo leggermente migliore di UB. Dato il "modo più efficiente", dubito che OP si preoccupi per OF.
chux - Ripristina Monica il

1

soluzione più generica 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
la divisione dei numeri interi genera un numero intero, quindi l'esponente negativo potrebbe essere molto più efficiente poiché restituirà solo 0, 1 o -1 ...
jswolf19

pow(i, INT_MIN)potrebbe essere un ciclo infinito.
chux - Ripristina Monica il

1
@chux: potrebbe formattare il disco rigido: l'overflow dei numeri interi è UB.
Salterio,

@MSalters pow(i, INT_MIN)non è overflow dei numeri interi. L'assegnazione di quel risultato temppotrebbe certamente traboccare, causando potenzialmente la fine dei tempi , ma mi accontenterò di un valore apparentemente casuale. :-)
chux - Ripristina Monica il

0

Un'altra implementazione (in Java). Potrebbe non essere la soluzione più efficiente ma il numero di iterazioni è uguale a quello della soluzione esponenziale.

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

Non è una domanda Java!
Cacahuete Frito,

0

Uso ricorsivo, se exp è pari, 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

Oltre alla risposta di Elias, che causa un comportamento indefinito quando implementato con numeri interi con segno e valori errati per input elevati se implementato con numeri interi senza segno,

ecco una versione modificata di Exponentiation by Squaring che funziona anche con tipi interi con segno e non fornisce valori errati:

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

Considerazioni per questa funzione:

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

In caso di overflow o avvolgimento, return 0;

Ho usato int64_t, ma qualsiasi larghezza (con o senza segno) può essere usata con poche modifiche. Tuttavia, se è necessario utilizzare un tipo intero non-larghezza fissa, sarà necessario cambiamento SQRT_INT64_MAXda (int)sqrt(INT_MAX)(in caso di utilizzo int) o qualcosa di simile, che dovrebbe essere ottimizzato, ma è brutto, e non un'espressione C costante. Anche il cast del risultato su sqrt()an intnon è molto buono a causa della precisione in virgola mobile nel caso di un quadrato perfetto, ma poiché non conosco alcuna implementazione in cui INT_MAXil massimo di qualsiasi tipo sia un quadrato perfetto, puoi vivere con quello.


0

Ho implementato un algoritmo che memorizza tutti i poteri calcolati e li usa quando necessario. Ad esempio, x ^ 13 è uguale a (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x dove x ^ 2 ^ 2 ha preso dalla tabella invece di calcolarlo di nuovo. Questa è sostanzialmente l'implementazione della risposta @Pramod (ma in C #). Il numero di moltiplicazioni necessarie è 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 funzioni con lo stesso nome? Questa è una domanda C.
Cacahuete Frito,

-1

Il mio caso è un po 'diverso, sto provando a creare una maschera da un potere, ma ho pensato di condividere comunque la soluzione che ho trovato.

Ovviamente, funziona solo per potenze di 2.

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

Ci ho provato, non funziona per 64 bit, si è spostato per non tornare mai più e, in questo caso specifico, sto cercando di impostare tutti i bit inferiori a X, incluso.
MarcusJ,

Era per 1 << 64? È un trabocco. Il numero intero più grande è proprio sotto: (1 << 64) - 1.
Michaël Roy

1 << 64 == 0, ecco perché. Forse la tua rappresentazione è la migliore per la tua app. Preferisco le cose che possono essere inserite in una macro, senza una variabile aggiuntiva, come #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)), in modo che possano essere calcolate al momento della compilazione
Michaël Roy

Sì, so cos'è un overflow. Solo perché non ho usato quella parola non è un invito a essere inutilmente condiscendente. Come ho detto, questo funziona per me e ci è voluto un po 'di sforzo per scoprirlo, quindi condividendolo. È così semplice.
MarcusJ,

Mi dispiace se ti ho offeso. Davvero non volevo.
Michaël Roy,

-1

Se conosci l'esponente (ed è un numero intero) in fase di compilazione, puoi utilizzare i modelli per srotolare il ciclo. Questo può essere reso più efficiente, ma volevo dimostrare qui il principio di base:

#include <iostream>

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

Terminiamo la ricorsione usando una specializzazione template:

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

L'esponente deve essere conosciuto in fase di esecuzione,

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

1
Questa chiaramente non è una domanda C ++. (c != c++) == 1
Cacahuete Frito,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.