본문 바로가기
C │ C++ │ C#/C++

C++ Chapter 5.3 : 난수 만들기

by Pokaa 2023. 8. 23.
728x90
728x90
SMALL
난수를 생성하는 원리

 

#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;
}
728x90
300x250
LIST