Наиболее эффективный способ реализации целочисленной степенной функции pow (int, int)

214

Каков наиболее эффективный способ поднять целое число до степени другого целого числа в C?

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

// 5^5
pow(5,5) == 3125
  • 3
    Когда вы говорите «эффективность», вам нужно указать эффективность в отношении чего. Скорость? Использование памяти? Размер кода? Ремонтопригодность?
  • 0
    Разве в C нет функции pow ()?
Показать ещё 4 комментария
Теги:
algorithm
math
exponentiation

18 ответов

348
Лучший ответ

Экспоненциальное возведение в квадрат.

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

    return result;
}

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

  • 0
    Возможно, в особых случаях для небольших показателей характерны лучшие алгоритмы: выясняется, что спрашивающий в любом случае не ожидает переполнения, поэтому они, вероятно, распространены.
  • 0
    AKA "быстрый алгоритм возведения в степень".
Показать ещё 15 комментариев
63

Обратите внимание, что возвышение по квадрату - не самый оптимальный метод. Вероятно, это лучшее, что вы можете сделать в качестве общего метода, который работает для всех значений экспоненты, но для определенного значения экспоненты может быть улучшена последовательность, которая требует меньше умножений.

Например, если вы хотите вычислить x ^ 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

Нет эффективных алгоритмов для поиска этой оптимальной последовательности умножений. Из Wikipedia:

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

  • 4
    @JeremySalwen: Как говорится в этом ответе, двоичное возведение в степень не является в общем случае наиболее оптимальным методом. В настоящее время не существует эффективных алгоритмов для нахождения минимальной последовательности умножений.
  • 2
    @EricPostpischil, это зависит от вашего приложения. Обычно нам не нужен общий алгоритм для работы со всеми числами. См. Искусство компьютерного программирования, том. 2: Получисленные алгоритмы
Показать ещё 3 комментария
17

Если вам нужно поднять 2 до мощности. Самый быстрый способ сделать это - сдвиг бит по мощности.

2 ** 3 == 1 << 3 == 8
2 ** 30 == 1 << 30 == 1073741824 (A Gigabyte)
  • 0
    Есть ли элегантный способ сделать это так, чтобы 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;
}
  • 0
    не работает для больших онемений, например, Pow (71045970,41535484)
  • 14
    @AnushreeAcharjee конечно нет. Вычисление такого числа потребовало бы арифметики произвольной точности.
Показать ещё 2 комментария
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);
}
  • 0
    Не мой голос, но pow(1, -1) не покидает диапазон int, несмотря на отрицательный показатель. Теперь это работает случайно, как и pow(-1, -1) .
  • 0
    Единственный отрицательный показатель, который не может заставить вас покинуть диапазон int, равен -1. И это работает только если база 1 или -1. Таким образом, есть только две пары (base, exp) с exp <0, которые не приводят к нецелым степеням. Хотя я математик и мне нравятся квантификаторы, я думаю, что в этом случае на практике можно сказать, что отрицательный показатель заставляет вас покинуть целочисленную область ...
6

Если вы хотите получить значение целого числа для 2, поднятого до степени чего-то, всегда лучше использовать опцию shift:

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

Это намного эффективнее.

  • 2
    да, но это работает только на 2 или кратных 2.
6

Чрезвычайно специализированный случай: когда вам нужно сказать, что 2 ^ (- x для y), где x, конечно, отрицательно, а y слишком велико, чтобы делать сдвиг по int. Вы все равно можете сделать 2 ^ x в течение постоянного времени, вставив поплавок.

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 float, могут появиться другие особые случаи возведения в степень.

  • 0
    Отличное решение, но unigend ??
  • 0
    Да, мой плохой. :) спасибо за указание на это.
Показать ещё 10 комментариев
4

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 (log (exp))

power() работает для отрицательной exp и базы данных.

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 (log (exp))

  • 0
    Чем это отличается от ответов Abhijit Gaikwad и Chux ? Пожалуйста, расскажите, как использовать float во втором представленном кодовом блоке (рассмотрим, как вычисляется power(2.0, -3) ).
  • 0
    @greybeard Я упомянул некоторые комментарии. может быть, что может решить ваш запрос
Показать ещё 1 комментарий
4

Также как комментарий к эффективности возведения в степень возведения в квадрат.

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

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

