IT Hacks - 자연수 승수 계산을 비트연산으로 빠르게 하는 법
자연수 승수 연산에 한해, 비트연산 트릭을 활용하여 C++에서 기본으로 제공하는 pow() 함수보다 빠르게 계산하는 방법을 알아봅니다.
TL;DR
double pow_int(double base, int exp)
{
double result = 1.;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
double pow_int(double base, int exp)
{
double result = 1.;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
원리
실수 와 자연수 에 대하여, 을 계산하는 경우를 살펴보겠습니다.
여기서 은 2진법 변환을 통해 다음과 같이 표현할 수 있습니다.
위 식을 이용하면 은 다음과 같이 변형됩니다.
이 식의 의미를 해석해보면 다음과 같습니다.
-
을 2진법으로 나타내었을 때 낮은 자리수부터 확인()
-
매 를 확인할 때마다, 의 형태로 계속 제곱해 나감
-
가 0이면 그냥 넘어가고,
가 1이면 2에서 구한 를 누적하여 곱함
예시
를 계산해보겠습니다. 여기서 이고, 가 됩니다.
-
먼저, 을 2진법으로 변환하여 들을 구합니다.
-
이제 낮은 자리 수부터 확인하며 누적곱 작업을 수행합니다. 누적곱의 초기 값은 곱셈의 항등원인 1로 설정합니다.
-
는 3입니다.
을 확인합니다. 가 이므로, 1에 3을 곱합니다.
결과는 3이 됩니다.
-
는 9입니다.
을 확인합니다. 가 이므로, 아무것도 곱하지 않습니다.
결과는 여전히 3입니다.
-
는 81입니다.
을 확인합니다. 가 이므로, 3에 81을 곱합니다.
결과는 243이 됩니다.
-
이후의 들은 모두 이므로 최종 결과는 243입니다.
-
코드로의 적용(C++)
위 내용을 코드로 변환하면 다음과 같습니다.
변수명의 의미를 고려하여, 위 식에서 를 base
로, 을 exp
로 명명하였습니다.
double pow_int(double base, int exp)
{
double result = 1.;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
-
누적곱을 수행하기 위한
result
변수의 초기 값을 곱셈의 항등원인 1로 설정합니다. -
exp
값이 유효한 동안 while 반복문을 수행합니다.-
exp
를 2진법으로 표현하였을 때 의 가장 낮은 자리수를 구합니다. (exp & 1
)만약 이 값이 1이라면, 가 1인 경우에 해당합니다. 따라서, 현재의
base
값을result
에 누적하여 곱합니다.만약 이 값이 0이라면, 가 0인 경우에 해당합니다. 따라서
result
에 아무것도 곱하지 않고 넘어갑니다.예를 들어,
exp
의 값이 1이었다면, while 반복문을 맨 처음 수행할 때 이 if 조건이 참이 되어,result *= base;
구문이 수행되고, 위 설명에서 를 곱해주는 것과 동일한 작업이 이루어집니다. -
exp
를 오른쪽으로 한 칸 비트시프트를 수행합니다. (exp >>= 1;
)이를 통해, 방금 살펴본 가 버려지고, 그 다음 자리에 해당하는 이 가장 낮은 자리로 옮겨지는 과정이 반복됩니다.
-
base
를 제곱합니다. (base *= base;
)이를 통해 에 해당하는 반복 연산이 수행됩니다.
-
-
최종적으로 누적곱이 수행된 결과를 반환합니다. (
return result;
)
pow() 함수와의 비교용 예시 코드
코드
#include <iostream>
double pow_int(double base, int exp)
{
double result = 1.;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
int main()
{
const int N = 100000000;
double base = 12.345;
int exp = 67;
double result1, result2;
std::cout << base << "^" << exp << "\n\n";
auto startT1 = clock();
for (int i = 0; i < N; ++i)
result1 = pow(base, exp);
auto endT1 = clock();
std::cout << "Calculated by pow() function\n";
std::cout << "Answer: " << result1 << "\n";
std::cout << "Elapsed time: " << (endT1 - startT1) << " ms\n\n";
auto startT2 = clock();
for (int i = 0; i < N; ++i)
result2 = pow_int(base, exp);
auto endT2 = clock();
std::cout << "Calculated by pow_int() function\n";
std::cout << "Answer: " << result2 << "\n";
std::cout << "Elapsed time: " << (endT2 - startT2) << " ms\n\n";
return 0;
}
결과
#include <iostream>
double pow_int(double base, int exp)
{
double result = 1.;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
int main()
{
const int N = 100000000;
double base = 12.345;
int exp = 67;
double result1, result2;
std::cout << base << "^" << exp << "\n\n";
auto startT1 = clock();
for (int i = 0; i < N; ++i)
result1 = pow(base, exp);
auto endT1 = clock();
std::cout << "Calculated by pow() function\n";
std::cout << "Answer: " << result1 << "\n";
std::cout << "Elapsed time: " << (endT1 - startT1) << " ms\n\n";
auto startT2 = clock();
for (int i = 0; i < N; ++i)
result2 = pow_int(base, exp);
auto endT2 = clock();
std::cout << "Calculated by pow_int() function\n";
std::cout << "Answer: " << result2 << "\n";
std::cout << "Elapsed time: " << (endT2 - startT2) << " ms\n\n";
return 0;
}
i5-9500 @ 3.00GHz 기준
Author And Source
이 문제에 관하여(IT Hacks - 자연수 승수 계산을 비트연산으로 빠르게 하는 법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@evanote/bithacks-power저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)