C++ Chapter 5.3 : 난수 만들기

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


난수를 생성하는 원리

#include <iostream>

using namespace std;

unsigned int PRNG()
{
	static unsigned int seed = 5523; // 임의로 선택된 seed값 '5523'
	seed = 8235729 * seed + 2396403;  // 일부러 overflow를 일으킨다. 

	return seed % 32768; // [0, 32767] 사이의 값을 반환하게 된다.
}

int main()
{
	for (int count = 1; count < 100; ++count) // 난수를 100개 생성한다. 
	{
		cout << PRNG() << "\t";  // 난수 생성 후 출력
		if (count % 5 == 0) cout << endl;  // 5개출력마다 개행
	}
}
  • static unsigned int seed = 5523
    • seed : 난수 생성 시작 숫자
    • 5523 : 임의로 선택된 시드 넘버
    • static : 난수 생성 원리의 포인트!
  • seed = 8235729 * seed + 2396403;
    • unsigned int 로 담지 못하는 overflow 문제를 만들어낸다.
      • 최대한 시드와 관련없고 규칙없고 예측 못하게 난수를 생성하기 위하여.
      • static 변수는 값이 메모리에 유지가 된다.
        • 따라서 5523으로의 초기화는 딱 한번만 이루어지고 이 부분에서 seed값이 변하므로 난수를 생성할 때마다 (PRNG 함수가 호출될 때 마다) 변한 seed값으로 연산을 하게 된다.
  • 이 같은 원리로 인하여 seed 넘버가 똑같으면 생성되는 난수 패턴도 똑같다.
    • 그래서 seed 넘버를 상수로 하지 않고 매 순간 변하는 시간을 int 로 형변환해 설정하기도 한다. 밑에 참고


라이브러리를 사용하여 난수 생성하기

<csdlib>

  • std::rand() : seed로부터 랜덤 값 생성
  • std::srand(6545) : seed 넘버를 6545로 설정 ex) std::srand(6545) 로 시드 넘버를 설정해준 후 std::rand() 하여 랜던 값 가져오기
  • 다만 이렇게 시드 넘버가 상수이면 프로그램 실행할 때마다 생성되는 난수 패턴이 똑같다는게 한계점

<ctime>

  • std::time() : 1970년 1월 1일을 기준으로 현재까지 경과된 시간을 초 단위로 반환한다.
std::srand(statoc_cast<unsigned int>(std::time(0))); // 시드 넘버 설정

for (int count = 1; count <=100; ++count)
{
	cout << std::rand() << "\t"; // 난수 생성 및 출력

	if (count % 5 == 0) count << endl; 
}
  • 시드 넘버를 statoc_cast<unsigned int>(std::time(0)) 로 설정
    • 현재 시간(초)를 unsigned int로 형변환한 것을 시드 넘버로 설정
    • 시드 넘버를 시간으로 설정해주면 프로그램 실행시마다 난수 패턴을 다르게 할 수 있다.
  • 다만 디버깅 할 땐 시드넘버를 상수로 하는 것을 추천한다.


특정한 정수 범위 사이의 난수 생성하기

#include <iostream>
#include <cstdlib> //std::rand(), std::srand()
#include <ctime> //std:: time()

using namespace std;

int getRandomNumber(int min, int max)
{
	static const double fraction = 1.0 / (RAND_MAX + 1.0); // 역수

	return min + static_cast<int>((max - min + 1)* (std::rand()*fraction));
}

int main()
{
	std::srand(static_cast<unsigned int>(std::time(0))); // 시드 값은 시간으로.

	for(int count = 1; count <=100; ++count)
	{cout << getRandomNumber(5, 8)<< "\t"; // 탭 맞춰주기

	if (count % 5 == 0) cout << endl; // 5개 출력할 때마다 개행

	return 0;
}

int getRandomNumber(int min, int max)

static

  • 나누기 연산자체가 보통 느려서 초기화때만 나누기 연산 한번만 실행될 수 있도록 static으로 정의.
  • fraction 값은 보존될 것

RAND_MAX

  • 난수를 생성해서 나올 수 있는 가장 큰 숫자.
  • 매크로로 정의가 되어있다.

fraction

  • RAND_MAX + 1 의 역수
return min + static_cast<int>((max - min + 1)* (std::rand()*fraction));
  • 난수를 생성한후 fraction을 곱해주어 [0, 1) 범위의 난수로 만든다.
  • 이를 또 (max - min + 1)를 곱해주어 [0, max-min+1) 범위의 난수로 만든다.
  • 이를 int로 형 변환하여 [0, max-min+) 범위의 정수인 난수로 만든다.
  • min을 더해주면 최종적으로 [min, max + 1) 범위 내에 있는 난수가 리턴될 것.

getRandomNumber(5, 8) : 5 6 7 8 중 하나가 리턴된다.

for(int count = 1; count <=100; ++count)
	{
		cout << std::rand() %4 + 5 << "\t"; 

		if (count % 5 == 0) cout << endl; /
}

rand() % 4 + 5

  • rand() % 4 -> 0 , 1 , 2 , 3 값 중 하나가 될텐데
  • 여기에 5를 더해주면 5, 6, 7, 8 값 중 하나가 된다.
  • 이 방법의 문제점은 특정 범위로 몰릴 수가 있다는 것이다. 정밀한 경우에는 쓰지 않는 것이 좋다.

<random> 라이브러리

  • C++ 11 부터 사용 가능하다.
  • 난수가 특정 범위로 몰려 생성될 경우를 없애준다.
    • 각 숫자가 나올 확률을 전부 동일하게 해줌
using namespace std;

int main()
{
	std::random_device rd; // 시드 넘버 생성. time처럼 시드 넘버 계속 바뀌게 한다.
	
	std::mt19937_64 mersenne(rd()); // 랜덤 넘버 생성 알고리즘. mersenne는 알고리즘 이름이다.
	std::uniform_int_distribution<> dice(1, 6); // 1~6 사이의 수를 랜덤하게 만든다. 단, 각 숫자가 나올 확률은 전부 동일하다.
	
	for (int count = 1; count <= 20; ++count)
		cout << dice(mersenne) << endl; // 균등하게 랜덤 넘버 생성

	return 0;
}


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기