Cách hiệu quả nhất để thực hiện hàm pow dựa trên số nguyên (int, int)


249

Cách hiệu quả nhất được đưa ra để nâng một số nguyên lên sức mạnh của một số nguyên khác trong C là gì?

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

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

3
Khi bạn nói "hiệu quả", bạn cần chỉ định hiệu quả liên quan đến những gì. Tốc độ? Sử dụng bộ nhớ? Kích thước mã? Bảo trì?
Andy Lester

C không có hàm pow () à?
jalf

16
đúng, nhưng nó hoạt động trên phao hoặc đôi, không phải trên ints
Nathan Fellman

1
Nếu bạn đang gắn bó với ints thực tế (và không phải là một lớp int-int lớn), rất nhiều cuộc gọi đến ipow sẽ tràn ra. Nó khiến tôi tự hỏi liệu có một cách thông minh để tính toán trước một bảng và giảm tất cả các kết hợp không tràn vào một bảng tra cứu đơn giản. Điều này sẽ chiếm nhiều bộ nhớ hơn hầu hết các câu trả lời chung, nhưng có lẽ hiệu quả hơn về tốc độ.
Adrian McCarthy

pow()không phải là một chức năng an toàn
EsmaeelE

Câu trả lời:


391

Lũy thừa bằng bình phương.

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

    return result;
}

Đây là phương pháp tiêu chuẩn để thực hiện lũy thừa mô-đun cho số lượng lớn trong mật mã bất đối xứng.


38
Bạn có thể nên thêm một kiểm tra rằng "exp" không âm. Hiện tại, chức năng này sẽ cung cấp một câu trả lời sai hoặc vòng lặp mãi mãi. (Tùy thuộc vào việc >> = trên một int đã ký không đệm hay mở rộng ký - Trình biên dịch C được phép chọn một trong hai hành vi).
dùng9876

23
Tôi đã viết một phiên bản tối ưu hơn về điều này, có thể tải xuống miễn phí tại đây: gist.github.com/3551590 Trên máy của tôi, nó nhanh hơn khoảng 2,5 lần.
orlp

10
@AkhilJain: Nó hoàn toàn tốt C; để làm cho nó hợp lệ trong Java, thay thế while (exp)if (exp & 1)bằng while (exp != 0)if ((exp & 1) != 0)tương ứng.
Ilmari Karonen

3
Chức năng của bạn có lẽ nên có unsigned exp, nếu không thì xử lý tiêu cực expđúng cách.
Craig McQueen

5
@ZinanXing Nhân n lần cho kết quả nhân nhiều hơn và chậm hơn. Phương pháp này tiết kiệm phép nhân bằng cách tái sử dụng chúng một cách hiệu quả. Ví dụ, để tính n ^ 8 phương pháp ngây thơ n*n*n*n*n*n*n*nsử dụng 7 phép nhân. Thuật toán này thay vì tính toán m=n*n, sau đó o=m*m, sau đó p=o*o, trong đó p= n ^ 8, chỉ với ba phép nhân. Với số mũ lớn, sự khác biệt về hiệu suất là rất đáng kể.
bames53

68

Lưu ý rằng lũy thừa bằng bình phương không phải là phương pháp tối ưu nhất. Đây có thể là cách tốt nhất bạn có thể làm như một phương pháp chung hoạt động cho tất cả các giá trị số mũ, nhưng đối với một giá trị số mũ cụ thể, có thể có một chuỗi tốt hơn cần ít phép nhân hơn.

Chẳng hạn, nếu bạn muốn tính x ^ 15, phương pháp lũy thừa bằng bình phương sẽ cho bạn:

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

Đây là tổng cộng 6 phép nhân.

Hóa ra điều này có thể được thực hiện bằng cách sử dụng "chỉ" 5 phép nhân thông qua phép lũy thừa chuỗi bổ sung .

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

Không có thuật toán hiệu quả để tìm chuỗi nhân tối ưu này. Từ Wikipedia :

Vấn đề tìm chuỗi bổ sung ngắn nhất không thể được giải quyết bằng lập trình động, bởi vì nó không thỏa mãn giả định về cấu trúc tối ưu. Đó là, không đủ để phân rã sức mạnh thành các sức mạnh nhỏ hơn, mỗi sức mạnh được tính toán tối thiểu, vì các chuỗi bổ sung cho các quyền lực nhỏ hơn có thể có liên quan (để chia sẻ tính toán). Ví dụ: trong chuỗi bổ sung ngắn nhất cho a¹⁵ ở trên, bài toán con cho a⁶ phải được tính là (a³) ² vì a³ được sử dụng lại (trái ngược với, a = a² (a²) ², cũng cần ba bội số ).


