C++ Random 함수

Seongcheol Jeon·2024년 11월 17일
0

CPP

목록 보기
19/47
post-thumbnail

난수 생성

프로그래밍을 하다 보면 난수가 필요할 때가 많다. 특히 게임 분야에서 난수는 매우 중요하다. 난수란 정의된 범위에서 무작위로 추출된 임의의 수를 의미한다. 난수는 그 다음에 나올 값을 누구도 확신할 수 없어야 한다. 컴퓨터 프로그래밍에서 난수를 만들 때는 보통 시드(seed)라는 시작 숫자를 이용하는데, 이 시드값으로 현재 시각을 사용하는 경우가 많다. 즉, 시시각각 변하는 현재 시작을 특정한 알고리즘에 넣어 난수를 만든다.

CC++ 언어에서는 이처럼 난수를 생성하는 randsrand 함수가 있다.

  • rand 함수는 난수 생성 패턴을 한 개로 설정.
  • srand 함수는 난수 생성 패턴을 여러 개로 설정.

이 함수들이 정의된 헤더 파일을 보면 #define RAND_MAX 0x7fff 라는 매크로 상수가 선언되어 있다. 이는 난수의 범위가 0 ~ 32,767까지라는 의미이다. 범위가 생각보다 넓지 않아서 난수가 균등하게 분포되지 않을 수 있다.

이런 문제를 해결하기 위해 C++11 부터는 고품질의 난수 생성기와 분포 클래스를 제공하여 난수의 형식, 범위, 분포와 형태 등을 세세하게 조절할 수 있도록 했다.

다음 코드는 <random> 헤더 파일에 있는 std::mt19937을 이용하는 예이다. mt1993732bit 버전이고, 64bit 버전인 mt19937_64도 있다. 다음 코드는 64bit 버전을 이용하여 임의의 수 10개를 생성하는 예이다.

  • mt19937 : 32bit
  • mt19937_64 : 64bit
#include <iostream>
#include <random>


using namespace std;


int main()
{
    mt19937_64 mt_rand;

    for (int i = 0; i < 10; i++) {
        cout << mt_rand() << endl;
    }

    return 0;
}

실행 결과

14514284786278117030
4620546740167642908
13109570281517897720
17462938647148434322
355488278567739596
7469126240319926998
4635995468481642529
418970542659199878
9604170989252516556
6358044926049913402

그런데 이 코드는 난수를 생성할 때에 시드값을 사용하지 않아 실행할 때마다 같은 값이 나올 수 있다. 이번에는 시드값으로 시스템의 현재 시각을 넣어 난수를 생성해보자.

#include <iostream>
#include <random>
#include <chrono>


using namespace std;


int main()
{
    // seed 값 사용
    auto curTime = chrono::system_clock::now();
    auto duration = curTime.time_since_epoch();
    auto millis = chrono::duration_cast<chrono::milliseconds>(duration).count();

    mt19937_64 mt_rand(millis);

    for (int i = 0; i < 10; i++) {
        cout << mt_rand() << endl;
    }

    return 0;
}

실행 결과

13046483459348615973
428865184304725544
4092226827631619104
9798059489380720993
17101403504210358503
18339317656448932595
8073935047207915881
13039217037191916072
785563860698437517
806762417456524090

이처럼 시드값을 이용하면, 실행할 때마다 다른 값을 만들 수 있다. 그런데 이러한 시드값 대신 하드웨어 엔트로피를 이용하는 방법도 있다.

하드웨어 엔트로피란?

시스템에서 발생하는 무작위성의 정도.

표준 라이브러리에서 제공하는 random_device 클래스를 이용하면 이러한 하드웨어 엔트로피를 이용해 난수를 생성할 수 있다. 하드웨어의 마우스 움직임, 커서 위치, 키보드 입력, 디스크 I/O 등 다양한 외부 요인을 활용하여 엔트로피를 수집한다.

#include <iostream>
#include <random>


using namespace std;


int main()
{
    random_device rng;

    for (int i = 0; i < 10; i++) {
        auto result = rng();
        cout << result << endl;
    }

    return 0;
}

실행 결과

2599606016
3595906431
4166036206
2567559748
3970587961
3258923598
1014178902
1690990272
2048142794
3489405322

그러나 random_device는 대체로 mt19937 엔진보다 느리다. 그래서 생성할 난수가 많을 때는 mt19337 엔진을 사용하고, random_device는 엔진의 시드값을 생성하는 데만 사용하는 것이 좋다.

random_device는 mt19937 엔진보다 느리다!

<random>에는 random_device 외에도 3가지 난수 엔진을 제공한다. 표준 라이브러리에서 다양한 난수 생성 엔진을 제공한다는 것만 기억해 두고 필요할 때 참고하면 된다.

  • linear_congruential_engine
    • 선형 합동 난수 엔진
  • mersenne_twister_engine
    • 메르센 트위스터 난수 엔진
  • subtract_with_carry_engine
    • 감산 캐리 난수 엔진

완벽한 난수를 만들수 있을까?

완벽한 난수 생성기는 예측이 불가능하며 어떠한 패턴도 갖지 않아야 한다. 그러나 컴퓨터에서 사용되는 대부분의 난수 생성기는 사실상 의사 난수(pseudo-random number)이다.

의사 난수는 특정한 알고리즘을 기초로 생성되며 초깃값이 같으면 같은 순서로 생성한다. 따라서 초깃값이나 시드값에 의존하므로 예측할 수 있다. 또한, 오랫동안 사용하면 주기적으로 반복되는 패턴이 나타날 수 있다.

이를 보완하려면 더 복잡하고 예측이 어려운 알고리즘을 사용하거나 외부 장치를 활용하여 무작위성을 높이는 방법이 있다. 외부 장치로는 물리적인 현상을 활용하는 하드웨어 난수 발생기가 있다. 이러한 발생기는 주로 양자역학의 물리적인 현상을 활용하여 완벽한 난수를 생성한다.

정리하면, 컴퓨터에서 완벽한 난수를 만드는 것은 매우 어려우며, 현실에서는 특정한 요구 사항에 맞는 안전하고 예측이 어려운 난수 생성기를 선택하는 것이 중요하다.

0개의 댓글