정수 기반의 전력 함수를 구현하는 가장 효율적인 방법 pow (int, int)


249

C에서 정수를 다른 정수만큼 거듭 제곱하는 가장 효율적인 방법은 무엇입니까?

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

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

3
"효율"이라고 말할 때는 무엇과 관련하여 효율적으로 지정해야합니다. 속도? 메모리 사용량? 코드 크기? 유지 보수성?
Andy Lester

C에 pow () 함수가 없습니까?
jalf

16
네, 그러나 그것은 int가 아닌 수레 또는 복식에서 작동합니다
Nathan Fellman

1
실제 int클래스 (그리고 거대한 클래스가 아닌)를 고수하고 있다면 ipow에 대한 많은 호출이 오버플로됩니다. 테이블을 미리 계산하고 오버플로되지 않는 모든 조합을 간단한 테이블 조회로 줄이는 영리한 방법이 있는지 궁금합니다. 이것은 대부분의 일반적인 답변보다 더 많은 메모리를 필요로하지만 속도면에서 더 효율적입니다.
Adrian McCarthy

pow()안전한 기능이
아님

답변:


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에서 >> =가 0 채우기 또는 부호 확장을 수행하는지 여부에 따라 C 컴파일러는 동작 중 하나를 선택할 수 있습니다.
user9876

23
gist.github.com/3551590 무료로 다운로드 할 수있는이 버전의 최적화 된 버전을 작성했습니다. 내 컴퓨터에서는 약 2.5 배 더 빠릅니다.
orlp

10
@ AkhilJain : 그것은 완벽하게 좋은 C입니다; 자바 또한 유효하게 교체 while (exp)if (exp & 1)while (exp != 0)if ((exp & 1) != 0)각각.
Ilmari Karonen

3
함수에 아마도가 있어야 unsigned exp하거나 그렇지 않으면 음수를 exp올바르게 처리 해야합니다.
Craig McQueen

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

참고 제곱에 의해 지수가 가장 최적의 방법은 아닙니다. 모든 지수 값에 대해 작동하는 일반적인 방법으로 수행하는 것이 가장 좋을 수도 있지만 특정 지수 값의 경우 곱셈이 덜 필요한 더 나은 시퀀스가있을 수 있습니다.

예를 들어, 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

이 최적의 곱셈 시퀀스를 찾기위한 효율적인 알고리즘은 없습니다. 에서 위키 백과 :

가장 짧은 덧셈 체인을 찾는 문제는 최적의 하위 구조의 가정을 만족시키지 않기 때문에 동적 프로그래밍으로는 해결할 수 없습니다. 즉, 전력을 더 작은 전력으로 분해하는 것만으로는 충분하지 않으며, 각각의 전력은 최소로 계산되는데, 더 작은 전력에 대한 추가 체인이 (계산을 공유하기 위해) 관련 될 수 있기 때문이다. 예를 들어, 위의 a¹ for에 대한 가장 짧은 덧셈 체인에서, a³가 재사용되기 때문에 a a의 하위 문제는 (a³) ²로 계산되어야합니다 (예 : a⁶ = a² (a²) ²). ).


4
@JeremySalwen :이 답변에서 알 수 있듯이 이진 지수는 일반적으로 가장 최적의 방법이 아닙니다. 최소한의 곱셈 시퀀스를 찾기 위해 현재 알려진 효율적인 알고리즘은 없습니다.
Eric Postpischil

2
@EricPostpischil, 응용 프로그램에 따라 다릅니다. 일반적으로 모든 숫자에 대해 작동하기 위해 일반적인 알고리즘이 필요하지 않습니다 . 컴퓨터 프로그래밍의 기술, Vol. 2 : 반 수치 알고리즘
Pacerier

3
Alexander Stepanov와 Daniel Rose의 수학부터 일반 프로그래밍 까지이 정확한 문제에 대한 좋은 설명이 있습니다. 이 책은 모든 소프트웨어 전문가 인 IMHO의 선반에 있어야합니다.
Toby Speight

2
en.wikipedia.org/wiki/… 도 참조하십시오 .
lhf

32 비트 정수에 오버플로를 발생시키지 않는 255 개 이하의 정수 전력이 있기 때문에 정수에 최적화 될 수 있습니다. 각 int에 대한 최적의 곱셈 구조를 캐시 할 수 있습니다. 나는 코드를 상상 + 데이터는 여전히 단순히 모든 힘을 캐싱보다 작은 것 ...
요시야 Yoder 보낸

22

2를 거듭 제곱해야하는 경우. 그렇게하는 가장 빠른 방법은 전력에 의한 비트 시프트입니다.

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

2 ** 0 == 1이되도록하는 우아한 방법이 있습니까?
Rob Smallshire

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

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는 아닙니다. 이러한 수를 계산하려면 임의의 정밀 산술이 필요합니다.
David Etler

큰 숫자를 사용 BigInteger를 # modPow 또는 BigInteger를 # 펑, 인수의 크기에 따라 적절한 알고리즘은 이미 구현
라만 Yelianevich

