random 라이브러리

 

C++ 11때 modern C++로 넘어오면서 추가된 난수 라이브러리

기존의 C언어에서 난수를 생성하기 위해 사용하던 time.h나 stdlib.h의 문제점을 개선할 수 있음

 

 

 

C언어 방식의 문제점

 

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    srand(time(NULL));
    for (int i = 0; i < 10; i++) {
        printf("%d \n", rand() % 100);
    }
    
    return 0;
}

 

1. 진짜 랜덤이 아님

컴퓨터로 완벽한 랜덤 난수를 생성하는것은 어려움

 

위 코드의 방식은 진짜 난수를 생성하는 것이 아니라, 난수처럼 보이는 숫자를 생성하는 코드이며

첫 번째 수만 랜덤으로 정한 후 나머지 숫자들은 첫 숫자를 기반으로 만들어진 수열

 

무작위로 정해진 첫 번째 수를 시드(seed)라고 부르며 C언어는 srand로 seed를 설정함

위 코드에서는 time(NULL)을 srand의 값으로 주면서 프로그램을 실행한 시간을 기반으로 시드값을 설정함

그 후 rand()를 호출할때마다 seed를 기반으로 무작위처럼 보이는 수열을 생성

 

2. 시드값이 느리게 변함

time(NULL)을 이용하면 프로그램 실행시점의 초를 기준으로 첫 난수를 설정함

즉 동시에 여러 프로그램이 srand(time(NULL))을 사용하면 모두 같은 seed를 가지게 될 수 있음

 

3. 난수가 균등하게 생성되지 않음

위 코드에서 0 ~ 99의 숫자를 생성하기 위해 rand() % 100을 해줌으로써 난수를 100으로 나눈 나머지가 나오게 만듬

 

이때 rand()가 만드는 난수는 0부터 RAND_MAX까지인데 만약 RAND_MAX가 100의 배수가 아니라면?

만약 RAND_MAX가 150이라면 rand() % 100을 했을때 1이 나오는 조건은 rand()가 1이거나 101인 경우 두가지가 됨

반면 51이 나오기 위한 rand() 결과는 51 하나뿐이므로 모든 숫자가 나올 확률이 균등하지 않음

 

4. rand() 자체의 성능이 별로 좋지 않음

rand()에서 사용하는 알고리즘이 아주 훌륭하지는 않다고 함

 

 

 

C++ 방식의 random 라이브러리

 

#include <iostream>
#include <random>

int main() {    
    std::random_device rd;  // 시드값을 얻기 위한 random_device 생성    
    std::mt19937 gen(rd()); // random_device 를 통해 난수 생성 엔진을 초기화 한다
    std::uniform_int_distribution<int> dis(0, 99); // 균등 분포 정의(0 ~ 99가 균등한 난수열을 생성하기 위해)

    for (int i = 0; i < 10; i++) {
        std::cout << dis(gen) << ' ';
    }

    return 0;
}

 

1. std::random_device rd

seed값을 얻기 위한 객체

C언어는 time(NULL)로 seed를 얻었지만 modern C++의 random 라이브러리는 random_device를 이용해 seed를 얻음

 

C언어 방식은 수학적 알고리즘을 이용해 가짜 난수를 생성했지만 random_device는 운영체제 자체에서 제공하는 진짜 난수를 기반으로 함

운영체제가 제공하는 난수는 정말 랜덤하게 생기는 요소들이며 예를들면 장치드라이버의 noise를 기반으로 만들어진 난수같은것이라고 함

 

다만 진짜 난수이지만 컴퓨터가 주변과 상호작용하며 만들어지는 것이므로 C 방식의 가짜 난수보다는 속도가 매우 느림

그러므로 seed값만 random_device로 얻고 그 후는 seed값을 기반으로 한 난수 생성 엔진으로 난수를 생성함

 

2. std::mt19937 gen(rd())

std::mt19937은 random 라이브러리가 제공하는 난수 생성 엔진 중 하나

메르센 트위스터 알고리즘을 사용해서 C언어 방식의 rand()보다 난수들 간의 상관관계가 매우 작은 비교적 더 양질의 난수열을 생성함

 

mt19937 객체가 만들어지기만 하면 그 후 난수를 생성하는 작업은 매우 빠르지만 mt19937은 객체의 크기가 커서 메모리 제약이 심한 상황이라면 random 라이브러리의 다른 난수 생성 엔진을 사용할 수도 있음

C언어 방식의 rand()와 같은 알고리즘을 사용한 minstd_rand함수가 있다고 함

 

3. std::uniform_int_distribution<int> dis(0, 99)

0부터 99까지 균등한 확률로 난수를 만들기 위해 균등 분포를 정의하는 과정

다양한 분포 설정이 가능하며 균등 분포 외에 자주 사용되는 것은 정규분포

 

※ 균등 분포 사용방법

#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';
    }
}

 

 

 

 

※ 참고 문헌

https://modoocode.com/304