Najbardziej wydajny sposób implementacji funkcji mocy opartej na liczbach całkowitych pow (int, int)


249

Jaki jest najskuteczniejszy sposób podniesienia liczby całkowitej do potęgi innej liczby całkowitej w C?

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

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

3
Kiedy mówisz „efektywność”, musisz określić efektywność w stosunku do czego. Prędkość? Zużycie pamięci? Rozmiar kodu? Konserwowalność?
Andy Lester,

Czy C nie ma funkcji pow ()?
jalf

16
tak, ale to działa na liczbach zmiennoprzecinkowych lub podwójnych, a nie na liczbach wewnętrznych
Nathan Fellman

1
Jeśli trzymasz się rzeczywistych ints (a nie jakiejś wielkiej klasy), wiele połączeń do ipow przepełni się. Zastanawiam się, czy istnieje sprytny sposób, aby wstępnie obliczyć tabelę i zredukować wszystkie nieprzepełnione kombinacje do prostego wyszukiwania w tabeli. Zajmie to więcej pamięci niż większość ogólnych odpowiedzi, ale być może będzie bardziej wydajne pod względem szybkości.
Adrian McCarthy

pow()funkcja nie jest bezpieczna
EsmaeelE,

Odpowiedzi:


391

Potęgowanie przez kwadrat.

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

    return result;
}

Jest to standardowa metoda wykonywania potęgowania modułowego dla ogromnych liczb w kryptografii asymetrycznej.


38
Prawdopodobnie powinieneś dodać zaznaczenie, że „exp” nie jest ujemne. Obecnie ta funkcja albo da złą odpowiedź, albo zapętli na zawsze. (W zależności od tego, czy >> = na podpisanym int robi zerowanie lub rozszerzenie znaku - kompilatory C mogą wybierać dowolne zachowanie).
user9876

23
Napisałem bardziej zoptymalizowaną wersję tego, którą można bezpłatnie pobrać tutaj: gist.github.com/3551590 Na mojej maszynie było około 2,5 razy szybsze.
orlp

10
@AkhilJain: Jest idealnie dobry C; aby to ważne również w języku Java, wymienić while (exp)i if (exp & 1)z while (exp != 0)i if ((exp & 1) != 0)odpowiednio.
Ilmari Karonen

3
Twoja funkcja prawdopodobnie powinna mieć unsigned exp, lub inaczej exppoprawnie obsługiwać negację .
Craig McQueen

5
@ ZinanXing Mnożenie n razy skutkuje większą liczbą mnożeń i jest wolniejsze. Ta metoda oszczędza multiplikacje, skutecznie je ponownie wykorzystując. Np. Do obliczenia n ^ 8 naiwna metoda n*n*n*n*n*n*n*nwykorzystuje 7 mnożenia. Ten algorytm zamiast tego oblicza m=n*n, a o=m*mnastępnie p=o*o, gdzie p= n ^ 8, tylko z trzema multiplikacjami. W przypadku dużych wykładników różnica w wydajności jest znacząca.
bames53

69

Zauważ, że potęgowanie przez kwadrat nie jest najbardziej optymalną metodą. Jest to prawdopodobnie najlepsza metoda, jaką można zrobić jako ogólną metodę, która działa dla wszystkich wartości wykładniczych, ale dla konkretnej wartości wykładniczej może istnieć lepsza sekwencja, która wymaga mniejszej liczby mnożenia.

Na przykład, jeśli chcesz obliczyć x ^ 15, metoda potęgowania przez podniesienie do kwadratu da ci:

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

Jest to w sumie 6 multiplikacji.

Okazuje się, że można tego dokonać za pomocą „tylko” 5 krotności poprzez potęgowanie łańcucha dodatków .

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

Nie ma wydajnych algorytmów pozwalających znaleźć tę optymalną sekwencję mnożenia. Z Wikipedii :

Problem znalezienia najkrótszego łańcucha dodawania nie może zostać rozwiązany przez programowanie dynamiczne, ponieważ nie spełnia założenia optymalnej podbudowy. Oznacza to, że nie wystarczy rozłożyć mocy na mniejsze moce, z których każda jest obliczana minimalnie, ponieważ łańcuchy dodatków dla mniejszych mocy mogą być powiązane (w celu współdzielenia obliczeń). Na przykład w najkrótszym łańcuchu dodawania dla powyższej a⁵, podproblem dla a⁶ musi zostać obliczony jako (a³) ², ponieważ a³ jest ponownie użyte (w przeciwieństwie do, powiedzmy, a⁶ = a² (a²) ², co również wymaga trzech mnożeń ).