이것은 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입니다. 그리고 base가 1 또는 -1 인 경우에만 작동합니다. 따라서 정수가 아닌 제곱을 유발하지 않는 exp <0의 두 쌍 (base, exp) 만 있습니다. 나는 배우자이고 정량화를 좋아하지만,이 경우에 실제로는 음의 지수로 인해 정수 영역을 떠나게된다고 말할 수 있습니다.
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 (log (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 (log (exp))


이것은 Abhijit Gaikwadchux 의 답변 과 어떻게 다릅니 까? float제시된 두 번째 코드 블록에서 사용 방법을 논쟁 해보십시오 ( power(2.0, -3)계산 방법을 고려하십시오 ).
greybeard

@ greybeard 나는 몇 가지 의견을 언급했다. 귀하의 질의를 해결할 수있을 것입니다
roottraveller

1
GNU Scientific Library는 이미 두 번째 기능을 가지고 있습니다 : 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 ^ 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);
}

기본 유형으로 double을 사용하면 2의 거듭 제곱을 얻을 수 있습니다. (이 게시물을 제곱하는 데 도움을 주신 의견을 주신 분들께 감사드립니다).

IEEE float 에 대해 더 많이 배우 면 다른 특별한 지수화 사례가 나타날 수도 있습니다.


멋진 솔루션, 그러나 unsigend ??
paxdiablo

IEEE float는 기본 x 2 ^ exp이며, 지수 값을 변경해도 2의 거듭 제곱으로 곱할 수 있습니다. 플로트를 비정규
화할

당신은 모두 맞습니다. 제 솔루션은 원래 2의 거듭 제곱을 위해 오래 전에 작성되었다는 것을 잘못 기억했습니다. 문제에 대한 특별한 경우의 해결책으로 답변을 다시 작성했습니다.
Doug T.

먼저 코드가 인용 된대로 깨져 컴파일하려면 컴파일이 필요합니다. 두 번째로 코드는 gcc를 사용하여 core2d에서 손상됩니다. 이 덤프를 참조하십시오 아마도 내가 잘못한 것입니다. 그러나 IEEE float 지수가 10 진법이므로 이것이 작동하지 않을 것이라고 생각합니다.
freespace

3
베이스 10? 아뇨, 이진수로 10을 의미하지 않는 한 기본 2입니다.
Drealmer

4

제곱에 의한 지수의 효율성에 대한 언급에 대한 후속 조치와 같습니다.

이 방법의 장점은 log (n) 시간에 실행된다는 것입니다. 예를 들어, x ^ 1048575 (2 ^ 20-1)와 같이 거대한 것을 계산하려는 경우 순진한 접근 방식을 사용하면 1 백만이 아니라 루프를 20 회만 이동하면됩니다.

또한 코드의 복잡성 측면에서 라프 라모 (La Pramod)의 제안 인 가장 최적의 곱셈 시퀀스를 찾는 것보다 간단합니다.

편집하다:

누군가가 오버플로 가능성을 태그하기 전에 명확히해야한다고 생각합니다. 이 접근법은 일종의 hugeint 라이브러리가 있다고 가정합니다.


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*intUB 인 오버플로가 될 수 있습니다 .


powjii(INT_MAX, 63)에서 UB를 발생시킵니다 base *= base. 곱하거나 부호없는 것으로 이동하여 줄 바꿈 할 수 있는지 확인하십시오.
Cacahuete Frito

exp서명 할 이유가 없습니다 . 이 때문에 이상한 상황 코드를 복잡하게 (-1) ** (-N)유효하며, 어느 abs(base) > 10음의 값 exp부호가 그 코드를 저장하는 것이 좋습니다 그래서.
Cacahuete Frito

1
@CacahueteFrito y서명 된 그대로 실제로 필요하지 않고 의견을 남긴 합병증을 가져 왔지만 OP의 요청은 구체적이었습니다 pow(int, int). 따라서 이러한 좋은 의견은 OP의 질문에 속합니다. OP가 오버플로에서 수행 할 작업을 지정하지 않았기 때문에 잘 정의 된 오답은 UB보다 조금 더 낫습니다. "가장 효율적인 방법"을 고려할 때 OP가 OF에 관심을 갖는 것은 의심 스럽다.
chux-복원 Monica Monica

1

부정적인 exponenet을 고려한보다 일반적인 솔루션

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-복원 Monica Monica

1
@ chux : 하드 디스크를 포맷 할 수 있습니다 : 정수 오버플로는 UB입니다.
MSalters

@MSalters pow(i, INT_MIN)는 정수 오버플로가 아닙니다. 그 결과를 temp확실히 넘치면 시간끝날 수 있지만 잠재적으로 임의의 값으로 설정됩니다. :-)
chux-복원 Monica Monica

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

자바 질문이 아닙니다!
Cacahuete Frito

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

Elias의 답변 외에도 부호있는 정수로 구현하면 정의되지 않은 동작이 발생하고 부호없는 정수로 구현하면 높은 입력 값이 잘못됩니다.

다음은 부호있는 정수 유형에서도 작동하며 잘못된 값을 제공하지 않는 Squaing에 의한 지수의 수정 된 버전입니다.

#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또는 최적화되어야 비슷한), 그러나 그것은 C 상수 표현 이보다이며, 없습니다. 또한 결과 주조 sqrt()int있기 때문에 완벽한 정사각형의 경우 포인트 precission 부동의 매우 좋지 않다,하지만 난 어떤 구현을 알고하지 않는 한 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 개의 기능? 이것은 C 질문입니다.
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 != c++) == 1
Cacahuete Frito
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.