Cara paling efisien untuk mengimplementasikan pow function power berbasis integer (int, int)


249

Apa cara paling efisien yang diberikan untuk menaikkan bilangan bulat ke kekuatan bilangan bulat lain di C?

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

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

3
Ketika Anda mengatakan "efisiensi," Anda perlu menentukan efisiensi dalam kaitannya dengan apa. Mempercepat? Penggunaan memori? Ukuran kode? Kemampuan pemeliharaan?
Andy Lester

Bukankah C memiliki fungsi pow ()?
jalf

16
ya, tapi itu bekerja pada pelampung atau ganda, bukan pada int
Nathan Fellman

1
Jika Anda tetap menggunakan ints aktual (dan bukan kelas int besar), banyak panggilan ke ipow akan meluap. Itu membuat saya bertanya-tanya apakah ada cara cerdas untuk pra-menghitung tabel dan mengurangi semua kombinasi yang tidak melimpah ke pencarian tabel sederhana. Ini akan membutuhkan lebih banyak memori daripada sebagian besar jawaban umum, tetapi mungkin lebih efisien dalam hal kecepatan.
Adrian McCarthy

pow()bukan fungsi yang aman
EsmaeelE

Jawaban:


391

Eksponen dengan mengkuadratkan.

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

    return result;
}

Ini adalah metode standar untuk melakukan eksponensial modular untuk angka besar dalam kriptografi asimetris.


38
Anda mungkin harus menambahkan cek bahwa "exp" tidak negatif. Saat ini, fungsi ini akan memberikan jawaban yang salah atau mengulang selamanya. (Bergantung pada apakah >> = pada int yang ditandatangani melakukan zero-padding atau sign-extension - kompiler C diizinkan untuk memilih salah satu perilaku).
user9876

23
Saya menulis versi yang lebih optimal dari ini, dapat diunduh secara bebas di sini: gist.github.com/3551590 Pada mesin saya itu sekitar 2,5x lebih cepat.
orlp

10
@AkhilJain: Ini sangat bagus C; untuk membuatnya valid juga di Jawa, ganti while (exp)dan if (exp & 1)dengan while (exp != 0)dan if ((exp & 1) != 0)masing - masing.
Ilmari Karonen

3
Fungsi Anda mungkin seharusnya unsigned exp, atau menangani negatif expdengan benar.
Craig McQueen

5
@ZinanXing Mengalikan n kali menghasilkan lebih banyak perkalian dan lebih lambat. Metode ini menyimpan perkalian dengan menggunakannya kembali secara efektif. Misalnya, untuk menghitung n ^ 8 metode naif n*n*n*n*n*n*n*nmenggunakan 7 perkalian. Algoritma ini sebagai gantinya menghitung m=n*n, kemudian o=m*m, kemudian p=o*o, di mana p= n ^ 8, hanya dengan tiga perkalian. Dengan eksponen besar perbedaan dalam kinerja sangat signifikan.
bames53

69

Perhatikan bahwa eksponensial dengan mengkuadratkan bukan metode yang paling optimal. Ini mungkin yang terbaik yang dapat Anda lakukan sebagai metode umum yang bekerja untuk semua nilai eksponen, tetapi untuk nilai eksponen tertentu mungkin ada urutan yang lebih baik yang membutuhkan lebih sedikit perkalian.

Misalnya, jika Anda ingin menghitung x ^ 15, metode eksponensial dengan mengkuadratkan akan memberi Anda:

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

Ini adalah total 6 perkalian.

Ternyata ini bisa dilakukan dengan menggunakan "hanya" 5 perkalian melalui eksponensial rantai penjumlahan .

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

Tidak ada algoritma yang efisien untuk menemukan urutan perkalian yang optimal ini. Dari Wikipedia :

Masalah menemukan rantai penambahan terpendek tidak dapat diselesaikan dengan pemrograman dinamis, karena tidak memenuhi asumsi substruktur optimal. Artinya, tidak cukup untuk menguraikan daya menjadi kekuatan yang lebih kecil, yang masing-masing dihitung secara minimal, karena rantai tambahan untuk kekuatan yang lebih kecil mungkin terkait (untuk berbagi perhitungan). Misalnya, dalam rantai penambahan terpendek untuk a¹⁵ di atas, subproblem untuk a⁶ harus dihitung sebagai (a³) ² karena a³ digunakan kembali (sebagai lawan dari, katakanlah, a⁶ = a² (a²) ², yang juga membutuhkan tiga kali lipat ).