4
@JeremySalwen: Jak wynika z tej odpowiedzi, potęgowanie binarne nie jest na ogół najbardziej optymalną metodą. Obecnie nie są znane skuteczne algorytmy do znajdowania minimalnej sekwencji mnożenia.
Eric Postpischil,

2
@EricPostpischil, To zależy od twojej aplikacji. Zwykle nie potrzebujemy ogólnego algorytmu do działania dla wszystkich liczb. Patrz The Art of Computer Programming, tom. 2: Algorytmy seminumeryczne
Pacerier

3
Dokładny opis tego dokładnego problemu znajduje się w artykule Od matematyki do programowania ogólnego autorstwa Aleksandra Stepanowa i Daniela Rose. Ta książka powinna znajdować się na półce każdego praktyka oprogramowania, IMHO.
Toby Speight,


Można to zoptymalizować dla liczb całkowitych, ponieważ istnieje znacznie mniej niż 255 mocy całkowitych, które nie spowodują przepełnienia dla liczb całkowitych 32-bitowych. Możesz buforować optymalną strukturę mnożenia dla każdej int. Wyobrażam sobie, że kod + dane nadal byłyby mniejsze niż zwykłe buforowanie wszystkich mocy ...
Josiah Yoder,

22

Jeśli musisz podnieść 2 do potęgi. Najszybszym sposobem na to jest przesunięcie bitowe według siły.

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

Czy istnieje elegancki sposób, aby to zrobić, aby 2 ** 0 == 1?
Rob Smallshire,

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

14

Oto metoda w Javie

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

nie działa dla dużych liczb np. pow (71045970,41535484)
Anushree Acharjee

16
@AnushreeAcharjee oczywiście nie. Obliczenie takiej liczby wymagałoby arytmetyki o dowolnej precyzji.
David Etler

Użyj BigInteger # modPow lub Biginteger # pow dla dużych liczb, odpowiednie algorytmy oparte na wielkości argumentów są już zaimplementowane
Raman Yelianevich

To NIE jest pytanie 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);
}

Nie mój głos, ale pow(1, -1)nie opuszcza zakresu int pomimo ujemnego wykładnika. Teraz, gdy ktoś działa przez przypadek, tak jak działa pow(-1, -1).
MSalters

Jedynym ujemnym wykładnikiem, który może nie zmuszać cię do opuszczenia zakresu int, jest -1. Działa to tylko wtedy, gdy podstawa to 1 lub -1. Są więc tylko dwie pary (podstawowa, exp) z exp <0, które nie prowadziłyby do potęgi niecałkowitych. Chociaż jestem matematykiem i lubię kwantyfikatory, myślę, że w tym przypadku w praktyce można powiedzieć, że wykładnik ujemny powoduje opuszczenie królestwa liczb całkowitych ...
bartgol

6

Jeśli chcesz uzyskać wartość liczby całkowitej dla 2 podniesioną do potęgi czegoś, zawsze lepiej jest użyć opcji shift:

pow(2,5) można zastąpić 1<<5

Jest to o wiele bardziej wydajne.


6

power()funkcja działa tylko dla liczb całkowitych

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;

}

Złożoność = O (log (exp))

power()funkcja do pracy dla ujemnego exp i 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 
    }

} 

Złożoność = O (log (exp))


Czym się to różni od odpowiedziach Abhijit Gaikwad i chux ? Argumentuj użycie floatdrugiego przedstawionego bloku kodu (rozważ pokazanie, w jaki sposób power(2.0, -3)zostanie obliczony).
greybeard

@greybeard Wspominałem o komentarzach. może być w stanie rozwiązać twoje zapytanie
roottraveller

1
Biblioteka naukowa GNU ma już twoją drugą funkcję: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller czy mógłbyś wyjaśnić negative exp and float baserozwiązanie? dlaczego używamy temp, oddziel exp przez 2 i sprawdzamy exp (parzyste / nieparzyste)? dzięki!
Lev

6

Niezwykle wyspecjalizowanym przypadkiem jest, gdy trzeba powiedzieć 2 ^ (- x do y), gdzie x jest oczywiście ujemne, a y jest zbyt duże, aby wykonać przesunięcie int. Nadal możesz zrobić 2 ^ x w stałym czasie, wkręcając pływak.

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

Możesz uzyskać więcej mocy 2, używając podwójnego jako typu podstawowego. (Wielkie dzięki dla komentujących za pomoc w poprawieniu tego posta).

