C++ 표준 난수 라이브러리 <random>
완벽한 Random number를 소프트웨어로 생성하기란 불가능하다.
그래서 의사(pseudo) 난수 생성기라고도 부른다.
무작위인 것 처럼 보이게 만드는 수학 공식에 따라 생성하기 때문이다.
C++에서 제공하는 표준 난수 라이브러리를 사용하기 위해서는 아래와 같이 헤더를 추가해 주어야 한다.
#include <random>
기존의 C-style 난수 생성방법은 여러가지 문제들이 있었다.
이를 보완하기 위해서 C++에서는 좀 더 퀄리티가 좋은 난수 생성기를 제공한다.
C++ radom 라이브러리는 크게 두 가지 구성으로 이루어져 있다.
- Engine
- Distribution
Engine
Engine은 기본적으로 uniform distribution을 따르는 random number를 생성해 준다.
Engine은 seed를 받아서 random number를 만들어주는데 만약 디버깅을 위해서 매번 동일한 난수를 생성하고 싶다면
engine을 초기화 할 때마다 같은 시드를 사용하면 된다.
Random engine의 종류
- random_device
- linear_congruential_engine
- mersenne_twister_engine
- subtract_with_carry_engine
C++ 에서는 다양한 engine들을 typedef를 사용하여 정의해 놓았다.
위의 random engine 중에서 가장 대표적인 두 가지만 살펴보겠다.
random_device
random_device 엔젠은 소프트웨어 기반 엔진의 목적으로 만들어진 것이 아니다.
컴퓨터에 특수 하드웨어가 작창돼 있어야 쓸 수 있는 진정한 non-deterministic 난수 발생기이다.
현재 사용하는 컴퓨터에 특수 하드웨어가 없을 때는 소프트웨어 알고리즘 중 아무거나 적용하도록 정의돼 있다.
대체적으로 pseudo 난수 생성기 엔진보다 느리다.
그래서 주로 생성해야할 무작위수가 많다면 pseudo 난수 생성기 엔진에 들어가는 시드 값을 생성하는 데만 사용된다.
mersenne_twister_engine
소프트웨어 기반 난수 생성기 중에서 가장 품질이 좋다.
품질도 좋을 뿐더러 속도 측면에서도 빠르다고 손꼽힐 정도로 준수하다.
mersenne_twister_engine은 반환하는 수의 bit size에 따라서 두 가지 aliasing을 사용하고 있다.
이 때 사용되는 parameter들은 굉장히 많은데, mersenne_twister_engine 내부 구현에 관심이 없으면 그냥 default 값으로 사용하면 된다.
- using mt19937 = mersenne_twister_engine<uint_fast32_t, .... >
- using mt19937_64 = mersenne_twister_engine<uint_fast64_t, ...>
Distribution
Distribution은 engine에서 생성된 random number를 특정한 분포에 맞게 mapping 하는 역할을 한다.
Distribution의 종류
<random>에서는 다양한 distribution들을 제공한다.
그 중에서 대표적으로 사용되는 몇 가지만 살펴본다.
- Uniform distribution
Uniform distribution 난수 생성기의 경우 Type, a, b 총 3개의 parameter를 받아서 [a, b] 사이 범위의 숫자를 반환한다.
/* Initialization */
mt19937 random_engine;
std::uniform_int_distribution<TYPE> uniform_distribution(a, b);
/* Usage */
uniform_distribution(engine)
// Wikipedia
// https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution
- Normal distribution
Normal distribution 난수 생성기의 경우도 Type, m, s 총 3개의 parameter를 받는다.
하지만 여기서는 m, s가 범위가 아니라 mean와 standard deviation을 의미한다.
default type은 double이다.
/* Initialization */
mt19937 random_engine;
std::normal_distribution<TYPE> normal_distribution(n, s);
/* Usage */
normal_distribution(engine)
// Wikipedia
// https://en.cppreference.com/w/cpp/numeric/random/normal_distribution
- Poisson distribution
Poisson distribution 난수 생성기의 경우도 Type, m 총 2개의 parameter를 받는다.
여기서 m은 mean을 의미한다.
default type는 int이다.
/* Initialization */
mt19937 random_engine;
std::poisson_distribution<TYPE> poisson_distribution(m);
/* Usage */
poisson_distribution(engine)
// Wikipedia
// https://en.cppreference.com/w/cpp/numeric/random/poisson_distribution
Exercise #1
Random engine만 가지고 난수 생성해보기.
#include <iostream>
#include <random>
#include <typeinfo>
using namespace std;
int main()
{
random_device rd;
mt19937 random_engine(rd());
cout << " = RANDOM NUMBERS = " << endl;
cout << "[type] [byte] [value]" << endl;
for (int i = 0; i < 10; ++i)
{
auto random_number = random_engine();
cout << typeid(random_number).name() << "\t" << sizeof(random_number) << "\t" << random_number << endl;
}
}
위의 예제는 mt19937 엔진만을 사용해서 random number를 만들었다.
mt19937은 32bit 난수 생성기라고 하길래 출력되는 값들의 type, size, 그리고 실제 값을 출력해서 살펴보았다.
Result
Tip ) 처음에 typeid().name을 통해 출력했을 때는 unsigned long이라고 나오지 않고 m 이라는 값이 출력되었다.
이럴 때는 실행옵션으로 c++filt -t 옵션을 같이 주면 human readable 한 표현으로 변환되어서 출력된다!
ex) ./test | c++filt -t
결과를 보았을 때, type은 unsigned long 타입이 나왔고, byte는 8byte가 나왔다.
실제로 출력되는 숫자를 보니 uint32_t의 범위에서 출력되는 것을 확인할 수 있었다.
Default로는 32bit의 unsigned type으로 만들어주는 것을 확인했다.
따라서 32bit 난수 생성기의 목적대로 사용하기 위해서는 나온 결과 값을 안전하게 static_cast<uint32_t>와 같이 casting 해준 뒤 사용하면 될 것 같다.