Edit:

Думаю, я должен уточнить, прежде чем кто-то запишет меня за возможность переполнения. Этот подход предполагает, что у вас есть какая-то огромная библиотека.

2

Поздно участнику:

Ниже приведено решение, которое также имеет дело с y < 0 как можно лучше.

  • Он использует результат intmax_t для максимального диапазона. Не существует условий для ответов, которые не соответствуют intmax_t.
  • powjii(0, 0) --> 1, который является общим результатом для этого случая.
  • pow(0,negative), другой результат undefined возвращает 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.

  • 0
    powjii(INT_MAX, 63) вызывает UB в base *= base . Подумайте о том, чтобы проверить, что вы можете умножить или перейти к беззнаковому и позволить ему обернуться.
  • 0
    Нет причин подписывать exp . Это усложняет код из-за нечетной ситуации, когда (-1) ** (-N) является допустимым, и любой abs(base) > 1 будет 0 для отрицательных значений exp , поэтому лучше иметь его без знака и сохранить этот код.
Показать ещё 1 комментарий
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 ...
  • 0
    pow(i, INT_MIN) может быть бесконечным циклом.
Показать ещё 2 комментария
0

В дополнение к ответу Элиаса, который приводит к неопределенному поведению при реализации с целыми числами со знаком и к неправильным значениям для высокого ввода при реализации с целыми числами без знака,

Вот модифицированная версия 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, но любую ширину (со int64_t или без знака) можно использовать с небольшими изменениями. Однако, если вам нужно использовать целочисленный тип не фиксированной ширины, вам нужно изменить SQRT_INT64_MAX на (int)sqrt(INT_MAX) (в случае использования int) или что-то подобное, что должно быть оптимизировано, но это уродливее, а не константное выражение C. Кроме того, приведение результата sqrt() к типу int не очень хорошо из-за точности с плавающей запятой в случае идеального квадрата, но, как я не знаю ни одной реализации, где INT_MAX -or максимум любого type- равен идеальный квадрат, вы можете жить с этим.

0

Мое дело немного другое, я пытаюсь создать маску от власти, но я думал, что поделюсь решением, которое я нашел в любом случае.

Очевидно, что он работает только для степеней 2.

Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;
  • 0
    Это немного надумано .... почему бы не привести = (1 << Exponent) - 1; ?
  • 0
    Я попробовал это, он не работает для 64 бит, он сдвинут, чтобы никогда не возвращаться, и в этом конкретном случае я пытаюсь установить все биты ниже X включительно.
Показать ещё 9 комментариев
0

Я реализовал алгоритм, который запоминает все вычисленные мощности, а затем использует их при необходимости. Так, например, x ^ 13 равно (x ^ 2) ^ 2 ^ 2 * x ^ 2 ^ 2 * x, где x ^ 2 ^ 2 это взято из таблицы вместо того, чтобы вычислять это снова. Это в основном реализация ответа @Pramod (но в С#). Необходимое количество умножения: 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);
}
  • 0
    public ? 2 функции названы одинаково? Это вопрос C.
0

Я использую рекурсивный, если exp четный, 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

Еще одна реализация (в Java). Не может быть наиболее эффективным решением, но количество итераций аналогично решению Exponential.

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;
    }       
}
  • 0
    Не вопрос Java!
-2

Если вы знаете показатель экспоненты (и это целое число) во время компиляции, вы можете использовать шаблоны для разворачивания цикла. Это можно сделать более эффективным, но я хотел продемонстрировать основной принцип здесь:

#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;
}
  • 0
    Это явно не вопрос C ++. (c != c++) == 1
-4

Игнорируя особый случай 2, поднятый до мощности, наиболее эффективным способом будет простая итерация.

int pow(int base, int pow) {
  int res = 1;
  for(int i=pow; i<pow; i++)
    res *= base;

  return res;
}

EDIT: Как было указано, это не самый эффективный способ... пока вы определяете эффективность как циклы процессора, которые, как я думаю, достаточно справедливы.

  • 2
    O (N), где O (log N) возможно - см. Яррков
  • 1
    Это на самом деле может быть наиболее эффективным. N не может быть сколь угодно большим. Его максимум составляет 31 или 63 (в зависимости от вашего размера int). Это похоже на то, как сортировка вставки бьет быструю сортировку для низкого N.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню