본문 바로가기

프로그래밍/C++

C++11 새로운 Rand 함수를 써보자


이전의 rand()함수의 문제점은 난수함수의 최대 문제점인


데이터가 고르게 분포되지 않다는 점이,, 라고 한다라고 하지만,


가장 큰 문제점은 전역함수이므로 시드값을 프로그램 전체가 공유한다는 점이다.


때문에 사용자가 모르는 사이에 어디선가 호출이 되고, 


시드가 변경되는 것을 컨트롤 하기가 다소 불편한점 있었다.


static long holdrand = 1L;


void srand(unsigned int seed) {

  holdrand = (long) seed;

}


int rand() {

  return (((holdrand = holdrand * 214013L + 2531011L) >> 16) & 0x7fff);

}

// 기존의 난수함수는 시드값이 전역변수라는게 문제였다!



대체방안으로 메르센 트위스터(MT.Mersenne Twister)라는 랜덤생성기가 있는데,


기존에는 부스트 라이브러리가 있어야만 사용가능했지만,


C++11 에 포함이 되면서 이제 그냥 사용할 수 있게 되었다.



메르센 트위스터

위키백과, 우리 모두의 백과사전.

메르센 트위스터(Mersenne Twister)는 1997년에 마츠모토 마코토(松本 眞)와 니시무라 다쿠지(西村 拓士)가 개발한 유사난수 생성기이다.[1] 메르센 트위스터는 동일한 저자들이 개발한 TT800 생성기의 개선판으로, 기존 생성기들의 문제점들을 피하면서 매우 질이 좋은 난수를 빠르게 생성할 수 있도록 설계되었다.

메르센 트위스터의 이름은 난수의 반복 주기가 메르센 소수인 데에서 유래했다. 메르센 트위스터는 그 속도와 난수의 품질 때문에 점점 많은 곳에서 채택되고 있으며, 흔히 주기가 2^{19937}-1인 MT19937을 사용한다. MT19937과 같으나 생성해 내는 난수가 32비트가 아닌 64비트인 MT19937-64도 쓰이며, 2006년에 동일한 저자들이 발표한 SIMD 기반 메르센 트위스터는 MT19937에 비해 대략 두 배 정도 빠른 것으로 알려져 있다.

난수의 품질에도 불구하고, 메르센 트위스터는 암호학적으로 안전한 유사난수 생성기가 아니다. 즉 난수의 특성(주기, 난수 범위)을 알고 있을 때 유한한 수의 난수(이 경우 624개)만으로 현재 생성기의 상태를 알아 낼 수 있으며, 그 뒤에 나올 난수를 예측해 낼 수 있다. 암호학적으로 안전한 유사난수 생성기를 얻기 위해서는 해시 함수를 사용해야 하지만 난수의 생성 속도가 낮아진다. 또는 블룸 블룸 슙(BBS)과 같이 암호학적으로 안전하게 설계된 생성기를 쓸 수도 있다.

<위키피디아>

https://ko.wikipedia.org/wiki/%EB%A9%94%EB%A5%B4%EC%84%BC_%ED%8A%B8%EC%9C%84%EC%8A%A4%ED%84%B0



사용방법은 다음과 같다.

<random> 헤더를 추가하고


default_random_engine re; // 기본 엔진선언

uniform_int_distribution<int> nd(0, 100); // distribution 의 타입과 데이터범위를 선언


auto norm = bind(nd, re); // 여러번 호출해야 하므로 편하게 bind로 묶고

for (int i = 0; i < MAXCOUNT; ++i) {

norm(); }

// 끝


default_random_engine 의 정의부분을 보면

typedef mt19937 default_random_engine;

다음과 같이 타입이 정의되어 있는데, 

다시 정의부분을 보면 mersenne_twister_engine 라는 클래스의 구현부분을 볼 수 있다.


난수함수 엔진에는 default_random_engine 말고도 여러가지가 있으니 관련문서를 참조해 보시고..


그리고 시드를 초기화 하기 위해서는 보통 다음과 같은 방법을 사용한다.

std::random_device rd;

default_random_engine re(rd());

좀 더 나은 분포도를 가지도록 하려면 timeGetTime() 보다 나으니 이걸 권장.

참고로 랜덤디바이스 생성은 기존의 rand함수 호출보다 느리니 참고바람.


그럼 이제 테스트.


테스트환경은 그냥 내 PC;;;;

int형 배열 2000개를 선언.

범위는 0~100

그리고 난수함수를 2000번 돌려서 나온 데이터들을 모아 단순 평균을 해보았다.




1~5 번 데이터가 기존 난수함수이고


8~12번 데이터가 이번에 추가된 함수 결과 이다.


입력데이터가 적은지 이것만 봐서는 판단하기가 어렵다.


또한 다른 블로그에서도 분포도 테스트를 보았는데, 과연 후자 방식의 분포도가


월등히 나은지는 아직 모르겠다.


그렇다면 이제 실행 속도


이번에는 단순 for문으로 난수값을 10만번 호출하여 걸린 시간값을 측정했다.







1~3번이 기존 난수함수이고 5~7번이 후자로 실행한 결과값이다.


메르센 트위스터 생성기가 약 3배 정도 빠르다는 것을 알 수 있다.


분명 속도는 빠르다.



결론은..


전역함수를 대체 할 수 있다는 점에서 가장 높은 점수를 줄 수 있고,


분명 속도도 빠르다.


하지만 분포도는 아직 미지수다.



C++ Rand 함수

새 알고리즘


홈페이지에서는 이런식으로 분포도가 월등히 낫다고 홍보를 하고 있는데..


움..


마지막으로 실생활의 분포도와 비슷한 형태인 정규분포도도 지원한다.


normal_distribution


default_random_engine re; // 기본 생성엔진

normal_distribution<> nd(31, 8); 최대 31, 표준편차 8인 distribution



auto norm = bind(nd, re);


vector<int> mn(64);


int main()

{

for (int i = 0; i < 1000; ++i) ++mn[round(norm())]; // 난수생성


for (int i = 0; i < mn.size(); ++i) {

cout << i << '\t';

for (int j = 0; j < mn[i]; ++j) cout << '*';

cout << '\n';

}

}




실행하면 종모양의 정규분포도 형태가 나온다.