가끔씩 정수의 제곱근을 구해야할 경우가 가끔씩 있는데, 이때 가장 편한 해결책은 float로 캐스팅해서 sqrt()함수에 넣은뒤 다시 int로 캐스팅하는 것입니다. 요즘 CPU들은 워낙 좋아서 부동소수점 제곱근 연산은 뚝딱 해치우니 속도 걱정은 할 필요가 없지요. 다만... 부동소수점 연산이 잘 지원안되는 환경에서 코딩을 하는 경우에는 정수 제곱근을 구하기위해 선뜻 sqrt함수를 쓰기가 꺼려집니다. 그래서 간단한 연산을 통해 정수 제곱근을 구해내는 함수를 구현해보도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 |
int isqrt( int n) { if (n < 0) return -1; if (n == 0) return 0; int i = 0; for (;n >= 0;++i) { n -= 2*i + 1; } return i-1; } |
정수 제곱근을 구해주는 isqrt 함수입니다.구현원리는 정말 단순합니다. 어떤 정수 n으로부터 차례대로 1, 3, 5, 7, 9 ...를 빼가면, 그 빼간 횟수가 n의 정수 제곱근이 됩니다.
근데 이건 반복횟수가에 비례해서 증가하기에, 그다지 효율적인 구현이라고는 보기 힘들군요. 약간 최적화해봅시다. 입력된 n이 k비트 정수라고 가정하면, 당연히 n의 제곱근은 k/2비트가 될겁니다. 이걸 이용하면, 반복횟수를 확실히 줄일수 있을듯 한데요?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
int bit8(unsigned char b) { static int r[256] = { 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, }; return r[b]; } int bit32(unsigned int b) { int r; if ((r = bit8(b >> 24)) > 0) return r + 24; if ((r = bit8(b >> 16)) > 0) return r + 16; if ((r = bit8(b >> 8)) > 0) return r + 8; return bit8(b); } int isqrt2( int n) { if (n < 0) return -1; if (n == 0) return 0; int t = (bit32(n)-1) >> 1; int i = 1 << t; n -= 1 << (t << 1); for (;n >= 0;++i) { n -= 2*i + 1; } return i-1; } |
의도치 않게 코드가 길어졌는데, 위의 bit8과 bit32는 정수가 몇 비트짜리 정수인지 구하기 위한 함수입니다. (기억이 가물가물한데 아마 저 역할을 해주는 어셈블리 연산자가 있었던거 같은데... 찾기 귀찮아서 그냥 함수를 구현해버렸긔ㅋ...) 최적화된 isqrt2함수는 비트수를 이용해 제곱근을 근사해서 i를 설정하고, 거기서부터 차례대로 홀수를 빼나가서 반복횟수를 줄였습니다.
아 최적화를 했으면 성능 비교를 해봐야겠지요.
isqrt함수와 isqrt2함수를 임의의 int 정수 500만개에 대해 수행해보았습니다. (/O2옵션주고 컴파일)
제 컴퓨터의 결과는 isqrt는 890ms, isqrt2는 390ms로 60%에 가까운 속도향상을 보이는군요 훗훗.
[c++ 11] 잘 쓰면 매우 편리한 C++ 11의 문법들 (15) | 2013.06.08 |
---|---|
템플릿 함수로 반복문 만들기 (0) | 2012.10.22 |
고정소수점 구현해보기 1. 사칙연산 (1) | 2012.10.11 |
if(ptr)ptr=NULL,return; 은 왜 안될까 (쉼표 연산자 이야기) (1) | 2009.09.13 |
템플릿과 역행렬 3. 2^n차가 아닌 행렬 (3) | 2009.06.28 |
템플릿과 역행렬 2. 본격 역행렬 구하기 (0) | 2009.06.28 |
댓글 영역