numeric_random

numeric_random (난수 라이브러리)

난수 라이브러리는 난수와 의사난수를 생성하는 클래스들을 제공해요. 균일 난수 비트 생성기(URBG)와 다양한 통계 분포로 구성돼요.

출처: cppreference

본문

난수 라이브러리는 난수와 의사난수를 생성하는 클래스들을 제공해요. 이 클래스들은 다음과 같아요:

  • 균일 난수 비트 생성기 (URBG) — 정수 시퀀스를 균일 분포로 생성하는 의사난수 생성기인 난수 엔진과, (가능하다면) 진정한 난수 생성기를 모두 포함해요.
  • 난수 분포 (예: 균일, 정규, 푸아송 분포) — URBG의 출력을 다양한 통계 분포로 변환해요.

URBG와 분포는 함께 사용해 난수 값을 생성하도록 설계되었어요. 모든 난수 엔진은 반복 가능한 시뮬레이터용으로 시드 지정, 직렬화, 역직렬화가 가능해요.

균일 난수 비트 생성기 (Uniform random bit generators)

균일 난수 비트 생성기는 가능한 결과 범위의 각 값이 (이상적으로) 같은 확률로 반환되도록 부호 없는 정수 값을 반환하는 함수 객체예요.

모든 균일 난수 비트 생성기는 UniformRandomBitGenerator 요구사항을 만족해요. C++20은 uniform_random_bit_generator 개념도 정의해요. <random> 헤더에 정의되어 있어요.

  • uniform_random_bit_generator (C++20) — 타입이 균일 난수 비트 생성기 자격을 갖추는지 지정 (개념)

난수 엔진 (Random number engines)

난수 엔진(흔히 엔진으로 줄여 부름)은 시드 데이터를 엔트로피 소스로 사용해 의사난수를 생성하는 균일 난수 비트 생성기예요.

어떤 시점에서든 타입 E의 엔진 e는 어떤 음이 아닌 정수 i에 대해 상태 eᵢ를 가져요. 생성 시 e는 엔진 매개변수와 초기 시드(또는 시드 시퀀스)에 의해 결정되는 초기 상태 e₀를 가져요.

모든 엔진 타입 E에 대해 항상 정의되는 속성은 다음과 같아요:

  • E 상태의 크기 (E::result_type 크기의 배수로, 즉 (sizeof eᵢ) / sizeof(E::result_type)).
  • e의 상태 eᵢ를 후속 상태 eᵢ₊₁로 진행하는 전이 알고리즘 TA (즉 TA(eᵢ) == eᵢ₊₁).
  • e의 상태를 E::result_type 타입의 값으로 매핑하는 생성 알고리즘 GA. 그 결과는 의사난수예요.

의사난수 시퀀스는 TA와 GA를 번갈아 호출해 생성할 수 있어요.

표준 라이브러리는 세 가지 다른 의사난수 생성 알고리즘 클래스를 클래스 템플릿으로 제공해서 알고리즘을 커스터마이즈할 수 있게 해요. 어떤 엔진을 쓸지는 여러 트레이드오프를 고려해야 해요:

  • 선형 합동 엔진은 적당히 빠르고 상태 저장 요구가 매우 작아요.
  • 메르센 트위스터 엔진은 더 느리고 상태 저장 요구가 크지만, 올바른 매개변수로 가장 긴 비반복 수열과 가장 바람직한 스펙트럼 특성을 가져요.
  • 차입 뺄셈(subtract with carry) 엔진은 고급 산술 명령 세트가 없는 프로세서에서도 매우 빠르지만, 상태 저장이 크고 때로는 스펙트럼 특성이 덜 바람직해요.
  • 필록스(Philox) 엔진은 카운터 기반 난수 생성기예요. 작은 상태와 긴 주기(2¹³⁰ 이상)를 가지며, 대규모 병렬 난수 생성이 필요한 몬테카를로 시뮬레이션용으로 설계되었어요. 벡터화·병렬화가 쉬워서 GPU 최적화 라이브러리에 구현되어 있어요. (C++26부터)

이 난수 엔진 중 어느 것도 암호학적으로 안전하지 않아요. 안전한 연산이 필요하면 암호 라이브러리를 사용해야 해요 (예: OpenSSL RAND_bytes).

이 템플릿들에서 인스턴스화된 모든 타입은 RandomNumberEngine 요구사항을 만족해요. <random> 헤더에 정의되어 있어요.

  • linear_congruential_engine (C++11) — 선형 합동 알고리즘 구현 (클래스 템플릿)
  • mersenne_twister_engine (C++11) — 메르센 트위스터 알고리즘 구현 (클래스 템플릿)
  • subtract_with_carry_engine (C++11) — 차입 뺄셈(지연 피보나치) 알고리즘 구현 (클래스 템플릿)
  • philox_engine (C++26) — 카운터 기반 병렬화 가능 생성기 (클래스 템플릿)

난수 엔진 어댑터 (Random number engine adaptors)

난수 엔진 어댑터는 다른 난수 엔진을 엔트로피 소스로 사용해 의사난수를 생성해요. 일반적으로 기본 엔진의 스펙트럼 특성을 바꾸는 데 사용해요. <random> 헤더에 정의되어 있어요.

  • discard_block_engine (C++11) — 난수 엔진의 일부 출력을 버림 (클래스 템플릿)
  • independent_bits_engine (C++11) — 난수 엔진의 출력을 지정된 비트 수 블록으로 묶음 (클래스 템플릿)
  • shuffle_order_engine (C++11) — 난수 엔진의 출력을 다른 순서로 전달 (클래스 템플릿)

