Programming/Modern c++

C++ 표준 난수 라이브러리 <random>

BiniU 2022. 4. 7. 16:51

완벽한 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 해준 뒤 사용하면 될 것 같다. 

 


 

 

 

반응형