Istnieje również możliwość, że jeśli dowiesz się więcej o pływakach IEEE , mogą pojawić się inne specjalne przypadki potęgowania.


Sprytne rozwiązanie, ale nie napisane ??
paxdiablo,

Liczba zmiennoprzecinkowa IEEE to podstawa x 2 ^ exp, zmiana wartości wykładnika nie prowadzi do niczego innego niż pomnożenie przez potęgę dwóch, a szanse są duże, że denormalizuje zmiennoprzecinkowe ... twoje rozwiązanie jest złe IMHO
Drealmer

Wszyscy macie rację, źle zapamiętałem, że moje rozwiązanie zostało napisane, och tak dawno temu, dla potęg 2 wyraźnie. Przepisałem swoją odpowiedź, aby była specjalnym rozwiązaniem problemu.
Doug T.,

Po pierwsze, kod jest uszkodzony zgodnie z cytatem i wymaga edycji, aby go skompilować. Po drugie, kod jest uszkodzony na core2d przy użyciu gcc. zobacz ten zrzut Być może zrobiłem coś złego. Nie sądzę jednak, aby to zadziałało, ponieważ wykładnik zmiennoprzecinkowy IEEE jest podstawą 10.
Frespace

3
Baza 10? Nie, to podstawa 2, chyba że miałeś na myśli 10 w wersji binarnej :)
Drealmer,

4

Podobnie jak kontynuacja komentarzy na temat wydajności potęgowania przez kwadrat.

Zaletą tego podejścia jest to, że działa w czasie log (n). Na przykład, jeśli zamierzasz obliczyć coś wielkiego, na przykład x ^ 1048575 (2 ^ 20-1), musisz przejść przez pętlę tylko 20 razy, a nie 1 milion +, stosując podejście naiwne.

Również pod względem złożoności kodu jest prostsze niż próba znalezienia najbardziej optymalnej sekwencji multiplikacji, co sugeruje la Pramod.

Edytować:

Chyba powinienem wyjaśnić, zanim ktoś oznaczy mnie tagiem pod kątem możliwości przepełnienia. Podejście to zakłada, że ​​masz jakąś ogromną bibliotekę.


2

Późno na imprezę:

Poniżej znajduje się rozwiązanie, które radzi sobie y < 0najlepiej, jak to możliwe.

  1. Wykorzystuje wynik intmax_tdla maksymalnego zasięgu. Nie ma przepisów dotyczących odpowiedzi, które nie pasują intmax_t.
  2. powjii(0, 0) --> 1co jest częstym wynikiem w tym przypadku.
  3. pow(0,negative), zwraca kolejny niezdefiniowany wynik 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;
    }

Ten kod używa pętli for(;;)na zawsze, aby uniknąć ostatecznego base *= basewspólnego w innych zapętlonych rozwiązaniach. To zwielokrotnienie jest 1) niepotrzebne i 2) może być int*intprzepełnieniem, które jest UB.


powjii(INT_MAX, 63)powoduje UB w base *= base. Zastanów się, czy możesz pomnożyć lub przejść do niepodpisanego i pozwolić mu się zawijać.
Cacahuete Frito

Nie ma powodu do exppodpisania. To komplikuje kod z powodu nieparzystej sytuacji, w której (-1) ** (-N)jest poprawny, i każda abs(base) > 1będzie miała 0ujemne wartości exp, więc lepiej mieć go bez znaku i zapisać ten kod.
Cacahuete Frito,

1
@CacahueteFrito Prawda, że ypodpisany nie jest tak naprawdę potrzebny i powoduje komplikacje, które skomentowałeś, ale prośba OP była konkretna pow(int, int). Tak więc te dobre komentarze należą do pytania OP. Ponieważ OP nie określił, co robić w przypadku przepełnienia, dobrze zdefiniowana zła odpowiedź jest tylko nieznacznie lepsza niż UB. Biorąc pod uwagę „najbardziej efektywny sposób”, wątpię, by OP troszczył się o OF.
chux - Przywróć Monikę

1

bardziej ogólne rozwiązanie z uwzględnieniem ujemnego exponenetu

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
dzielenie liczb całkowitych skutkuje liczbą całkowitą, więc wykładnik ujemny może być znacznie bardziej wydajny, ponieważ zwróci tylko 0, 1 lub -1 ...
jswolf19

pow(i, INT_MIN)może być nieskończoną pętlą.
chux - Przywróć Monikę

