Найбільш ефективний спосіб реалізувати функцію живлення на основі цілого числа pow (int, int)


249

Який найефективніший спосіб дати ціле число до сили іншого цілого числа в C?

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

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

3
Коли ви говорите "ефективність", вам потрібно вказати ефективність щодо того, що. Швидкість? Використання пам'яті? Розмір коду? Технічне обслуговування?
Енді Лестер

Чи не має функція pow ()?
jalf

16
так, але це працює на поплавках чи
дублях

1
Якщо ви дотримуєтесь фактичного ints (а не якогось величезного класу), багато дзвінків до ipow переповняться. Мене змушує замислитися, чи існує розумний спосіб попередньо обчислити таблицю і зменшити всі непереливні комбінації до простого пошуку таблиці. Це зайняло б більше пам’яті, ніж більшість загальних відповідей, але, можливо, було б більш ефективним щодо швидкості.
Адріан Маккарті

pow()не безпечна функція
EsmaeelE

Відповіді:


391

Експоненція шляхом квадратування.

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

    return result;
}

Це стандартний метод проведення модульної експоненції для величезних чисел в асиметричній криптографії.


38
Ймовірно, слід додати чек, що "exp" не є негативним. В даний час ця функція назавжди дасть неправильну відповідь або цикл. (Залежно від того, чи >> = на підписаному int робить нульове заміщення або розширення знаків - компілятори C можуть вибирати будь-яку поведінку).
user9876

23
Я написав більш оптимізовану версію цього, вільно завантажувану тут: gist.github.com/3551590 На моїй машині це було приблизно в 2,5 рази швидше.
orlp

10
@AkhilJain: Це абсолютно добре C; щоб зробити його дійсним і в Java, замінити while (exp)і if (exp & 1)з while (exp != 0)і if ((exp & 1) != 0)відповідно.
Ільмарі Каронен

3
Ваша функція, ймовірно, повинна мати unsigned exp, інакше expналежним чином обробляти негативні .
Крейг МакКуін

5
@ZinanXing Помноження в n разів призводить до більшого множення і відбувається повільніше. Цей спосіб економить множення, ефективно використовуючи їх. Наприклад, для обчислення n ^ 8 наивний метод n*n*n*n*n*n*n*nвикористовує 7 множень. Цей алгоритм замість цього обчислює m=n*n, тоді o=m*m, p=o*oде p= n ^ 8, з трьома множеннями. При великих показниках різниця в продуктивності значна.
bames53

69

Зауважимо, що експоненціація шляхом квадратування - не найоптимальніший метод. Це, мабуть, найкраще, що ви можете зробити як загальний метод, який працює для всіх значень експонента, але для конкретного значення експонента може бути краща послідовність, яка потребує меншої множення.

Наприклад, якщо ви хочете обчислити х ^ 15, метод експоненції шляхом квадратування дасть вам:

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

Це загалом 6 множень.

Виявляється, це можна зробити, використовуючи «просто» 5 множення за допомогою експоненції ланцюга додавання .

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

Не існує ефективних алгоритмів, щоб знайти цю оптимальну послідовність множень. З Вікіпедії :

Проблема пошуку найкоротшого ланцюга додавання не може бути вирішена динамічним програмуванням, оскільки воно не задовольняє припущення про оптимальну підструктуру. Тобто недостатньо розподілити владу на менші потужності, кожна з яких обчислюється мінімально, оскільки ланцюги додавання для менших потужностей можуть бути пов'язані (для обміну обчисленнями). Наприклад, у найкоротшому ланцюжку додавання для a¹⁵ вище, підпроблема для a⁶ повинна бути обчислена як (a³) ², оскільки a³ повторно використовується (на відміну від, скажімо, a⁶ = a² (a²) ², що також вимагає трьох множень ).


4
@JeremySalwen: Як зазначено у цій відповіді, бінарне експоненцію взагалі не є найбільш оптимальним методом. В даний час не існує ефективних алгоритмів для пошуку мінімальної послідовності множень.
Eric Postpischil

2
@EricPostpischil, це залежить від вашої заявки. Зазвичай нам не потрібен загальний алгоритм для роботи всіх чисел. Див. Мистецтво комп’ютерного програмування, Vol. 2: Напів числові алгоритми
Pacerier

3
Є гарна картина цієї точної проблеми в « Математиці до загального програмування » Олександра Степанова та Даніеля Роуза. Ця книга повинна бути на полиці кожного практикуючого програмного забезпечення IMHO.
Toby Speight

2
Дивіться також en.wikipedia.org/wiki / ... .
lhf

Це може бути оптимізовано для цілих чисел, оскільки існує значно менше 255 цілих потужностей, що не спричинить переповнення 32-бітових цілих чисел. Ви можете кешувати оптимальну структуру множення для кожного int. Я думаю, що код + даних все одно буде менше, ніж просто кешувати всі повноваження ...
Josiah Yoder

22

Якщо вам потрібно підняти 2 до потужності. Найшвидший спосіб зробити це - трохи перемістити потужність.

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

