-
난수, rand() 함수@ 16. 1 ~ 17. 1/C++ 2015. 11. 17. 19:25
나머지 기반 난수 발생기의 결점은
기본적으로 rand함수 때문에 생기는 것이다.
rand함수는 20억 주기의 값들을 돌려주지만, 나머지를 적용하면 그 주기가 줄어든다. rand 함수가 생성한 수들의 하위 비트들에는
패턴이 존재하며, 그 패턴의 주기는 20억보다 훨씬 짧다. 그런데 나머지는 본질적으로 수의 상위 비트들을 잘라내므로 결과적으로
나머지를 통해 얻은 난수들의 주기가 원래의 난수들보다 훨씬 짧아지게 된다. 즉, 패턴이 좀 더 자주 반복되는 것이다.
패턴이 좀 더 나은 난수 생성기를 만들기 위해서는 하위 비트들이 아니라 상위비트들을 보존 활용해야한다.
나누기를 이용한 범위 결정
상위 비트를 보존 활용하는 가장 쉬운 방법은 나누기 연산자를 사용하는 것이다.
number = ((6 * rand()) / (RAND_MAX+1)) + 1; //1에서 6사이의 난수를 만들어내는 예이다.
rand가 돌려준 값에 6을 곱하고 그것을 최대 크기 더하기 1로 나눈다. 그러면 0 ~ 5사이의 수가 된다.
거기에 1을 더하니까 1 ~ 6의 수가 된다.
int RandomRange(int p_min, int p_max) { int difference = (p_max - p_min) + 1; return ((difference * rand()) / (RAND_MAX + 1)) + p_min; }
한가지 단점이 있다. 난수의 범위 크기(6이) 2^17보다 커서는 안된다. 크면 오버플로우가 일어남.
rand가 15비트의 수를 돌려주기 때문이다. int는 32비트이며 32비트 한계안에서 15비트수에 곱할 수 있는 최대한계는 17비트이다.
예를들어 18비트이면 오버플로우가 일어난다. 어쨌든 범위는 131072이상이면 안됨.
이산적인 수는 1,2,3처럼 정수인데..
비율을 난수로 얻어야 할 때가 있다. 0.1 0.2 등등..0.0 부터 1.0 사이의 소수..
어떻게 해야할까?
rand가 생성하는 난수의 범위는 0에서 RAND_MAX이므로 rand가 돌려준 값을 RAND_MAX로 나누면 원하는 결과를 얻는다.
예를들어 rand가 RAND_MAX를 돌려줬다면 RAND_MAX/RAND_MAX는 1이다.
마찬가지로 0/RAND_MAX는? 0이다.
float RandomPercent() { //여기서 중요한것은 정수 나누기가 일어나서는 안된다. //정수 나누기를 피하기 위해서 rand의 결과와 RAND_MAX 모두를 먼저 float으로 형변환한 후 나누기 수행 return (float)rand() / (float)RAND_MAX; }
비율을 생성할 수 있다면 임의의 범위의 부동소수점 난수도 쉽게 만들 수 있다.
위의 RandomPercent함수로 0~1사이의 부동 소수점 값을 얻고 범위를 적절히 조정하면 된다.
예를 들어 0.0 에서 5.0사이의 부동소수점 난수를 얻고자 하면. RandomPercent 결과에 5.0을 곱하기만 하면된다.
float num = RandomPercent() * 5.0f;
또 예를 들어 1에서 6사이의 실수를 얻고자 할때는 ? 정수와 마찬가지로 결과에 1을 더해주면 된다.
float num = RandomPercent() * 5.0f + 1.0f;
float RandomRangeF(float p_min, float p_max) { float difference = (p_max - p_min); return (RandomPercent() * difference) + p_min; }
위의 함수는 임의의 범위의 부동소수점 난수를 생성하는 함수(위의 RandomPercent()와 같이..)
그런데 지금까지 한 난수의 경우 균일한 확률내에서 난수가 발생하게 된다.
예를들어 1~6까지이면 1/6씩의 확률로 난수가 발생하게 된다.
그러나 실제 세상을 예로 들면 키가 가장 큰 사람과 가장 작은 사람은 보통 사람들 보다 확률이 매우 적다
그래서 우리는 비선형 난수 분포도 필요하다
비선형 난수 분포를 보이는 난수 발생에서 가장 간단한 방법은 두 난수를 더하면 된다.
int x = RandomRange(0, 4) + RandomRange(0, 4);
x의 범위가 그러면 0 ~ 8까지이고...난 수의 분포는 더이상 균일하지 않게 된다. 0이 나올 확률이 4가 나올 확률보다 훨씬 작아진다.
첫번째 난수나 두번째 난수의 결과에 따라 최종 결과의 값이 확 달라진다.
모든 가능한 경우들을 2차원 배열 형태로 표시하면..
0
1
2
3
4
0
0
1
2
3
4
1
1
2
3
4
5
2
2
3
4
5
6
3
3
4
5
6
7
4
4
5
6
7
8
파란색은 첫번째 난수의 결과, 빨간색은 두번째 난수의 결과
최종 결과가 0이 나올 경우는 0,0 딱 하나이다. 즉 확률은 1/25 = 4퍼센트 인것이다.
그런데 이것도 완만한 곡선과는 거리가 멀다.(그래프를 그린다면..)
그래서 한개의 난수를 더 더해서.. 세개의 난수를 더하면 좀 더 곡선에 가깝게 된다.
그러니까 0 ~ 9까지의 난수가 필요하다면
0 ~ 3사이의 난수를 3개 더한 결과를 이용하라는 것..
그럼? 4개 더한건? 더 좋지!
'@ 16. 1 ~ 17. 1 > C++' 카테고리의 다른 글
다시 C++ 정리 (0) 2016.11.15 클래스 메서드와 데이터 멤버에 대한 포인터 (0) 2015.11.18 Understanding and Using C Pointers(기억이 잘 안나는 부분) (0) 2015.06.28 전문가를 위한 C++ 정리(4) (0) 2015.05.01 전문가를 위한 C++ 정리(3) (0) 2015.04.21