4
@JeremySalwen: Như câu trả lời này nêu rõ, lũy thừa nhị phân nói chung không phải là phương pháp tối ưu nhất. Hiện tại không có thuật toán hiệu quả nào được biết đến với việc tìm ra chuỗi nhân tối thiểu.
Eric Postpischil 27/12/13

2
@EricPostpischil, Điều đó phụ thuộc vào ứng dụng của bạn. Thông thường chúng ta không cần một thuật toán chung để làm việc cho tất cả các số. Xem Nghệ thuật lập trình máy tính, Tập. 2: Thuật toán chuyên ngành
Pacerier

3
Có một giải thích tốt về vấn đề chính xác này trong Từ Toán học đến Lập trình Chung của Alexander Stepanov và Daniel Rose. Cuốn sách này nên có trên kệ của mọi học viên phần mềm, IMHO.
Toby Speight

2
lhf

Điều này có thể được tối ưu hóa cho số nguyên vì có dưới 255 số nguyên sẽ không gây ra tràn cho số nguyên 32 bit. Bạn có thể lưu trữ cấu trúc nhân tối ưu cho mỗi int. Tôi tưởng tượng mã + dữ liệu sẽ vẫn nhỏ hơn đơn giản là lưu trữ tất cả các quyền hạn ...
Josiah Yoder

22

Nếu bạn cần nâng 2 lên một sức mạnh. Cách nhanh nhất để làm như vậy là thay đổi bit theo sức mạnh.

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

Có một cách thanh lịch để làm điều này sao cho 2 ** 0 == 1?
Rob Smallshire

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

14

Đây là phương thức trong 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;
}

không hoạt động cho tê lớn, ví dụ như pow (71045970,41535484)
Anushree Acharjee

16
@AnushreeAcharjee tất nhiên là không. Việc tính toán một số như vậy sẽ đòi hỏi số học chính xác tùy ý.
David Etler

Sử dụng BigInteger # modPow hoặc BigInteger # pow cho số lượng lớn, các thuật toán thích hợp dựa trên kích thước của các đối số đã được thực hiện
Raman Yelianevich

Đây KHÔNG phải là một câu hỏi 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);
}

Không phải phiếu bầu của tôi, nhưng pow(1, -1)không rời khỏi phạm vi int mặc dù có số mũ âm. Bây giờ một người làm việc tình cờ, cũng như pow(-1, -1).
MSalters

Số mũ âm duy nhất có thể không khiến bạn rời khỏi phạm vi int là -1. Và nó chỉ hoạt động nếu cơ sở là 1 hoặc -1. Vì vậy, chỉ có hai cặp (cơ sở, exp) với exp <0 sẽ không dẫn đến quyền hạn không nguyên. Mặc dù tôi là một người theo chủ nghĩa định lượng và tôi thích các bộ lượng hóa, nhưng tôi nghĩ trong trường hợp này, trên thực tế, có thể nói rằng một số mũ âm khiến bạn rời khỏi cõi số nguyên ...
bartgol

6

Nếu bạn muốn lấy giá trị của một số nguyên cho 2 tăng lên sức mạnh của thứ gì đó thì tốt hơn là sử dụng tùy chọn thay đổi:

pow(2,5) có thể được thay thế bởi 1<<5

Điều này là hiệu quả hơn nhiều.


6

power()Chức năng chỉ hoạt động cho Số nguyên

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;

}

Độ phức tạp = O (log (exp))

power()Chức năng làm việc cho exp âm và cơ sở float .

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 
    }

} 

Độ phức tạp = O (log (exp))


Điều này khác với câu trả lời của Abhijit Gaikwadchux như thế nào? Vui lòng tranh luận việc sử dụng floattrong khối mã thứ hai được trình bày (xem xét hiển thị cách power(2.0, -3)tính toán).
greybeard

@greybeard Mình đã đề cập đến một số bình luận. có thể điều đó có thể giải quyết truy vấn của bạn
roottraveller

1
Thư viện khoa học GNU đã có chức năng của bạn thứ hai: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller bạn có thể vui lòng giải thích giải negative exp and float basepháp? tại sao chúng tôi sử dụng temp, tách exp bằng 2 và kiểm tra exp (chẵn / lẻ)? cảm ơn!
Lev

6

Một trường hợp cực kỳ đặc biệt là, khi bạn cần nói 2 ^ (- x với y), trong đó x, tất nhiên là âm và y quá lớn để thực hiện dịch chuyển trên một int. Bạn vẫn có thể thực hiện 2 ^ x trong thời gian liên tục bằng cách bắt vít bằng phao.

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

Bạn có thể nhận được nhiều quyền hạn hơn bằng cách sử dụng gấp đôi làm loại cơ sở. (Cảm ơn rất nhiều đến những người bình luận đã giúp bình phương bài này đi).