Чи є елегантний спосіб зробити це, щоб 2 ** 0 == 1?
Роб Смоллшир

16
2 ** 0 == 1 << 0 == 1
Джейк

14

Ось метод на 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;
}

не працює для великих нумерів, напр. Pow (71045970,41535484)
Anushree Acharjee

16
@AnushreeAcharjee звичайно ні. Обчислення такого числа вимагало б арифметики довільної точності.
Девід Етлер

Використовуйте BigInteger # modPow або Biginteger # pow для великих чисел, відповідні алгоритми, засновані на розмірі аргументів, вже реалізовані
Раман Єліаневич

Це НЕ питання 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);
}

Не мій голос, але pow(1, -1)не залишає діапазону int, незважаючи на негативний показник. Тепер, коли один працює випадково, як і він pow(-1, -1).
MSalters

Єдиний негативний показник, який може не змусити вас залишити діапазон int - -1. І він працює лише в тому випадку, якщо база дорівнює 1 або -1. Отже, є лише дві пари (база, exp) з exp <0, що не призведе до не цілих повноважень. Хоча я математик і мені подобаються квантори, я думаю, що в цьому випадку на практиці нормально сказати, що негативний показник змушує вас залишити цілу область ...
bartgol

6

Якщо ви хочете отримати значення цілого числа для 2, підняте на потужність чогось, завжди краще скористатися параметром shift:

pow(2,5) можна замінити на 1<<5

Це набагато ефективніше.


6

power()функціонувати лише для цілих чисел

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;

}

Складність = O (журнал (exp))

power()функція для роботи для негативного досвіду exp та 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 
    }

} 

Складність = O (журнал (exp))


Чим це відрізняється від відповідей Абхіджіт Гайквад і чу ? Будь ласка, аргументуйте використання floatу представленому другому блоці коду (розгляньте, як показувати, як power(2.0, -3)отримується обчислення).
сіра борода

@greybeard Я згадав якийсь коментар. можливо, це може вирішити ваш запит
roottraveller

1
Наукова бібліотека GNU вже має вашу другу функцію: gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito

@roottraveller, чи можете ви поясніть negative exp and float baseрішення? чому ми використовуємо temp, розділимо exp на 2 та перевіримо exp (парний / непарний)? Дякую!
Лев

6

Надзвичайно спеціалізований випадок, коли вам потрібно сказати 2 ^ (- x to y), де x, звичайно, є негативним, а y занадто великим, щоб зробити зрушення на int. Ще можна робити 2 ^ х у постійному часі, прикручуючи поплавком.

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

Ви можете отримати більше повноважень 2, використовуючи подвійний як базовий тип. (Велике спасибі коментаторам за те, що вони допомогли розмістити цю публікацію).

Існує також можливість, що дізнавшись більше про IEEE поплавці , інші особливі випадки експоненції можуть представити себе.


Вишуканий розчин, але нерізноманітний ??
paxdiablo

Поплавок IEEE - це база x 2 ^ exp, зміна значення експонента не призведе до нічого іншого, ніж до множення на потужність двох, і швидше за все, це денормалізує поплавок ... ваше рішення неправильне IMHO
Drealmer

Ви все правильно, я згадав, що моє рішення спочатку було написано, так давно, для повноважень 2. Я переписав свою відповідь, щоб вирішити проблему в спеціальному випадку.
Дуг Т.

По-перше, код порушується як цитується, і він потребує редагування, щоб його скласти. По-друге, код розбивається на core2d за допомогою gcc. бачити цей смітник Можливо, я зробив щось не так. Однак я не думаю, що це буде спрацьовувати, оскільки показник поплавця IEEE є базовим 10.
простір

3
База 10? Ні, це база 2, якщо ви не мали на увазі 10 у двійковій
мові

4

Так само, як продовження коментарів щодо ефективності експоненції шляхом квадратування.

Перевага такого підходу полягає в тому, що він працює в журнал (n) час. Наприклад, якщо ви збиралися обчислити щось величезне, наприклад, x ^ 1048575 (2 ^ 20 - 1), вам слід пройти цикл лише 20 разів, а не 1 мільйон +, використовуючи наївний підхід.

Крім того, з точки зору складності коду, це простіше, ніж намагатися знайти найоптимальнішу послідовність множень, пропозицію a la Pramod.

Редагувати:

Я думаю, я повинен уточнити, перш ніж хтось позначає мене на потенціал переповнення. Цей підхід передбачає, що у вас є якась бібліотека величезних кольорів.


2

Пізно до вечірки:

Нижче наведено рішення, яке також розглядає y < 0якнайкраще.

  1. Він використовує результат intmax_tдля максимального діапазону. Немає положень для відповідей, які не підходять intmax_t.
  2. powjii(0, 0) --> 1що є загальним результатом для даного випадку.
  3. pow(0,negative), повертається ще один невизначений результат 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;
    }

Цей код використовує вічний цикл, for(;;)щоб уникнути остаточного base *= baseпоширеного в інших певних рішеннях. Це множення 1) не потрібно і 2) може бути int*intпереповнене, що є UB.


