Tamsayı tabanlı bir güç fonksiyonu pow (int, int) uygulamanın en etkili yolu


249

Bir tamsayıyı C'deki başka bir tamsayının gücüne yükseltmenin en etkili yolu nedir?

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

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

3
"Verimlilik" dediğinizde, neyle ilgili olarak verimli belirtmeniz gerekir. Hız? Hafıza kullanımı? Kod boyutu? İdame?
Andy Lester

C'nin pow () işlevi yok mu?
jalf

16
evet, ama bu şamandıralar üzerinde ya da çiftler üzerinde çalışıyor
Nathan Fellman

1
Gerçek ints'ye (ve bazı büyük int sınıflarına) bağlı değilseniz, ipow'a yapılan birçok çağrı taşar. Bir tabloyu önceden hesaplamanın ve taşmayan tüm kombinasyonları basit bir tablo aramasına indirmenin akıllı bir yolu olup olmadığını merak ediyor. Bu, genel cevapların çoğundan daha fazla bellek alır, ancak belki de hız açısından daha verimli olur.
Adrian McCarthy

pow()güvenli bir işlev değil
EsmaeelE

Yanıtlar:


391

Kareleme ile üs alma.

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

    return result;
}

Bu, asimetrik kriptografide büyük sayılar için modüler üs alma işleminin standart yöntemidir.


38
Muhtemelen "exp" in negatif olmadığına dair bir kontrol eklemelisiniz. Şu anda, bu işlev ya yanlış bir cevap verecektir ya da sonsuza dek döngü verecektir. (İmzalı bir int üzerindeki >> = değerinin sıfır doldurma veya işaret uzantısı yapmasına bağlı olarak - C derleyicilerinin her iki davranışı seçmesine izin verilir).
user9876

23
Bunun daha optimize edilmiş bir versiyonunu yazdım, burada özgürce indirilebilir: gist.github.com/3551590 Makinemde yaklaşık 2,5 kat daha hızlıydı.
orlp

10
@AkhilJain: Mükemmel derecede iyi C; Java da geçerli kılmak için, değiştirmek while (exp)ve if (exp & 1)ile while (exp != 0)ve if ((exp & 1) != 0)sırasıyla.
Ilmari Karonen

3
İşlevinizin muhtemelen unsigned expnegatif olması veya başka expşekilde negatif işlemesi gerekir.
Craig McQueen

5
@ZinanXing n kere çarpmak daha fazla çarpmaya neden olur ve daha yavaştır. Bu yöntem çarpmaları etkili bir şekilde tekrar kullanarak kaydeder. Örneğin, n ^ 8 hesaplamak için saf yöntem n*n*n*n*n*n*n*n7 çarpma kullanır. Bu algoritma, bunun yerine m=n*n, o o=m*mzaman p=o*oburada p= n ^ 8, sadece üç çarpma ile hesaplar . Büyük üslerle performans farkı önemlidir.
bames53

69

Not, kare alma ile üs en uygun bir yöntem değildir. Muhtemelen tüm üs değerleri için çalışan genel bir yöntem olarak yapabileceğiniz en iyisidir, ancak belirli bir üs değeri için daha az çarpmaya ihtiyaç duyan daha iyi bir dizi olabilir.

Örneğin, x ^ 15 hesaplamak istiyorsanız, karelemeyle üs alma yöntemi size şunları verecektir:

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

Bu toplam 6 çarpmadır.

Bunun, ekleme zinciri üsleri ile "sadece" 5 çarpımı kullanılarak yapılabileceği anlaşılmaktadır .

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

Bu optimal çarpma dizisini bulmak için etkili bir algoritma yoktur. Gönderen Vikipedi :