4
@JeremySalwen: Seperti yang dinyatakan dalam jawaban ini, eksponensial biner pada umumnya bukan metode yang paling optimal. Tidak ada algoritma yang efisien saat ini dikenal untuk menemukan urutan perkalian minimal.
Eric Postpischil

2
@EricPostpischil, Itu tergantung pada aplikasi Anda. Biasanya kita tidak memerlukan algoritma umum untuk bekerja untuk semua angka. Lihat Seni Pemrograman Komputer, Vol. 2: Algoritma Seminumerik
Pacerier

3
Ada eksposisi yang baik dari masalah yang tepat ini di Dari Matematika ke Pemrograman Generik oleh Alexander Stepanov dan Daniel Rose. Buku ini harus ada di rak setiap praktisi perangkat lunak, IMHO.
Toby Speight

2

Ini bisa dioptimalkan untuk integer karena ada jauh di bawah 255 kekuatan integer yang tidak akan menyebabkan overflow untuk integer 32 bit. Anda bisa menyimpan struktur perkalian optimal untuk setiap int. Saya membayangkan kode + data masih akan lebih kecil dari sekadar caching semua kekuatan ...
Josiah Yoder

22

Jika Anda perlu meningkatkan 2 ke daya. Cara tercepat untuk melakukannya adalah dengan sedikit menggeser kekuatan.

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

Apakah ada cara yang elegan untuk melakukan ini sehingga 2 ** 0 == 1?
Rob Smallshire

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

14

Ini metode di Jawa

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

tidak bekerja untuk mati rasa besar misalnya pow (71045970,41535484)
Anushree Acharjee

16
@AnushreeAcharjee tentu saja tidak. Menghitung angka seperti itu akan membutuhkan aritmatika presisi yang sewenang-wenang.
David Etler

Gunakan BigInteger # modPow atau Biginteger # pow untuk angka besar, algoritme yang tepat berdasarkan ukuran argumen sudah diterapkan
Raman Yelianevich

Ini BUKAN pertanyaan 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);
}

Bukan suara saya, tetapi pow(1, -1)tidak meninggalkan kisaran int meskipun eksponen negatif. Sekarang yang satu bekerja secara tidak sengaja, seperti halnya pow(-1, -1).
MSalters

Eksponen hanya negatif yang mungkin tidak membuat Anda meninggalkan berbagai int adalah -1. Dan itu hanya berfungsi jika basis adalah 1 atau -1. Jadi hanya ada dua pasangan (basis, exp) dengan exp <0 yang tidak akan menghasilkan kekuatan non integer. Meskipun saya seorang matematikawan dan saya suka bilangan bulat, saya pikir dalam kasus ini, dalam praktiknya, tidak apa-apa untuk mengatakan bahwa eksponen negatif membuat Anda meninggalkan dunia bilangan bulat ...
bartgol

6

Jika Anda ingin mendapatkan nilai integer untuk 2 dengan kekuatan sesuatu, selalu lebih baik menggunakan opsi shift:

pow(2,5) dapat digantikan oleh 1<<5

Ini jauh lebih efisien.


6

power()berfungsi hanya untuk Integer

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;

}

Kompleksitas = O (log (exp))

power()berfungsi untuk bekerja untuk basis exp dan float negatif .

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 
    }

} 

Kompleksitas = O (log (exp))


Bagaimana ini berbeda dari jawaban Abhijit Gaikwad dan chux ? Harap berdebat penggunaan floatdalam blok kode kedua yang disajikan (pertimbangkan untuk menunjukkan bagaimana cara power(2.0, -3)dikomputasi).
greybeard

@ ChrisBeard Saya telah menyebutkan beberapa komentar. mungkin itu bisa menyelesaikan kueri Anda
roottraveller

1
Perpustakaan Ilmiah GNU sudah memiliki fungsi kedua: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller bisa tolong jelaskan negative exp and float basesolusinya? mengapa kita menggunakan temp, pisahkan exp dengan 2 dan periksa exp (datar / ganjil)? Terima kasih!
Im

6

Kasus yang sangat khusus adalah, ketika Anda perlu mengatakan 2 ^ (- x ke y), di mana x, tentu saja negatif dan y terlalu besar untuk melakukan pengalihan pada int. Anda masih dapat melakukan 2 ^ x dalam waktu konstan dengan mengacaukan float.

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

