C++ 난수 생성 원리와 사용법


C++ 난수 생성의 원리와 사용법

이번에는 C++에서 난수 생성의 원리와 난수 생성 라이브러리를 사용해 볼 것이다.
우선 난수 생성의 원리를 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
 
unsigned int PRNG()
{
    static size_t seed = 5523;
    seed = 8253729 * seed + 2396403;
    return seed % 37268;
}
int main()
{
    for (int count = 1; count <= 100; count++)
    {
        cout << PRNG() << '\t';
        if (count % 5 == 0cout << endl;
    }
    return 0;
}
cs

< 실행 결과 >


이 코드를 보면 난수의 생성 원리를 볼 수 있다.

우선 13행에서 반복문을 돌려 PRNG 함수를 호출하고 출력을 한다.

6행을 보면 seed값에 5523을 넣고 이상한 값을 곱하고 더하고 나누고 한다.

이것은 오버플로우를 사용해서 seed 값과 전혀 관련없는 숫자를 나타내게 된다.

오버플로우를 이용하기 때문에 전혀 상상할수 없는 값이 나온다. (뭐 직접 계산해보고 오버플로우 계산하면 어떤 값이 나오는지 알수 있다.)


컴퓨터는 난수를 생성하지 못한다. 다만, 흉내를 내서 난수를 만드는 것처럼 값을 나오게 할 수는 있다.

난수를 생성한다기 보다는 계산을 한다는 표현이 더 맞는 표현이다.

8행에서 32768 숫자를 10의 자리 숫자를 나오게 하고 싶으면 * 100을 해주면 된다. 그럼 1~99까지의 숫자가 나오게 된다.


그럼 난수 생성 라이브러리를 사용해보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <cstdlib>
using namespace std;
 
int main()
{
    srand(5323); // seed 설정
 
    for (int count = 1; count <= 100; count++)
    {
        cout << rand() << '\t';
        if (count % 5 == 0cout << endl;
    }
    return 0;
}
cs

< 출력 결과 >


난수 생성 라이브러리를 사용하려면 cstdlib를 include 해줘야 사용할 수 있다.
7행에서 srand 함수로 5323의 정수값으로 seed를 설정해주고 있다.
11행에서 rand 함수로 난수 생성을 하고 있다.
또 한번 실행을 시켜보면 똑같은 값이 나오고 만다.
seed number가 똑같은 값이면 고정된 값만 나올수 밖에 없다.
그럼 매번 실행 할 때마다 다른 값이 나오게 할 수는 없을까? 라는 생각이 든다.
많이들 사용하는 법을 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
 
int main()
{
    srand(static_cast<size_t>(time(0))); // seed 설정
 
    for (int count = 1; count <= 100; count++)
    {
        cout << rand() << '\t';
        if (count % 5 == 0cout << endl;
    }
    return 0;
}
cs

일단 3행에서 ctime을 include 하자. ctime은 time() 함수를 사용할 수 있다.
time 함수는 시간을 연동?? 하는거라고 생각하면 편하다.
8행에서 time() 함수로 시간을 연동해주고 나오는 값을 size_t 형으로 강제 형변환을 해준다.
그럼 매번 실핼 할때마다 다른 값이 나오게 된다.
time() 함수로 시간을 연동하고 시간을 계속 흘러가니 seed 값에 매번 다른 값이 들어가게 된다.
1~100까지의 숫자를 나오게 하고싶으면 12행에서 rand() % 100 + 1 을 해주면 된다.

그럼 특정한 숫자 사이에서만 랜덤 난수를 발생시킬수는 없을까? 라는 생각이 든다.

한번 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <cstdlib>
#include <ctime>
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* (rand() * fraction));
}
 
int main()
{
    srand(static_cast<size_t>(time(0))); // seed 설정
 
    for (int count = 1; count <= 100; count++)
    {
        cout << getRandomNumber(5,8<< '\t';
        if (count % 5 == 0cout << endl;
    }
    return 0;
}
cs

< 출력 결과 >


6행에서 새로운 함수를 만든다.
우선 18행을 살펴보면 getRandomNumber 함수에 5와 8을 넘겨준다.
그리고 8행에서 fraction 변수 값을 넣어준다. RAND_MAX는 랜덤 값에 큰 값을 나타내주는 매크로이다.
9행에서 return을 5 값과 계산과정을 거친 값을 int형으로 형변환 해주고 더해주고 리턴해준다.
그럼 5~8 사이의 숫자만 랜덤값으로 나오게 할 수 있다.

이 방법을 사용하지 않고 그냥 18행에서 rand() % 4 + 5를 사용해줘도 5~8 사이의 값을 출력해줄 수 있다.
다만 4의 크기가 커지면은 특정 범위에 몰릴수가 있다.



이제 아주아주 정확한 랜덤값을 출력해줄 수 있는 라이브러리가 또 하나가 있다.
이것에 대해 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <random>
 
int main()
{
    std::random_device rd;
    std::mt19937 random(rd());
    std::uniform_int_distribution<> dice(16);
 
    for (int count = 1; count <= 20++count)
        std::cout << dice(random) << std::endl;
    return 0;
}
cs

이런 함수들을 사용하려면 random을 include 해야된다.
6행에서 아까는 랜덤을 시간에 연동하고 했는데 이번에는 random_device를 제공해주고 있다.
7행에서는 mt19937이라는 알고리즘을 사용한다. mt19937_64 알고리즘도 있는데 이것은 64비트에 맞는 랜덤값을 생성해준다.
8행은 1,2,3,4,5,6이 나올 확률이 동일하게 나온다.

이것 말고 정말 다양한것이 있다.



댓글

Designed by JB FACTORY