Ngoài ra còn có khả năng tìm hiểu thêm về phao của IEEE , các trường hợp lũy thừa đặc biệt khác có thể xuất hiện.


Giải pháp tiện lợi, nhưng không mong muốn ??
paxdiablo

Một float của IEEE là cơ sở x 2 ^ exp, việc thay đổi giá trị số mũ sẽ không dẫn đến bất kỳ điều gì khác ngoài phép nhân với hai lũy thừa và rất có thể nó sẽ làm mất chuẩn hóa ... giải pháp của bạn sai IMHO
Drealmer

Tất cả các bạn đều đúng, tôi đã đánh giá sai rằng giải pháp của tôi ban đầu được viết, ồ từ rất lâu rồi, với quyền hạn 2 rõ ràng. Tôi đã viết lại câu trả lời của mình để trở thành một giải pháp cho trường hợp đặc biệt.
Doug T.

Đầu tiên, mã bị hỏng như được trích dẫn và yêu cầu chỉnh sửa để biên dịch nó. Thứ hai, mã bị hỏng trên core2d bằng gcc. thấy bãi rác này Có lẽ tôi đã làm gì đó sai. Tuy nhiên tôi không nghĩ rằng nó sẽ hoạt động, vì số mũ phao của IEEE là cơ sở 10.
freespace

3
Cơ sở 10? À không, đó là cơ sở 2, trừ khi bạn có nghĩa là 10 trong nhị phân :)
Drealmer

4

Cũng như theo dõi để nhận xét về hiệu quả của lũy thừa bằng cách bình phương.

Ưu điểm của phương pháp đó là nó chạy trong thời gian log (n). Ví dụ: nếu bạn định tính toán một cái gì đó rất lớn, chẳng hạn như x ^ 1048575 (2 ^ 20 - 1), bạn chỉ phải thực hiện vòng lặp 20 lần chứ không phải 1 triệu + bằng cách sử dụng phương pháp ngây thơ.

Ngoài ra, về độ phức tạp của mã, đơn giản hơn là cố gắng tìm chuỗi nhân tối ưu nhất, một gợi ý của la Pramod.

Biên tập:

Tôi đoán tôi nên làm rõ trước khi ai đó gắn thẻ cho tôi về khả năng tràn. Cách tiếp cận này giả định rằng bạn có một số loại thư viện khổng lồ.


2

Đi dự tiệc muộn:

Dưới đây là một giải pháp cũng giải quyết y < 0tốt nhất có thể.

  1. Nó sử dụng một kết quả của intmax_tphạm vi tối đa. Không có quy định cho câu trả lời không phù hợp vớiintmax_t .
  2. powjii(0, 0) --> 1đó là một kết quả phổ biến cho trường hợp này.
  3. pow(0,negative), một kết quả không xác định khác, trả về 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;
    }

Mã này sử dụng một vòng lặp mãi mãi for(;;)để tránh điểm chung cuối cùng base *= basetrong các giải pháp vòng lặp khác. Phép nhân đó là 1) không cần thiết và 2) có thể bị int*inttràn đó là UB.


powjii(INT_MAX, 63)gây ra UB trong base *= base. Xem xét việc kiểm tra xem bạn có thể nhân lên, hoặc di chuyển đến không dấu và để cho nó bao quanh.
Cacahuete Frito 17/03/19

Không có lý do để expđược ký kết. Nó làm phức tạp mã vì tình huống kỳ lạ (-1) ** (-N)là hợp lệ và bất kỳ giá trị nào abs(base) > 1sẽ là 0giá trị âm exp, vì vậy tốt hơn là không ký và lưu mã đó.
Cacahuete Frito 17/03/19

1
@CacahueteFrito Đúng là ynhư đã ký không thực sự cần thiết và mang lại những phức tạp mà bạn đã nhận xét, nhưng yêu cầu của OP là cụ thể pow(int, int). Do đó, những bình luận tốt thuộc về câu hỏi của OP. Vì OP chưa chỉ định phải làm gì khi tràn, một câu trả lời sai được xác định rõ chỉ tốt hơn một chút so với UB. Với "cách hiệu quả nhất", tôi nghi ngờ OP quan tâm đến OF.
chux - Tái lập lại

1

giải pháp chung hơn xem xét exponenet tiêu cực

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
phân chia số nguyên dẫn đến một số nguyên, do đó số mũ âm của bạn có thể hiệu quả hơn rất nhiều vì nó sẽ chỉ trả về 0, 1 hoặc -1 ...
jswolf19

pow(i, INT_MIN)có thể là một vòng lặp vô hạn.
chux - Tái lập lại

1
@chux: Nó có thể định dạng ổ cứng của bạn: tràn số nguyên là UB.
MSalters