Anda bisa mendapatkan lebih banyak kekuatan 2 dengan menggunakan ganda sebagai tipe dasar. (Terima kasih banyak kepada para komentator karena telah membantu untuk memperbaiki posisi ini).

Ada juga kemungkinan bahwa mempelajari lebih lanjut tentang IEEE mengapung , kasus eksponensial khusus lainnya mungkin muncul dengan sendirinya.


Solusi bagus, tapi tidak sopan ??
paxdiablo

IEEE float adalah basis x2 ^ exp, mengubah nilai eksponen tidak akan mengarah ke perkalian selain dengan kekuatan dua, dan kemungkinan besar itu akan denormalize float ... solusi Anda salah IMHO
Drealmer

Anda semua benar, saya salah ingat bahwa solusi saya awalnya ditulis, oh dulu, untuk kekuatan 2 secara eksplisit. Saya telah menulis ulang jawaban saya untuk menjadi solusi kasus khusus untuk masalah ini.
Doug T.

Pertama, kode rusak seperti dikutip, dan membutuhkan pengeditan untuk mengkompilasinya. Kedua kode rusak pada core2d menggunakan gcc. lihat dump ini Mungkin saya melakukan sesuatu yang salah. Namun saya tidak berpikir ini akan berhasil, karena eksponen float IEEE adalah basis 10.
freespace

3
Basis 10? Eh, tidak, ini basis 2, kecuali jika Anda berarti 10 dalam biner :)
Drealmer

4

Sama seperti tindak lanjut komentar tentang efisiensi eksponensial dengan mengkuadratkan.

Keuntungan dari pendekatan itu adalah berjalan dalam waktu log (n). Misalnya, jika Anda akan menghitung sesuatu yang besar, seperti x ^ 1048575 (2 ^ 20 - 1), Anda hanya perlu melalui loop 20 kali, bukan 1 juta + menggunakan pendekatan naif.

Juga, dalam hal kompleksitas kode, ini lebih sederhana daripada mencoba menemukan urutan perkalian yang paling optimal, saran ala Pramod.

Edit:

Saya kira saya harus mengklarifikasi sebelum seseorang menandai saya untuk potensi meluap. Pendekatan ini mengasumsikan bahwa Anda memiliki semacam perpustakaan bigint.


2

Terlambat ke pesta:

Di bawah ini adalah solusi yang juga menangani y < 0sebaik mungkin.

  1. Ini menggunakan hasil intmax_tuntuk rentang maksimum. Tidak ada ketentuan untuk jawaban yang tidak cocok intmax_t.
  2. powjii(0, 0) --> 1yang merupakan hasil umum untuk kasus ini.
  3. pow(0,negative), hasil tidak terdefinisi lain, kembali 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;
    }

Kode ini menggunakan loop selamanya for(;;)untuk menghindari akhir base *= baseumum dalam solusi looped lainnya. Multiplikasi itu 1) tidak diperlukan dan 2) bisa int*intmeluap yaitu UB.


powjii(INT_MAX, 63)menyebabkan UB di base *= base. Pertimbangkan untuk memeriksa apakah Anda dapat melipatgandakan, atau pindah ke yang tidak ditandatangani dan membiarkannya membungkus.
Cacahuete Frito

Tidak ada alasan untuk expditandatangani. Ini menyulitkan kode karena situasi aneh di mana (-1) ** (-N)valid, dan apa pun abs(base) > 1akan 0untuk nilai negatif exp, jadi lebih baik untuk tidak ditandatangani dan menyimpan kode itu.
Cacahuete Frito

1
@CacahueteFrito Benar bahwa yang yditandatangani tidak benar-benar diperlukan dan membawa komplikasi yang Anda komentari, namun permintaan OP bersifat spesifik pow(int, int). Jadi komentar baik itu termasuk dalam pertanyaan OP. Karena OP belum menentukan apa yang harus dilakukan pada overflow, jawaban yang salah dengan baik hanya sedikit lebih baik dari UB. Diberi "cara paling efisien", saya ragu OP peduli tentang OF.
chux - Reinstate Monica

1

solusi yang lebih umum mempertimbangkan eksponen negatif

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
pembagian integer menghasilkan bilangan bulat, sehingga eksponen negatif Anda bisa menjadi jauh lebih efisien karena hanya akan mengembalikan 0, 1, atau -1 ...
jswolf19

pow(i, INT_MIN)bisa menjadi loop tak terbatas.
chux

1
@ chux: Ini bisa memformat harddisk Anda: integer overflow adalah UB.
MSalters

