관리 메뉴

드럼치는 프로그래머

[C/C++] rand() % M 사용하지 말라!!! 본문

★─Programing/☆─C | C++

[C/C++] rand() % M 사용하지 말라!!!

드럼치는한동이 2009. 2. 3. 11:09

사이트 돌아다니다가 rand() 관련 글이 있길래 영어공부 하는 셈치고 대충

번역해보았습니다..가다듬는 작업을 안했으므로 신뢰성 -100% 그냥 심심해서

해본거니까 -.-;....

원문은 아래 사이트에 있으니 원문보고 싶으시면 가보시길..

 

원문 :http://members.cox.net/srice1/random/crandom.html

 

=============================================================================

ANSI C에서는 rand() 0 RAND_MAX 범위 안의 정수를 무작위로 발생 시키는데

RAND_MAX값은 stdlib.h에 정의되어 있고 일반적으로 최소 32767 이상이다.

 

주목 할 것은 32767 이라는 수가 큰 수가 아니라는 점이다.

만약 RAND_MAX 32767 이라면 시퀀스가 시작되어 난수성을 상실할 때까지 20000개의 난수 정수를 얻을 수 있을 것이다.

 

RAND_MAX는 더 커져야 한다.

당신을 위해서라도 RAND_MAX 값을 확인해보자.

 

경험상 난수성을 상실 할 때까지 RAND_MAX 2/3 정도의 숫자만 발생 할 수 있을 것이다.

안전을 위해서 나는 RAND_MAX 1 / 10 보다 작은 난수 값을 만들 것을 추천한다.

 

만약 RAND_MAX 1 / 10 이상보다 더 큰 수를 필요로 한다면 SOURCE CODE AND DISCUSSION OF NEWER & BETTER RANDOM NUMBER GENERATORS 에 있는 새롭고 개선 된 방법을 찾도록 해라

 

RAND_MAX 값을 확인하는 방법

#include <stdio.h>

#include <stdlib.h>

main()

{

    printf("RAND_MAX = %u\n", RAND_MAX);

}

 

C C++에서 rand() 함수의 사용 예

#include <stdio.h>

#include <stdlib.h>

 

main()

{

/* 변수 선언 */

 int seed;   /* 난수생성을 위하여 수형 초기값을 아무거나 선택한다.

                초기값이 같은 경우 발생되서 나오는 난수는 동일하기 때문에

                다음번 난수 생성시에 다른 숫자를 발생 시키고 싶다면

                초기값을 다르게 설정해야 한다.

              */ 

                                     

 

 double r;  /* 범위 [0, 1) 사이의 난수 값 */

 

 

 long int M;  /* 사용자가 설정한 상위 제한범위 */

 

 

 double x;  /* 범위 [0, M) 사이의 난수 값 */

 int y;     /* 범위 [0, M) 사이의 난수 정수..

               만약 M이 정수라면 범위는 [0, M - 1) */ 

 int z;     /* 범위 [1, M + 1) 사이의 난수 정수..

               만약 M이 정수라면 범위는 [1, M] */

 int count; /* 이 예제에서 얼마나 많은 난수를 발생 시켰는지

               필요할 때 사용할 변수 */

/* 코드 시작 */

 seed = 10000;  /* 초기값 설정 */

 srand(seed);  /* 난수 발생기 초기화 */

 

 M = 1000;  /* 제한범위를 설정 (M RAND_MAX보다 작아야 한다) */

 for(count=1; count<=20; ++count)

 {

        r = (   (double)rand() / ((double)(RAND_MAX)+(double)(1)) );

        /* r은 범위 [0, 1)..0포함 1제외 의 난수 부동소수점

           정수로 나누는 것을 피하기 위해 rand()

           RAND_MAX + 1을 부동소수점으로 변환하였다. */

       

        x = (r * M);

        /* x는 범위 [0, M) 사이의 난수 부동소수점...0포함 M미만 */

      

        y = (int) x;              

        /* y는 범위 [0, M) 사이의 난수 정수값...0포함 M미만

            M이 정수라면 범위는 [0, M - 1] */

        z = y + 1;

         /* z는 범위 [1, M + 1)의 난수 정수값...1포함 M + 1 미만

             M이 정수라면 범위는 [1, M] */

        printf("random number %3d %5f %5f %5d %5d\n",count,r,x,y,z);

    }

}

 

M의 값에 따라서 범위를 확장 또는 축소 시킬 수 있다.

범위를 올리거나 또는 내리는 것에 따라서 나오는 숫자들의 고정값도 더해지거나 빼질것이다.

 

주의: 절대 사용하지 말라!

      y = rand()  %  M;

 

rand()의 하위비트에 살펴보자.

선형적인 난수 생성기는 rand()가 자주 불리면 상위 바이트에 비해서 하위 바이트가 난수성을 잃어 간다.

사실 최하위비트는 0 1에서만 순환한다.

그리하여 rand()는 짝수와 홀수로 순환한다 (직접 해보라)

주의 할 점은 rand()linear congruential random number generators 가 아니라는 것이다.

이러한 문제가 없다면 꽤 사용할만한 것이다.

 

우리는 r, x,y, z를 생성할 수 있는 방정식을 조합 할 수 있다.

 

r = [0,1) = {r: 0 <= r < 1} 실수

x = [0,M) = {x: 0 <= x < M} 실수

y = [0,M) = {y: 0 <= y < M} 정수

z = [1,M] = {z: 1 <= z <= M} 정수

 

 

주의 할 점은 rand() RAND_MAX + 1을 부동소수점형태(double)로 캐스팅을 한 후에 나눠야 한다.

RAND_MAX가 아키텍처에서 표현 할 수 있는 최대 양수정수 값일 가능성이있다.

그래서 (RAND_MAX) + 1이 오버플로우 또는 아키텍처에서 표현 할 수 있는 가장 큰 음수정수 값으로 변환 될 수 있기 때문에 RAND_MAX 1을 더하기전에 double 형태로 컨버팅을 한다.

 

 



 

 

  

M이 정수라면 y의 결과는 0 M - 1 포함

M이 정수라면 z의 결과는 1 M – 1 포함

 

주의 할 점은 계산시의 순서이다.

우리는 [rand() * M] 을 계산 시에 우리 컴퓨터가 표현 할 수 있는 최대 정수값을 넘어서는 (오버플로우)를 원하지 않을 것이다.

그래서 x,y,z는 이 형태로도 계산 할 수 있다.

 

 



 

 

여기서 [(double)(RAND_MAX) + (double)1] / (double)M rand()로 나눗셈을 하여 결과를 계산한다.

마찬가지로 M이 정수라면 y의 결과는 0 M – 1 포함하고

z의결과도 1 M 포함이다.

 

Dave Beleznay M RAND_MAX보다 클수록 더 큰차이의 결과를

얻을 수 있다고 한다.

또한 M이 아주 큰 값일 지라도 RAND_MAX / M 0

접근하면 언더플로우가 발생 할 수 있는 문제가 있다.(알아서 예외 처리 하라는걸지도..)

 

다시한번 강조하자면 : 절대 쓰지 말라!!!
      y = rand()  %  M;


[주의 : 어떤 C 언어의구현에서 저 공식의 사용이 필요 하지 않을 수 있다.

        당신이 사용하는 C언어의 구현에서는 완전히 다른 방식의

        난수 발생 수도 코드를 사용 할 수도 있기 때문이다


Comments