En kısa toplama zincirini bulma problemi, dinamik programlama ile çözülemez, çünkü optimum altyapı varsayımını karşılamaz. Yani, gücü her biri minimal olarak hesaplanan daha küçük güçlere ayırmak yeterli değildir, çünkü daha küçük güçler için ilave zincirler ilişkili olabilir (hesaplamaları paylaşmak için). Örneğin, a¹⁵ için en kısa toplama zincirinde, a⁶ alt-problemi (a³) ² olarak hesaplanmalıdır, çünkü a³ yeniden kullanılır (örneğin, a⁶ = a² (a²) ²'nin aksine, üç çarpı gerektirir ).


4
@ JererSalwen: Bu yanıtın belirttiği gibi, ikili üs genel olarak en uygun yöntem değildir. Şu anda minimal çarpma dizisini bulmak için bilinen etkili bir algoritma yoktur.
Eric Postpischil

2
@EricPostpischil, Bu, uygulamanıza bağlıdır. Genellikle tüm sayılar için çalışmak için genel bir algoritmaya ihtiyacımız yoktur . Bkz. Bilgisayar Programlama Sanatı, Cilt. 2: Seminumerical Algorithms
Pacerier

3
Alexander Stepanov ve Daniel Rose'un matematikten jenerik programlamaya kadar bu probleminin iyi bir açıklaması var . Bu kitap her yazılım uygulayıcısı IMHO'nun rafında olmalıdır.
Toby Speight

2
Ayrıca bkz . En.wikipedia.org/wiki/… .
lhf

Bu, tamsayılar için optimize edilebilir, çünkü 32 bit tamsayılar için taşmaya neden olmayacak 255'in altında tamsayı gücü vardır. Her int için en uygun çarpma yapısını önbelleğe alabilirsiniz. Kod + verilerin hala tüm güçleri önbelleğe almaktan daha küçük olacağını hayal ediyorum ...
Josiah Yoder

22

2'yi bir güce yükseltmeniz gerekiyorsa. Bunu yapmanın en hızlı yolu, güç tarafından biraz kaydırmaktır.

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

Bunu yapmanın zarif bir yolu var mı? 2 ** 0 == 1?
Rob Smallshire

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

14

İşte Java'da yöntem

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

büyük sayılar için çalışmaz, örneğin pow (71045970,41535484)
Anushree Acharjee

16
@AnushreeAcharjee tabii ki hayır. Böyle bir sayıyı hesaplamak keyfi hassasiyet aritmetiği gerektirir.
David Etler

Büyük sayılar için BigInteger # modPow veya Biginteger # pow kullanın, bağımsız değişkenlerin boyutuna göre uygun algoritmalar zaten uygulanmıştır
Raman Yelianevich

Bu bir Java sorusu DEĞİLDİR!
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);
}

Oyumu değil, pow(1, -1)negatif bir üsse rağmen int aralığını bırakmıyor. Şimdi biri de kazara çalışıyor pow(-1, -1).
MSalters

İnt aralığını terk etmeyebilecek tek negatif üs -1'tir. Ve sadece taban 1 veya -1 ise çalışır. Yani exp <0 ile tamsayı olmayan güçlere yol açmayacak sadece iki çift (base, exp) vardır. Ben bir matematikçi olmasına ve nicelik belirleyicileri sevmeme rağmen, bu durumda, pratikte, negatif bir
üsün

6

Bir şeyin gücüne yükseltilmiş 2 için bir tamsayı değerini almak istiyorsanız, shift seçeneğini kullanmak her zaman daha iyidir:

pow(2,5) ile değiştirilebilir 1<<5

Bu çok daha verimlidir.


6

power()işlevi yalnızca Tamsayılar için çalışma

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;

}

Karmaşıklık = O (log (exp))

power()negatif exp ve float base için işlev .

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 
    }

} 

Karmaşıklık = O (log (exp))


Bu Abhijit Gaikwad ve chux'un cevaplarından nasıl farklı ? Lütfen floatsunulan ikinci kod bloğunda kullanımını tartışın (nasıl power(2.0, -3)hesaplandığını göstermeyi düşünün ).
greybeard

@greybeard Bazı yorumlardan bahsettim. sorgunuzu çözebilir
roottraveller

1
GNU Bilimsel Kütüphane zaten ikinci bir işleve sahiptir: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller lütfen negative exp and float baseçözümü açıklar mısınız? neden temp kullanıyoruz, exp'yi 2 ile ayırıyoruz ve exp'yi (çift / tek) kontrol ediyoruz? Teşekkürler!
Lev

6

Son derece özel bir durum, 2 ^ (- y'den x'e) demeniz gerektiğinde, burada x, elbette negatiftir ve y, bir int üzerinde kaydırma yapmak için çok büyüktür. Bir şamandıra ile vidalayarak sabit zamanda 2 ^ x yapabilirsiniz.

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

Temel tip olarak bir çift kullanarak 2'den fazla güç elde edebilirsiniz. (Bu gönderiyi kaldırmaya yardımcı oldukları için yorum yapanlara çok teşekkür ederiz).