1
@chux: Może sformatować dysk twardy: przepełnienie liczb całkowitych to UB.
MSalters

@MSalters pow(i, INT_MIN)nie jest przepełnieniem liczb całkowitych. Przypisanie tego wyniku do z temppewnością może się przelać, potencjalnie powodując koniec czasu , ale zadowolę się pozornie losową wartością. :-)
chux - Przywróć Monikę

0

Jeszcze jedna implementacja (w Javie). Może nie być najskuteczniejszym rozwiązaniem, ale liczba iteracji jest taka sama jak w przypadku rozwiązania wykładniczego.

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

To nie jest pytanie Java!
Cacahuete Frito

0

Używam rekurencyjnego, jeśli exp jest parzysty, 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

Oprócz odpowiedzi Eliasa, która powoduje niezdefiniowane zachowanie po zaimplementowaniu za pomocą liczb całkowitych ze znakiem, oraz niepoprawne wartości wysokich danych wejściowych po zaimplementowaniu za pomocą liczb całkowitych bez znaku,

tutaj jest zmodyfikowana wersja potęgowania przez kwadrat, która działa również z podpisanymi typami liczb całkowitych i nie podaje niepoprawnych wartości:

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

Uwagi dotyczące tej funkcji:

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

Jeśli nastąpi przelew lub owijanie, return 0;

Użyłem int64_t, ale dowolnej szerokości (podpisanej lub niepodpisanej) można używać z niewielką modyfikacją. Jednakże, jeśli chcesz używać bez stałej szerokości typu INTEGER, będzie trzeba zmienić SQRT_INT64_MAXprzez (int)sqrt(INT_MAX)(w przypadku używania int) lub coś podobnego, który powinien zostać zoptymalizowany, ale jest brzydsze, a nie wyrazem C stała. Również rzutowanie wyniku sqrt()na „ intnie” jest bardzo dobre z powodu precyzji zmiennoprzecinkowej w przypadku idealnego kwadratu, ale ponieważ nie znam żadnej implementacji, w której INT_MAX- lub maksimum dowolnego typu - jest idealnym kwadratem, możesz żyć z tym.


0

Wdrożyłem algorytm, który zapamiętuje wszystkie obliczone moce, a następnie wykorzystuje je w razie potrzeby. Na przykład x ^ 13 jest równe (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x gdzie x ^ 2 ^ 2 wziął z tabeli zamiast go ponownie obliczać. Jest to w zasadzie implementacja odpowiedzi @Pramod (ale w języku C #). Wymagana liczba mnożenia to 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 funkcje o tej samej nazwie? To jest pytanie C.
Cacahuete Frito,

-1

Mój przypadek jest trochę inny, próbuję stworzyć maskę z mocy, ale pomyślałem, że i tak podzielę się rozwiązaniem, które znalazłem.

Oczywiście działa tylko dla potęg 2.

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

Próbowałem tego, nie działa dla wersji 64-bitowej, jest przesunięte, aby nigdy nie wracać, aw tym konkretnym przypadku próbuję ustawić wszystkie bity poniżej X włącznie.
MarcusJ

Czy to było dla 1 << 64? To przepełnienie. Największa liczba całkowita jest tuż poniżej: (1 << 64) - 1.
Michaël Roy

1 << 64 == 0, dlatego. Może Twoja reprezentacja jest najlepsza dla Twojej aplikacji. Wolę rzeczy, które można umieścić w makrze, bez dodatkowej zmiennej, takie jak #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)), aby można je było obliczyć w czasie kompilacji
Michaël Roy

Tak, wiem co to jest przepełnienie. To, że nie użyłem tego słowa, nie jest zaproszeniem do niepotrzebnego protekcjonizmu. Tak jak powiedziałem, działa to dla mnie i zajęło mi trochę wysiłku, aby odkryć, dlatego udostępniam to. To takie proste.
MarcusJ

Przepraszam, jeśli cię obraziłem. Naprawdę nie chciałem.
Michaël Roy

-1

Jeśli znasz wykładnik (i jest to liczba całkowita) w czasie kompilacji, możesz użyć szablonów do rozwinięcia pętli. Można to uczynić bardziej wydajnym, ale chciałem tutaj przedstawić podstawową zasadę:

#include <iostream>

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

Kończymy rekurencję za pomocą specjalizacji szablonu:

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

Wykładnik musi być znany w czasie wykonywania,

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

1
To oczywiście nie jest pytanie C ++. (c != c++) == 1
Cacahuete Frito,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.