본문 바로가기

난수 생성 Random Number Generation 본문

💘 C++/흐름제어

난수 생성 Random Number Generation

Hyonii 2022. 4. 29. 00:32

난수 생성 Random Number Generation

게임에서 가상캐릭터가 항상 같은 행동을 한다면 재미없다.

이럴 때 난수를 이용해서 마치 실제 캐릭터가 행동하는 것처럼 더 재밌게 만들 수 있다.

 

컴퓨터는 본질적으로 예측 가능한 결과를 산출하도록 설계되어 있다.

따라서 컴퓨터는 일반적으로 난수를 생성 할 수 없다.

대신 의사 난수 생성기(pseudo-random number generator)를 사용해서 가장 자주 수행되는 난수를 시뮬레이션 해야한다.

 

의사 난수 생성기(pseudo-random number generator:PRNG)시드(seed)라고 하는 시작 번호를 가지고

시드에 관련 없는 것으로 보이는 다른 번호로 변환하기 위해 수학 연산을 수행하는 프로그램이다.

그런 다음 생성된 숫자를 가져와서 같은 수학 연산을 수행하여 생성된 숫자와 관련없는 새로운 숫자로 변환한다.

마지막으로 생성된 숫자에 수학 연산을 수행하는 작업을 반복함으로써 충분히 복잡해지면 난수와 같은 새 숫자를 생성할 수 있다.

[EX 1] PRNG, 난수가 생성되는 원리를 설명하는 코드

난수를 생성하는 함수를 호출하게 되면 seed는 시작하는 숫자이다. (5523은 임의로 쓴 seed number임!)

이 때 유심히 봐야 할 점은 seed는 unsigned int 이다.

8253729는 오버플로우를 이용하고 있는 것이다.

큰 상수와 오버플로우를 사용하기 때문에 다음 숫자가 무엇인지 예측하기는 어렵다.

 

랜덤함수를 사용할 때에도 랜덤함수의 범위를 지정해주기 위해서 나머지 연산자를 사용하는 경우가 있다.

간단한 경우에는 그렇게 사용할 수 있는데 정밀한 결과가 필요한 경우에는 그렇게 하면 안좋은 결과가 나온다.

위 예제는 간단한 거라 나머지 연산자 쓰면서 설명한거임

 

아무튼 실행시켜보면

 

관련성이 없어 보이는 것 처럼 보이는 숫자들을 만들어낸다.

난수처럼 보이는 숫자들을 여러개 계산 해내는 것이다.

컴퓨터는 실제로 난수를 만들 능력이 없다는 것 기억하기!

 

seed 가 바뀌고 다시 PRNG 함수가 호출되었을 때에도

반복적으로 바뀐 값이 사용이 되어야 하기 때문에

static 변수를 선언하고 있다는 거 잘 봐둬야함!

[EX 2] cstdlib에 들어있는 std::rand(), std::srand()

cstdlib에 들어있는 std::rand(), std::srand()를 사용 할 수 있다.

 

srand의 s는 seed를 뜻한다. seed를 설정해주는것!

 

실행해보면

 

컴퓨터는 seed number가 고정되어 있을 때에는 다른 숫자를 생성할 수 없다.

이럴 때 seed number를 그때그때 바꿔주는 방법이 있다.

가장 많이 사용하는 방법 중 하나는 time을 이용하는 것이다.

 

[EX 3] ctime에 있는 std::time()

seed number를 고정된 숫자를 사용하는 대신에 현재시간(system clock)과 연결시키는 방법이 있다.

사용자가 프로그램을 실행할 때 마다 시간이 달라진다.

이 시간 값을 시드로 사용하면 프로그램이 실행될 때 마다 다른 시퀀스 숫자를 생성한다.

 

C++에는 초 수를 반환하는 time()이라는 함수가 있다.

이를 사용하려면 <ctime>헤더를 포함하고 srand()를 time(0)호출로 초기화 해야한다.

 

시간이 가니까 랜덤 넘버도 바뀌는 모습을 볼 수 있다.

이렇게 seed를 바꿔서 난수처럼 보이는 숫자들을 생성할 수 있다.

 

★seed가 고정되어 있는 것은 무조건 나쁜가?라고 생각 할 수 있다

 하지만 디버깅 할 때에는 오히려 seed를 고정 시켜야 한다.