@MSalters pow(i, INT_MIN)bukan integer overflow. Penugasan hasil itu temppasti meluap, berpotensi menyebabkan akhir waktu , tetapi saya akan puas dengan nilai yang tampaknya acak. :-)
chux - Reinstate Monica

0

Satu lagi implementasi (di Jawa). Mungkin bukan solusi yang paling efisien tetapi # iterasi sama dengan solusi Eksponensial.

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

Bukan pertanyaan Java!
Cacahuete Frito

0

Saya menggunakan rekursif, jika exp bahkan, 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

Selain jawaban oleh Elias, yang menyebabkan Perilaku Tidak Terdefinisi ketika diimplementasikan dengan bilangan bulat yang ditandatangani, dan nilai yang salah untuk input tinggi ketika diimplementasikan dengan bilangan bulat yang tidak ditandatangani,

di sini adalah versi modifikasi dari Exponentiation by Squaring yang juga berfungsi dengan tipe integer yang ditandatangani, dan tidak memberikan nilai yang salah:

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

Pertimbangan untuk fungsi ini:

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

Jika terjadi overflow atau pembungkus, return 0;

Saya menggunakan int64_t, tetapi lebar apa pun (ditandatangani atau tidak ditandatangani) dapat digunakan dengan sedikit modifikasi. Namun, jika Anda perlu menggunakan tipe integer lebar tidak-tetap, Anda perlu mengubahnya SQRT_INT64_MAXdengan (int)sqrt(INT_MAX)(jika menggunakan int) atau sesuatu yang serupa, yang harus dioptimalkan, tetapi lebih jelek, dan bukan ekspresi konstan C. Juga casting hasil dari sqrt()ke inttidak terlalu baik karena precisi floating point dalam kasus kuadrat sempurna, tetapi karena saya tidak tahu implementasi apa pun di mana INT_MAX- atau maksimum jenis apa pun - adalah kuadrat sempurna, Anda dapat hidup dengan itu.


0

Saya telah mengimplementasikan algoritma yang menghafal semua kekuatan yang dihitung dan kemudian menggunakannya ketika dibutuhkan. Jadi misalnya x ^ 13 sama dengan (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x di mana x ^ 2 ^ 2 diambil dari tabel alih-alih menghitungnya sekali lagi. Ini pada dasarnya adalah implementasi dari jawaban @Pramod (tetapi dalam C #). Jumlah perkalian yang dibutuhkan adalah 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 fungsi bernama sama? Ini adalah pertanyaan C.
Cacahuete Frito

-1

Kasus saya sedikit berbeda, saya mencoba membuat topeng dari kekuatan, tapi saya pikir saya akan membagikan solusi yang saya temukan.

Jelas, itu hanya berfungsi untuk kekuatan 2.

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

Saya mencoba itu, itu tidak bekerja untuk 64 bit, itu bergeser tidak pernah kembali, dan dalam kasus khusus ini, saya mencoba untuk mengatur semua bit lebih rendah dari X, inklusif.
MarcusJ

Apakah itu untuk 1 << 64? Itu meluap. Bilangan bulat terbesar tepat di bawah itu: (1 << 64) - 1.
Michaël Roy

1 << 64 == 0, itu sebabnya. Mungkin representasi Anda terbaik untuk aplikasi Anda. Saya lebih suka hal-hal yang dapat dimasukkan ke dalam makro, tanpa variabel tambahan, seperti #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)), sehingga dapat dihitung pada waktu kompilasi
Michaël Roy

Ya, saya tahu apa itu luapan. Hanya karena saya tidak menggunakan kata itu bukan undangan untuk direndahkan dengan sia-sia. Seperti yang saya katakan, ini bekerja untuk saya dan butuh sedikit usaha untuk menemukan dan membagikannya. Sesederhana itu.
MarcusJ

Maaf jika aku menyinggungmu. Aku benar-benar tidak bermaksud begitu.
Michaël Roy

-1

Jika Anda tahu eksponen (dan bilangan bulat) pada waktu kompilasi, Anda bisa menggunakan templat untuk membuka gulungannya. Ini dapat dibuat lebih efisien, tetapi saya ingin menunjukkan prinsip dasar di sini:

#include <iostream>

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

Kami mengakhiri rekursi menggunakan spesialisasi templat:

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

Eksponen perlu diketahui saat runtime,

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

1
Ini jelas bukan pertanyaan C ++. (c != c++) == 1
Cacahuete Frito
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.