@MSalters pow(i, INT_MIN)không phải là số nguyên tràn. Việc gán kết quả đó cho tempchắc chắn có thể tràn, tiềm năng gây ra sự kết thúc của thời gian , nhưng tôi sẽ giải quyết cho một giá trị dường như ngẫu nhiên. :-)
chux - Phục hồi Monica

0

Thêm một triển khai (trong Java). Có thể không phải là giải pháp hiệu quả nhất nhưng # lần lặp lại giống như giải pháp theo cấp số nhân.

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

Không phải là một câu hỏi Java!
Cacahuete Frito

0

Tôi sử dụng đệ quy, nếu exp là chẵn, 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

Ngoài câu trả lời của Elias, nguyên nhân gây ra Hành vi không xác định khi được triển khai với số nguyên đã ký và giá trị không chính xác cho đầu vào cao khi được triển khai với số nguyên không dấu,

đây là phiên bản sửa đổi của lũy thừa theo bình phương cũng hoạt động với các kiểu số nguyên đã ký và không đưa ra các giá trị không chính xác:

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

Cân nhắc cho chức năng này:

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

Nếu bất kỳ tràn hoặc gói sẽ diễn ra, return 0;

Tôi đã sử dụng int64_t , nhưng bất kỳ chiều rộng (đã ký hoặc không dấu) có thể được sử dụng với ít sửa đổi. Tuy nhiên, nếu bạn cần sử dụng loại số nguyên có chiều rộng không cố định, bạn sẽ cần thay đổi SQRT_INT64_MAXtheo (int)sqrt(INT_MAX)(trong trường hợp sử dụng int) hoặc một cái gì đó tương tự, cần được tối ưu hóa, nhưng nó xấu hơn và không phải là biểu thức hằng C. Ngoài ra, kết quả của sqrt()một kết quả intlà không tốt lắm vì có dấu phẩy động trong trường hợp một hình vuông hoàn hảo, nhưng như tôi không biết về bất kỳ triển khai nào INT_MAX- hay tối đa của bất kỳ loại nào - là một hình vuông hoàn hảo, bạn có thể sống với.


0

Tôi đã thực hiện thuật toán ghi nhớ tất cả các quyền hạn được tính toán và sau đó sử dụng chúng khi cần. Vì vậy, ví dụ x ^ 13 bằng (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x trong đó x ^ 2 ^ 2 nó được lấy từ bảng thay vì tính toán lại một lần nữa. Điều này về cơ bản là thực hiện câu trả lời @Pramod (nhưng trong C #). Số lượng nhân cần thiết là 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 hàm có tên giống nhau? Đây là một câu hỏi C.
Cacahuete Frito 17/03/19

-1

Trường hợp của tôi hơi khác một chút, tôi đang cố gắng tạo mặt nạ từ một sức mạnh, nhưng tôi nghĩ tôi sẽ chia sẻ giải pháp mà tôi tìm thấy bằng mọi cách.

Rõ ràng, nó chỉ hoạt động cho quyền hạn của 2.

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

Tôi đã thử rằng, nó không hoạt động trong 64 bit, nó đã bị tắt không bao giờ quay trở lại và trong trường hợp cụ thể này, tôi đang cố gắng đặt tất cả các bit thấp hơn X, bao gồm.
MarcusJ

Đó có phải là cho 1 << 64 không? Đó là một tràn. Số nguyên lớn nhất nằm ngay bên dưới: (1 << 64) - 1.
Michaël Roy

1 << 64 == 0, đó là lý do. Có thể đại diện của bạn là tốt nhất cho ứng dụng của bạn. Tôi thích những thứ có thể được đặt trong một macro, không có biến phụ, như #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1))vậy, để có thể được tính toán vào thời gian biên dịch
Michaël Roy

Vâng, tôi biết thế nào là tràn. Chỉ vì tôi đã không sử dụng từ đó không phải là một lời mời không cần thiết phải hạ mình. Như tôi đã nói, điều này làm việc cho tôi và tôi đã mất một chút nỗ lực để khám phá do đó chia sẻ nó. Nó đơn giản mà.
MarcusJ

Tôi xin lỗi nếu tôi đã làm bạn khó chịu. Tôi thực sự không có ý đó.
Michaël Roy

-1

Trong trường hợp bạn biết số mũ (và nó là số nguyên) tại thời gian biên dịch, bạn có thể sử dụng các mẫu để hủy đăng ký vòng lặp. Điều này có thể được thực hiện hiệu quả hơn, nhưng tôi muốn chứng minh nguyên tắc cơ bản ở đây:

#include <iostream>

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

Chúng tôi chấm dứt đệ quy bằng cách sử dụng một chuyên môn mẫu:

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

Số mũ cần được biết trong thời gian chạy,

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

1
Đây rõ ràng không phải là một câu hỏi C ++. (c != c++) == 1
Cacahuete Frito 17/03/19
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.