Каков наиболее эффективный способ поднять целое число до степени другого целого числа в C?
// 2^3
pow(2,3) == 8
// 5^5
pow(5,5) == 3125
Каков наиболее эффективный способ поднять целое число до степени другого целого числа в C?
// 2^3
pow(2,3) == 8
// 5^5
pow(5,5) == 3125
Экспоненциальное возведение в квадрат.
int ipow(int base, int exp)
{
int result = 1;
for (;;)
{
if (exp & 1)
result *= base;
exp >>= 1;
if (!exp)
break;
base *= base;
}
return result;
}
Это стандартный метод для модульного возведения в степень для огромных чисел в асимметричной криптографии.
Обратите внимание, что возвышение по квадрату - не самый оптимальный метод. Вероятно, это лучшее, что вы можете сделать в качестве общего метода, который работает для всех значений экспоненты, но для определенного значения экспоненты может быть улучшена последовательность, которая требует меньше умножений.
Например, если вы хотите вычислить 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²) ², что также требует трех умножений).
Если вам нужно поднять 2 до мощности. Самый быстрый способ сделать это - сдвиг бит по мощности.
2 ** 3 == 1 << 3 == 8
2 ** 30 == 1 << 30 == 1073741824 (A Gigabyte)
Вот метод в 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;
}
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);
}
Чрезвычайно специализированный случай: когда вам нужно сказать, что 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, могут появиться другие особые случаи возведения в степень.
Если вы хотите получить значение целого числа для 2, поднятого до степени чего-то, всегда лучше использовать опцию shift:
pow(2,5)
можно заменить на 1<<5
Это намного эффективнее.
Также как комментарий к эффективности возведения в степень возведения в квадрат.
Преимущество такого подхода заключается в том, что он работает в log (n) времени. Например, если вы собираетесь вычислить что-то огромное, например x ^ 1048575 (2 ^ 20 - 1), вам нужно пройти через цикл 20 раз, а не 1 миллион +, используя наивный подход.
Кроме того, с точки зрения сложности кода это проще, чем пытаться найти наиболее оптимальную последовательность умножений, предложение a la Pramod.
Edit:
Думаю, я должен уточнить, прежде чем кто-то запишет меня за возможность переполнения. Этот подход предполагает, что у вас есть какая-то огромная библиотека.
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))
Поздно участнику:
Ниже приведено решение, которое также имеет дело с 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.
более общее решение с учетом отрицательной экспоненты
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);
}
Еще одна реализация (в 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;
}
}
Я использую рекурсивный, если 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);
}
}
Я реализовал алгоритм, который запоминает все вычисленные мощности, а затем использует их при необходимости. Так, например, 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);
}
В дополнение к ответу Элиаса, который приводит к неопределенному поведению при реализации с целыми числами со знаком и к неправильным значениям для высокого ввода при реализации с целыми числами без знака,
Вот модифицированная версия 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- равен идеальный квадрат, вы можете жить с этим.
Мое дело немного другое, я пытаюсь создать маску от власти, но я думал, что поделюсь решением, которое я нашел в любом случае.
Очевидно, что он работает только для степеней 2.
Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;
Если вы знаете показатель экспоненты (и это целое число) во время компиляции, вы можете использовать шаблоны для разворачивания цикла. Это можно сделать более эффективным, но я хотел продемонстрировать основной принцип здесь:
#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;
}
Игнорируя особый случай 2, поднятый до мощности, наиболее эффективным способом будет простая итерация.
int pow(int base, int pow) {
int res = 1;
for(int i=pow; i<pow; i++)
res *= base;
return res;
}
EDIT: Как было указано, это не самый эффективный способ... пока вы определяете эффективность как циклы процессора, которые, как я думаю, достаточно справедливы.