Ayrıca, IEEE şamandıraları hakkında daha fazla bilgi edinmenin , diğer özel üstelleşme vakalarının kendilerini gösterme olasılığı da vardır.


Şık çözüm, ama affedilmez ??
paxdiablo

Bir IEEE şamandıra tabanı x 2 ^ exp'dir, üs değerinin değiştirilmesi iki güçle çarpma işleminden başka bir şeye yol
açmaz

Hepiniz haklısınız, çözümümün orijinal olarak, çok uzun zaman önce, açıkça 2 kişilik güçler için yazılmış olduğunu yanlış hatırladım. Yanıtımı soruna özel bir durum çözümü olarak yeniden yazdım.
Doug T.

İlk olarak, kod alıntılandığı gibi kesilir ve derlenmesi için düzenleme yapılması gerekir. İkinci olarak, kod gcc kullanılarak bir core2d'de bozulur. bkz bu dökümü Belki bir şey yanlış yaptı. Bununla birlikte, IEEE şamandıra üssü taban 10 olduğu için bunun işe yarayacağını düşünmüyorum
freespace

3
Temel 10? Hayır, ikili 10 demek istemediğiniz sürece bu temel 2 :)
Drealmer

4

Tıpkı karelemeyle üstelimin etkinliği üzerine yapılan yorumları takip etmek gibi.

Bu yaklaşımın avantajı, log (n) zamanında çalışmasıdır. Örneğin, x ^ 1048575 (2 ^ 20 - 1) gibi büyük bir şey hesaplayacak olsaydınız, saf yaklaşımı kullanarak döngüden sadece 20 kez geçmelisiniz, 1 milyondan fazla değil.

Ayrıca, kod karmaşıklığı açısından, la Pramod'un önerisi olan en uygun çarpma dizisini bulmaya çalışmaktan daha kolaydır.

Düzenle:

Sanırım biri beni taşma potansiyeli için etiketlemeden önce açıklığa kavuşturmalıyım. Bu yaklaşım bir tür dev kitaplığa sahip olduğunuzu varsayar.


2

Partiye geç:

Aşağıda, y < 0olabildiğince iyi olan bir çözüm bulunmaktadır .

  1. intmax_tMaksimum aralık için sonucunu kullanır . Uymayan cevaplar için bir hüküm yoktur intmax_t.
  2. powjii(0, 0) --> 1Bu da bu durum için ortak bir sonuçtur .
  3. pow(0,negative), tanımlanmamış başka bir sonuç döndürür 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;
    }

Bu kod, diğer ilmekli çözümlerde for(;;)son base *= baseortak önlemek için sonsuza kadar bir döngü kullanır . Bu çarpma 1) gerekli değildir ve 2) int*intUB olan taşma olabilir .


powjii(INT_MAX, 63)UB neden olur base *= base. Çoğaltabildiğinizi veya imzasıza geçip gidemeyeceğinizi kontrol edin ve etrafta sarın.
Cacahuete Frito

expİmzalanmak için bir neden yok . (-1) ** (-N)Geçerli olduğu garip durum nedeniyle kodu karmaşıklaştırır ve negatif değerleri için herhangi biri abs(base) > 1olacaktır , bu nedenle imzasız olması ve bu kodu kaydetmesi daha iyidir. 0exp
Cacahuete Frito

1
@CacahueteFrito İmzalı olanın ygerçekten gerekli olmadığını ve yorumladığınız komplikasyonları getirdiğini, ancak OP'nin talebinin spesifik olduğunu doğrulayın pow(int, int). Dolayısıyla bu iyi yorumlar OP'nin sorusuna aittir. OP taşma konusunda ne yapılacağını belirtmediğinden, iyi tanımlanmış bir yanlış cevap UB'den çok az daha iyidir. "En verimli yolu" göz önüne alındığında, OP OF OF umurunda şüpheliyim.
chux - Monica

1

negatif eksponenet göz önüne alındığında daha genel bir çözüm

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
tamsayı bölümü bir tamsayı ile sonuçlanır, bu nedenle negatif
üssünüz

pow(i, INT_MIN)Sonsuz bir döngü olabilir.
chux - Monica'yı geri döndür

1
@chux: Sabit diskinizi biçimlendirebilir: tamsayı taşması UB'dir.
MSalters

@ MSalters pow(i, INT_MIN)tamsayı taşması değil. Bu sonucun atanması tempkesinlikle taşabilir, bu da zamanın sonuna neden olabilir , ancak görünüşte rastgele bir değere razı olacağım. :-)
chux - Monica

0

Bir uygulama daha (Java'da). En verimli çözüm olmayabilir, ancak yineleme sayısı Üstel çözümle aynıdır.

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

Java sorusu değil!
Cacahuete Frito

0

Ben özyinelemeli, exp bile, 5 ^ 10 = 25 ^ 5 kullanın.

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

İşaretli tamsayılarla uygulandığında Tanımsız Davranışa ve işaretsiz tamsayılarla uygulandığında yüksek girdi için yanlış değerlere neden olan Elias tarafından verilen cevaba ek olarak,

imzalı tam sayı türleriyle de çalışan ve yanlış değerler vermeyen Squaring ile Exponentiation'un değiştirilmiş bir sürümü:

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

Bu işlev için dikkat edilmesi gerekenler:

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

Herhangi bir taşma veya sarma gerçekleşecekse, return 0;

Kullandım int64_t, ancak herhangi bir genişlik (imzalı veya imzasız) küçük bir değişiklikle kullanılabilir. Bununla birlikte, bir sabit-olmayan genişlikte bir tamsayı türü kullanmak gerekir, eğer değiştirmek gerekir SQRT_INT64_MAXile (int)sqrt(INT_MAX)(kullanıldığı durumda intya da optimize edilmelidir benzer bir), ancak bir Cı sabit ifade çirkin bir olup. Ayrıca mükemmel bir kare durumunda kayan nokta hassasiyeti nedeniyle sqrt()bir sonucun dökülmesi intçok iyi değildir, ancak INT_MAX-veya herhangi bir türün maksimumunun - mükemmel bir kare olduğu herhangi bir uygulama bilmiyorum. Bununla.


0

Tüm hesaplanmış güçleri ezberleyen ve sonra gerektiğinde bunları kullanan algoritma uyguladım. Örneğin x ^ 13 eşittir (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x; burada x ^ 2 ^ 2 bir kez daha hesaplamak yerine tablodan alınmıştır. Bu temelde @Pramod cevabının uygulanmasıdır (ancak C # 'da). Gerekli çarpma sayısı Tavan'dır (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? Aynı adı taşıyan 2 işlev? Bu bir C sorusu.
Cacahuete Frito

-1

Benim durumum biraz farklı, bir güçten maske oluşturmaya çalışıyorum, ama yine de bulduğum çözümü paylaşacağımı düşündüm.

Açıkçası, sadece 2 güç için çalışır.

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

Bunu denedim, 64 bit için çalışmıyor, asla geri dönmeyecek şekilde kaydırıldı ve bu özel durumda, X'ten daha düşük tüm bitleri ayarlamaya çalışıyorum.
MarcusJ

1 << 64 için miydi? Bu bir taşma. En büyük tam sayı bunun hemen altındadır: (1 << 64) - 1.
Michaël Roy

1 << 64 == 0, bu yüzden. Belki temsiliniz uygulamanız için en iyisidir. #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1))Derleme zamanında hesaplanabilecek ekstra bir değişken olmadan bir makroya yerleştirilebilecek şeyleri tercih ederim
Michaël Roy

Evet, taşmanın ne olduğunu biliyorum. Bu kelimeyi kullanmadığım için gereksiz yere küçümseyecek bir davet değil. Dediğim gibi, bu benim için çalışıyor ve bu nedenle paylaşmak için biraz çaba aldı. Bu kadar basit.
MarcusJ

Seni kırdıysam üzgünüm. Gerçekten demek istemedim.
Michaël Roy

-1

Derleme zamanında üs (ve bir tam sayı) olduğunu biliyorsanız, döngüyü açmak için şablonları kullanabilirsiniz. Bu daha verimli hale getirilebilir, ancak burada temel prensibi göstermek istedim:

#include <iostream>

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

Şablon uzmanlığı kullanarak özyinelemeyi sonlandırıyoruz:

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

Üssün çalışma zamanında bilinmesi gerekir,

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

1
Bu açıkça bir C ++ sorusu değildir. (c != c++) == 1
Cacahuete Frito
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.