디버깅 할 때 매번 숫자가 바뀌면, 문제라고 생각했던 현상이 발생했다가 안했다가 할 수 있다

그렇게 되면 디버깅 하기 엄청 힘들어 진다.

랜덤넘버를 사용한 프로그램을 작성할 때 디버깅이 필요하다면

seed를 고정을 시켜놓고 반복을 시키면 어짜피 같은 숫자를 발생 시키기 때문에 디버깅이 가능하다는 거 꼭 알아두기

[EX 4] 특정한 정수 사이에 랜덤 넘버를 발생시키는 방법

함수 하나를 만든다.

 

RAND_MAX는 랜덤넘버를 생성할 때 나올 수 있는 가장 큰 숫자를 의미. 매크로로 정의 되어있음

이 코드를 실행하면

 

5,6,7,8 중에 있는 숫자들이 적당히 분포해서 나온다.

초록부분 : RAND_MAX 범위 + 1인 숫자를 1로 나눠주고 있다

나누기는 보통 느리다. 그래서 1/숫자를 static으로 저장해놓고 반복해서 사용하고있다.

이렇게 사용하는 방법도 있고 전역상수를 만들어서 쓰는 방법도 있다.

 

그리고 어떤 경우에는

 

이렇게 하면 5~8이 나온다

이 방법도 있는데

 

이 숫자가 작을 경우에는 상관없다

숫자가 큰 범위에 대해서 난수를 생성해야 하는 경우에는

이 숫자(난수)가 특정영역으로 몰리는 문제가 생길 수 있다

정밀한 경우에는 저렇게 하는 것 보다는 랜덤 라이브러리를 사용하는 것이 좋다.

 

랜덤 라이브러리는 C++11부터 들어왔다

#include <random> 해야함

랜덤 라이브러리는 내용이 엄청 많다

통계를 잘 모르면 좀 어려울 수 있다 하지만 장기적으로는 이것을 쓰는 것을 권장한다

 

아까는 랜덤넘버를 랜덤하게 만들기위해서 시간에 연동시켰는데

여기서는 별도의 랜덤 디바이스를 제공해주고 있다. -> std::random_device

 

std::mt19937 은 랜덤넘버를 생성하는 알고리즘에 관련된건데

std::mt19937이 있고 std::mt19937_64가 있따

std::mt19937_64를 사용하면 64비트 짜리 난수를 생성해준다

std::mt19937을 사용하면 32비트 짜리를 난수를 생성해준다

 

mersenne는 수학자 이름임. 변수명이기 때문에 내가 원하는대로 바꿔 쓸 수 있다

여기서는 그 알고리즘을 사용한다는 의미로 메르센느라고 지음

 

그 다음 seed가 되는게 random device를 사용을 하니까 rd()를 넣음

정리를 하면 create a mersenne twister (twister는 숫자를 꼬아주는것)

 

그 다음 seeding 을 하는데 seed를 심어주는게 중요하다

seed가 고정이 되어있으면 같은 난수 패턴이 발생한다

여기서는 random device rd()를 사용해서 그런 현상을 막는것이다

아까 time 연동하는 것과 유사한 역할을 한다

 

std::uniform_int_distribution<> dice(1,6)

1부터 1포함 6이하 1,2,3,4,5,6 중에서 하나가 선택이 되는데

1이 나올 확률, 2 가 나올 확률, 3이 나올 확률 모두 동일한 확률로 나온다

그게 uniform distribution 이다

 

구조가 좀 다단이라서 복잡할 수 있다.

랜덤디바이스 rd 를 만들고

랜덤디바이스를 넣어서 생성기(mersenne)를 만들고

생성기가 어떤 분포를 따를지 해서 우리가 사용할 분포(dice)를 만들고

분포가 생성기로 진짜 난수를 만들고 이런식으로 된다

실행시켜 보면

 

다시 실행시켜보면

 

랜덤하게 잘 나온다

'💘 C++ > 흐름제어' 카테고리의 다른 글

std::cin 더 잘 쓰기  (0) 2022.04.29
break, continue  (0) 2022.04.27
for 문 for statement  (0) 2022.04.26
do-while 문 do-while statement  (0) 2022.04.26
while 문 while statement  (0) 2022.04.25
Comments