C++ 난수 생성 | "random" 라이브러리 가이드

C++ 난수 생성 | "random" 라이브러리 가이드

이 글의 핵심

C++ 난수 생성에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.

rand() 문제점

// ❌ 구식 방법
srand(time(0));
int x = rand() % 100;  // 0-99

// 문제점:
// 1. 균등 분포 아님
// 2. 품질 낮음
// 3. 스레드 안전 아님

현대적 난수 (C++11)

#include <random>

int main() {
    // 시드
    random_device rd;
    
    // 난수 엔진
    mt19937 gen(rd());
    
    // 분포
    uniform_int_distribution<> dis(1, 100);
    
    // 난수 생성
    for (int i = 0; i < 10; i++) {
        cout << dis(gen) << " ";
    }
}

난수 엔진

// Mersenne Twister (권장)
mt19937 gen32;      // 32비트
mt19937_64 gen64;   // 64비트

// 선형 합동 생성기 (빠름, 품질 낮음)
minstd_rand gen;

// 빼기 합동 생성기
ranlux24 gen;

분포

균등 분포

// 정수
uniform_int_distribution<> intDis(1, 6);  // 주사위
int dice = intDis(gen);

// 실수
uniform_real_distribution<> realDis(0.0, 1.0);
double x = realDis(gen);

정규 분포

normal_distribution<> normalDis(100.0, 15.0);  // 평균 100, 표준편차 15
double iq = normalDis(gen);

기타 분포

// 베르누이 (참/거짓)
bernoulli_distribution coinFlip(0.5);  // 50%
bool result = coinFlip(gen);

// 이항 분포
binomial_distribution<> binDis(10, 0.5);
int heads = binDis(gen);

// 포아송 분포
poisson_distribution<> poisDis(4.0);
int events = poisDis(gen);

// 지수 분포
exponential_distribution<> expDis(1.0);
double time = expDis(gen);

실전 예시

예시 1: 주사위 시뮬레이션

#include <random>
#include <map>

int main() {
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dice(1, 6);
    
    map<int, int> histogram;
    
    // 10000번 굴리기
    for (int i = 0; i < 10000; i++) {
        int roll = dice(gen);
        histogram[roll]++;
    }
    
    // 결과 출력
    for (const auto& [value, count] : histogram) {
        cout << value << ": " << string(count / 100, '*') << endl;
    }
}

예시 2: 랜덤 문자열

string generateRandomString(size_t length) {
    static random_device rd;
    static mt19937 gen(rd());
    static uniform_int_distribution<> dis(0, 61);
    
    const string chars = 
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    
    string result;
    for (size_t i = 0; i < length; i++) {
        result += chars[dis(gen)];
    }
    
    return result;
}

int main() {
    cout << generateRandomString(10) << endl;
    cout << generateRandomString(20) << endl;
}

예시 3: 셔플

#include <algorithm>

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    random_device rd;
    mt19937 gen(rd());
    
    shuffle(v.begin(), v.end(), gen);
    
    for (int x : v) {
        cout << x << " ";
    }
}

예시 4: 가중치 랜덤

#include <random>

int main() {
    random_device rd;
    mt19937 gen(rd());
    
    // 가중치: 10%, 30%, 60%
    discrete_distribution<> dis({10, 30, 60});
    
    map<int, int> histogram;
    
    for (int i = 0; i < 10000; i++) {
        int choice = dis(gen);
        histogram[choice]++;
    }
    
    for (const auto& [choice, count] : histogram) {
        cout << "선택 " << choice << ": " << count << "회" << endl;
    }
}

시드 설정

// 시간 기반 (재현 불가)
mt19937 gen1(time(0));

// random_device (권장)
random_device rd;
mt19937 gen2(rd());

// 고정 시드 (재현 가능)
mt19937 gen3(12345);

// 시드 시퀀스
seed_seq seq{1, 2, 3, 4, 5};
mt19937 gen4(seq);

자주 발생하는 문제

문제 1: 매번 엔진 생성

// ❌ 비효율
int getRandom() {
    random_device rd;
    mt19937 gen(rd());  // 매번 생성 (느림)
    uniform_int_distribution<> dis(1, 100);
    return dis(gen);
}

// ✅ static 사용
int getRandom() {
    static random_device rd;
    static mt19937 gen(rd());
    static uniform_int_distribution<> dis(1, 100);
    return dis(gen);
}

문제 2: 시드 재사용

// ❌ 같은 시드
for (int i = 0; i < 10; i++) {
    mt19937 gen(12345);  // 항상 같은 시퀀스
    cout << gen() << endl;
}

// ✅ 엔진 재사용
mt19937 gen(12345);
for (int i = 0; i < 10; i++) {
    cout << gen() << endl;
}

문제 3: 범위 편향

// ❌ 편향됨
int x = rand() % 100;  // 균등하지 않음

// ✅ 균등 분포
uniform_int_distribution<> dis(0, 99);
int x = dis(gen);

성능 비교

#include <chrono>

int main() {
    const int N = 10000000;
    
    // rand()
    srand(time(0));
    auto start = chrono::high_resolution_clock::now();
    for (int i = 0; i < N; i++) {
        int x = rand();
    }
    auto end = chrono::high_resolution_clock::now();
    cout << "rand(): " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
    
    // mt19937
    random_device rd;
    mt19937 gen(rd());
    start = chrono::high_resolution_clock::now();
    for (int i = 0; i < N; i++) {
        int x = gen();
    }
    end = chrono::high_resolution_clock::now();
    cout << "mt19937: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
}

FAQ

Q1: rand() vs random?

A:

  • rand(): 구식, 품질 낮음
  • random: 현대적, 품질 높음, 유연함

Q2: random_device는 항상 사용해야 하나요?

A: 시드로만 사용하세요. 난수 생성은 엔진을 사용하세요.

Q3: 어떤 엔진을 사용하나요?

A: 대부분 mt19937이 적합합니다.

Q4: 재현 가능한 난수는?

A: 고정 시드를 사용하세요.

Q5: 스레드 안전한가요?

A: 엔진과 분포를 스레드별로 생성하세요.

Q6: Random 학습 리소스는?

A:

  • cppreference.com
  • “The C++ Standard Library” (Nicolai Josuttis)
  • “Effective Modern C++“

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Random | “난수 생성” 가이드
  • C++ Distribution | “확률 분포” 가이드
  • C++ random_device | “하드웨어 난수” 가이드

관련 글

  • C++ Distribution |
  • C++ random_device |
  • C++ Random |
  • C++ async & launch |
  • C++ Atomic Operations |