C 스타일의 난수 생성의 문제점
아래는 C 스타일로 0부터 99까지의 난수를 생성하는 코드이다.
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(){ srand(time(NULL)); for(int i = 0; i < 5; i++){ print("난수 : %d \n", rand() % 100); } return 0; }
위 코드는 진짜 난수를 생성하는 것이 아니라 마치 난수처럼 보이는 의사 난수(pseudo random number)을 생성하는 코드이다. 첫 번째 수만 무작위로 정하고, 나머지 수들은 그 수를 기반으로 여러가지 수학적 방법을 통해서 난수처럼 보이지만 실제로는 무작위로 생성된 것이 아닌 수열들을 만들어내게 된다.
무작위로 정해진 첫 번째 수를 시드(seed)라고 부르는데, C의 경우 srand를 통해 seed를 설정할 수 있다. 위 프로그램의 경우 time(NULL)을 통해 프로그램을 실행했던 초를 시드값으로 지정하였다. 그리고 rand()는 호출할 때 마다 시드값을 기반으로 무작위처럼 보이는 수열을 생성하게 된다.
하지만 위 코드는 시드값이 너무 천천히 변하고, 균등하게 난수를 생성하지 않는 문제점이 있다. rand() 함수는 일부 시뮬레이션에 사용하기에 적합하지 않다.
C++에서는 C의 srand와 rand를 갖다 버리자!!!
<random>
0부터 99까지의 난수를 생성하는 코드를 C++의 <random> 라이브러리를 사용해서 작성해보면
#include <iostream> #include <random> int main(){ // 시드값을 얻기 위한 random_device 생성 std::random_device rd; // random_device를 통해 난수 생성 엔진을 초기화한다. std::mt19937 gen(rd()); // 0부터 99까지 균등하게 나타나는 난수열을 생성하기 위해 균등 분포 정의 std::uniform_int_distribution<int> dis(0, 99); for(int i = 0; i < 5; i++){ std::cout << "난수 : " << dis(gen) << std::endl; } }
C의 경우 time(NULL)을 통해서 시드값을 지정하였지만 이는 여러 가지 문제점이 있었다. random_device를 이용하면 진짜 난수를 이용할 수 있다. 다만 진짜 난수의 경우 컴퓨터가 주변의 환경과 무작위적으로 상호작용하면서 만들어지는 것이기 때문에 의사 난수보다 난수를 생성하는 속도가 매우 느리다. 따라서 시드값처럼 난수 엔진을 초기화 하는 데 사용하고, 이후의 난수열은 난수 엔진으로 생성하는 것이 바람직하다.
std::mt19937는 rand보다 좀 더 양질의 난수열을 생성한다. 무엇보다 생성되는 난수들 간의 상관관계가 매우 작기 때문에 여러 시뮬레이션에서 사용할 수 있다. random_device 객체를 이용해서 난수 생성 엔진을 초기화 하는 대신에 본인이 원하는 시드값으로 넣어주고 싶다면
std::mt19937 gen(1234);
와 같이 해도 된다.
난수 생성 엔진을 만들었지만 바로 난수를 생성할 수 있는 것은 아니다. 어디에서 수를 뽑아낼지 알려주는 분포(distribution)를 정의해야 한다. 0부터 99까지 균등한 확률로 정수를 뽑아내기 위해서 균등분포(Uniform distribution) 객체를 정의해야 한다. uniform_int_distribution<int> 의 생성자에 원하는 범위를 써넣는다.
마지막으로 균등 분포에 사용할 난수 엔진을 전달함으로써 균등 분포에서 무작위로 샘플을 뽑아낼 수 있다.
<random> 라이브러리에서는 균등 분포 말고도 여러가지 분포들을 제공하고 있다. 그 중 가장 많이 쓰이는 정규 분포(Normal distribution)만 살펴보자.
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<double> dist(/* 평균 = */ 0, /* 표준 편차 = */ 1);
std::map<int, int> hist{};
for (int n = 0; n < 10000; ++n) {
++hist[std::round(dist(gen))];
}
for (auto p : hist) {
std::cout << std::setw(2) << p.first << ' '
<< std::string(p.second / 100, '*') << " " << p.second << '\n';
}
}
실행 결과
-4 1
-3 38
-2 ****** 638
-1 ************************ 2407
0 ************************************** 3821
1 ************************ 2429
2 ***** 595
3 70
4 1
'Language > C++' 카테고리의 다른 글
[C++] OOP - this 포인터 (0) | 2021.10.24 |
---|---|
[C++] OOP - 클래스(class) (0) | 2021.10.24 |
[C++] <string> 라이브러리 (0) | 2021.10.03 |
[C++] OOP - 생성자와 소멸자 (0) | 2021.07.09 |
[C++] 공용체, 열거체 (0) | 2021.07.03 |