powjii(INT_MAX, 63)викликає UB в base *= base. Подумайте про те, чи можна розмножуватись, або переходити до непідписаного та нехай він обертається.
Cacahuete Frito

Підстав для expпідписання немає. Він ускладнює код через нечетну ситуацію, де (-1) ** (-N)є дійсним, і будь-який abs(base) > 1буде мати 0негативні значення exp, тому краще мати його без підпису і зберегти цей код.
Cacahuete Frito

1
@CacahueteFrito Щоправда, yпідписання насправді не потрібне та спричинює ускладнення, які ви прокоментували, проте запит ОП було конкретним pow(int, int). Таким чином, ці хороші коментарі належать до питання ОП. Оскільки ОП не уточнив, що робити при переповненні, чітко визначена неправильна відповідь лише незначно краща, ніж UB. Враховуючи "найефективніший спосіб", я сумніваюся, що ОП піклується про ОФ.
chux

1

більш загальне рішення з огляду на негативний показник

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
ціле ділення призводить до цілого числа, тому ваш негативний показник може бути набагато ефективнішим, оскільки він поверне лише 0, 1 або -1 ...
jswolf19

pow(i, INT_MIN)може бути нескінченною петлею.
chux

1
@chux: Це може відформатувати ваш жорсткий диск: переповнення цілого числа - це UB.
MSalters

@MSalters pow(i, INT_MIN)не є цілим числом переповнення. Присвоєння цього результату, tempбезумовно, може переповнитися, що може спричинити закінчення часу , але я погоджуся на, здавалося б, випадкове значення. :-)
chux

0

Ще одна реалізація (на Java). Можливо, це не найефективніше рішення, але # ітерацій таке ж, як і експоненціальне рішення.

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!
Cacahuete Frito

0

Я використовую рекурсивний, якщо досвід є рівним, 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

На додаток до відповіді Elias, яка спричиняє не визначену поведінку при реалізації з підписаними цілими числами, і неправильні значення для високого введення, коли реалізовано з непідписаними цілими числами,

ось модифікована версія Exponentiation by Squaring, яка також працює з підписаними цілими типами і не дає неправильних значень:

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

Розгляд цієї функції:

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

Якщо відбудеться якийсь перелив або обгортання, return 0;

Я використовував int64_t, але будь-яку ширину (з підписом або без підпису) можна використовувати з незначними змінами. Однак, якщо вам необхідно використовувати не фіксовану ширину целочисленного типу, то потрібно змінити SQRT_INT64_MAXшлях (int)sqrt(INT_MAX)(в разі використання int) або щось подібне, які повинні бути оптимізовані, але це потворніше, а не вираз константи С. Також виведення результату sqrt()на intне дуже добре через точність точок з плаваючою точкою у випадку ідеального квадрата, але, як я не знаю жодної реалізації, де INT_MAX- або максимум будь-якого типу - ідеальний квадрат, ви можете жити З цим.


0

Я реалізував алгоритм, який запам'ятовує всі обчислені повноваження, а потім використовує їх у разі потреби. Так, наприклад, x ^ 13 дорівнює (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x, де x ^ 2 ^ 2 він узятий з таблиці, а не обчислювати його ще раз. Це в основному реалізація відповіді @Pramod (але в C #). Кількість потрібного множення - 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 функції названі однаково? Це питання С.
Cacahuete Frito

-1

У моєму випадку трохи інше, я намагаюся створити маску від влади, але я подумав, що все-таки поділюсь рішенням, яке знайшов.

Очевидно, він працює лише для повноважень 2.

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

Я спробував це, він не працює для 64 біт, він відхиляється ніколи не повертається, і в цьому конкретному випадку я намагаюся встановити всі біти нижче X, включно.
MarcusJ

Це було за 1 << 64? Це перелив. Найбільше ціле число трохи нижче цього: (1 << 64) - 1.
Michaël Roy

1 << 64 == 0, тому. Можливо, ваше представлення найкраще підходить для вашої програми. Я віддаю перевагу речам, які можна помістити в макрос, без зайвої змінної, наприклад #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)), щоб їх можна було обчислити під час компіляції
Michaël Roy

Так, я знаю, що таке перелив. Тільки тому, що я не використовував це слово, це не запрошення бути поблажливим. Як я вже сказав, це працює для мене, і знадобилося трохи зусиль, щоб відкрити для цього спільний доступ. Це так просто.
MarcusJ

Вибачте, якщо я образив вас. Я справді цього не мав на увазі.
Michaël Roy

-1

Якщо ви знаєте експонент (а це ціле число) під час компіляції, ви можете використовувати шаблони для розгортання циклу. Це можна зробити більш ефективним, але я хотів продемонструвати тут основний принцип:

#include <iostream>

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

Ми припиняємо рекурсію за допомогою спеціалізації шаблону:

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

Експонент повинен бути відомий під час виконання,

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

1
Очевидно, це не питання С ++. (c != c++) == 1
Cacahuete Frito
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.