2010년 10월 23일 토요일

0~n-1 난수만들기


난수 발생의 난해함 [Dev] General

2005/11/30 10:43

복사 http://blog.naver.com/pengooni/100020052692

1. 난수(random number) 발생기(generator) 구현의 어려움

표준 C 라이브러리에는 rand()라는 함수가 있다. 물론 각각의 시스템에 따라 이 함수의 구현(implementation)은 완전하지 않을 수 있다. 그러나, 이 함수보다 더 좋은 성능을 가진 함수를 직접 만들기란 쉬운 일이 아니다!

직접 난수 발생기를 만들려고 한다면, 읽어야 할 책이 많다!

r250, RANLIB, FSULTRA 등의 라이브러리가 있긴 하지만..

2. 특정 범위 내에서 난수를 발생시키고 싶은데

다음과 같이 하는 것은 (0에서 N-1까지의 수치를 리턴) 매우 서투른 방법이다

rand() % N /* POOR */

왜냐하면, 대부분의 난수 발생기에서 하위(low-order) 비트들은 그리 랜덤하지 않기 때문이다.

따라서, 다름과 같이 하는 것이 좋다.

(int)((double)rand() / ((double)RAND_MAX + 1) * N)

실수를 쓰기 싫다면, 다음과 같이 해도 좋다.

rand() / (RAND_MAX / N + 1)

두 방법 모두 RAND_MAX를 (에 정의되어 있음) 사용하고, N이 RAND_MAX보다 아주 작은 값이라는 것을 가정한 방법이다.

(어쨌든, RAND_MAX는 rand()가 리턴할 수 있는 최대 수치를 나타낸다는 것을 기억해야 한다. RAND_MAX에 다른 수치를 대입할 수는 없으며, rand()가 다른 범위의 수치를 리턴하도록 해 주는 다른 방법은 존재하지 않는다!)

만약 0과 1사이의 범위를 가지는 난수 발생기를 가지고 있다면, 그리고 0과 N-1사이의 범위를 가지는 정수 난수를 만들고 싶다면, 단순히 그 난수 발생기의 수치에 N을 곱하면 된다!

3. 프로그램을 실행시킬 때마다, rand()는 일정한 순서로 난수를 발생시킨다. 왜 그런가

srand()함수를 불러서 가상 난수 발생기에게 (pseudo-random number generator) 초기 값을 임의로 주면 된다. 이 초기값은 'seed'라고 하며, 대개는 현재 시간이나, 유저가 어떤 키를 누르기 전까지의 시간차로 지정한다. (물론 어떤 키를 누르기 전의 시간을 얻는 방법은 이식성있게 만들기가 힘들다!) (일반적으로 프로그램 내에서는 srand() 함수를 한번만 불러주어도 충분하다! rand()를 부를 때마다, srand()를 부른다면, 충분한 임의의 수치를 얻을 수 없다.)

4. 참/거짓을 나타내는 난수가 필요하다. 그래서 rand() % 2를 썼는데, 계속 0, 1, 0, 1, 0 만을 반복하더라

엉성하게 만들어진 가상 난수 발생기는 (pseudo random number generator) 하위(low-order) 비트들에 대해서는 그렇게 랜덤하지 않다. 상위(high-order) 비트를 쓰는게 좋다.

5. 그럼 도데체 일반적이거나 정규 분포를 위한 난수는 어떻게 발생시킬 수 있는가

Marsaglia씨에 의해 개발되고 Knuth씨가 추천한 방법이다.

#include
#include

double gaussrand()
{
static double V1, V2, S;
static int phase = 0;
double X;

if (phase == 0) {
do {
double U1 = (double)rand() / RAND_MAX;
double U2 = (double)rand() / RAND_MAX;

V1 = 2 * U1 - 1;
V2 = 2 * U2 - 1;
S = V1 * V1 + V2 * V2;
} while (S >= 1 || S == 0);

X = V1 * sqrt(-2 * log(S) / S);
} else
X = V2 * sqrt(-2 * log(S) / S);

phase = 1 - phase;

return X;
}