미리 정의된 난수 생성기 (Predefined random number generators)

몇몇 특정 인기 알고리즘들이 미리 정의돼 있어요. <random> 헤더에 정의되어 있어요.

타입 정의
minstd_rand0 (C++11) std::linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647> — 1969년 Lewis, Goodman, Miller가 발견, 1988년 Park, Miller가 "Minimal standard"로 채택
minstd_rand (C++11) std::linear_congruential_engine<std::uint_fast32_t, 48271, 0, 2147483647> — 1993년 Park, Miller, Stockmeyer가 권장한 더 새로운 "Minimum standard"
mt19937 (C++11) std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> — 1998년 Matsumoto, Nishimura의 32비트 메르센 트위스터
mt19937_64 (C++11) std::mersenne_twister_engine<std::uint_fast64_t, ...> — 2000년 Matsumoto, Nishimura의 64비트 메르센 트위스터
ranlux24_base (C++11) std::subtract_with_carry_engine<std::uint_fast32_t, 24, 10, 24>
ranlux48_base (C++11) std::subtract_with_carry_engine<std::uint_fast64_t, 48, 5, 12>
ranlux24 (C++11) std::discard_block_engine<std::ranlux24_base, 223, 23> — 1994년 Martin Lüscher, Fred James의 24비트 RANLUX 생성기
ranlux48 (C++11) std::discard_block_engine<std::ranlux48_base, 389, 11> — 48비트 RANLUX
knuth_b (C++11) std::shuffle_order_engine<std::minstd_rand0, 256>
philox4x32 (C++26) std::philox_engine<std::uint_fast32_t, ...>
philox4x64 (C++26) std::philox_engine<std::uint_fast64_t, ...>
default_random_engine (C++11) 구현 정의된 RandomNumberEngine 타입

비결정적 난수 (Non-deterministic random numbers)

std::random_device는 비결정적 균일 난수 비트 생성기예요. 다만 비결정적 난수 생성을 지원하지 않는 경우 구현이 std::random_device를 의사난수 엔진으로 구현하는 것은 허용돼요.

  • random_device (C++11) — 하드웨어 엔트로피 소스를 사용하는 비결정적 난수 생성기 (클래스)

난수 분포 (Random number distributions)

난수 분포는 URBG의 출력을 정의된 통계 확률 밀도 함수에 따라 분포되도록 후처리해요. 난수 분포는 RandomNumberDistribution을 만족해요. <random> 헤더에 정의되어 있어요.

  • 균일 분포: uniform_int_distribution, uniform_real_distribution
  • 베르누이 분포: bernoulli_distribution, binomial_distribution, negative_binomial_distribution, geometric_distribution
  • 푸아송 분포: poisson_distribution, exponential_distribution, gamma_distribution, weibull_distribution, extreme_value_distribution
  • 정규 분포: normal_distribution, lognormal_distribution, chi_squared_distribution, cauchy_distribution, fisher_f_distribution, student_t_distribution
  • 샘플링 분포: discrete_distribution, piecewise_constant_distribution, piecewise_linear_distribution

유틸리티 (Utilities)

<random> 헤더에 정의되어 있어요.

  • generate_canonical (C++11) — 주어진 정밀도의 실수 값을 [0, 1)에 균등 분포 (함수 템플릿)
  • seed_seq (C++11) — 범용 편향 제거 스크램블 시드 시퀀스 생성기 (클래스)

난수 알고리즘 (Random number algorithms)

<random> 헤더에 정의되어 있어요.

  • ranges::generate_random (C++26) — 균일 난수 비트 생성기의 난수로 범위를 채움 (알고리즘 함수 객체)

C 난수 라이브러리 (C random library)

위에 설명한 엔진과 분포 외에 C 난수 라이브러리의 함수와 상수도 사용할 수 있지만 권장되지는 않아요. <cstdlib> 헤더에 정의되어 있어요.

  • rand — 의사난수 생성 (함수)
  • srand — 의사난수 생성기 시드 (함수)
  • RAND_MAX — std::rand가 생성할 수 있는 최댓값 (매크로 상수)

예제 (Example)

이 코드를 실행해 봐요.

#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
#include <string>

int main()
{
    // Seed with a real random value, if available
    std::random_device r;

    // Choose a random mean between 1 and 6
    std::default_random_engine e1(r());
    std::uniform_int_distribution<int> uniform_dist(1, 6);
    int mean = uniform_dist(e1);
    std::cout << "Randomly-chosen mean: " << mean << '\n';

    // Generate a normal distribution around that mean
    std::seed_seq seed2{r(), r(), r(), r(), r(), r(), r(), r()};
    std::mt19937 e2(seed2);
    std::normal_distribution<> normal_dist(mean, 2);

    std::map<int, int> hist;
    for (int n = 0; n != 10000; ++n)
        ++hist[std::round(normal_dist(e2))];

    std::cout << "Normal distribution around " << mean << ":\n"
              << std::fixed << std::setprecision(1);
    for (auto [x, y] : hist)
        std::cout << std::setw(2) << x << ' ' << std::string(y / 200, '*') << '\n';
}

가능한 출력:

Randomly-chosen mean: 4
Normal distribution around 4:
-4
-3
-2
-1
 0 *
 1 ***
 2 ******
 3 ********
 4 *********
 5 ********
 6 ******
 7 ***
 8 *
 9
10
11
12

더 알아보기 (Learn more)

cppreference