본문 바로가기
algorithm

의사 난수 생성기 - 4. 메르센 트위스터(Mersenne Twister)

by kanlee2010 2022. 5. 2.

1997에 Makoto Matsumoto와 Takuji Nishimura가 개발하여 현대에 보편적으로 사용하는 PRNG (의사 난수 생성기)로 R 이나 Ruby, Pascal, PHP, Python, GLib, C++ Boost등의 기본 난수 생성기 알고리즘으로 사용되고 있습니다. 그 이름의 유래는 난수 생성 주기가 Mersenne prime 수인 2^19937 - 1 개의 주기 길이에서 만들어졌습니다. 기본 구현인 MT19937은 32 비트로 구현 되었으며 64 비트로 구현된 것은 MT19937-64로 호칭합니다.

624개의 정수(seed 포함)를 짝지어서 623차원의 하이큐브에 해당하는 좌표에 점을 찍어서 일관성을 발견할 수 없을 만큼 분포가 좋습니다. 

필요한 버퍼 크기는 일반적으로 2.5KiB로 크며 작은 버퍼가 요구될 때는 127bit를 사용하는 TinyMT를 이용합니다.

C++에서는 아래와 같은 API로 메르센 트위스터로 생성한 의사 난수를 사용할 수 있다고 합니다.

#include <iostream>
#include <random>

int main(void)
{
    std::random_device random;                                //하드웨어 리소스를 기반으로 난수를 생성
    std::mt19937 engine(random());                            //메르센 트위스터 방식으로 난수를 생성하겠다는 선언
    std::uniform_int_distribution<int> distribution(0, 100);  //난수의 범위와 자료형 정의. rand() % 100과는 다르게 100도 나올 수 있음
    auto generated = distribution(engine);
    std::cout << generated << std::endl;
}

CryptMT를 제외하고 BSD-style 라이센스 이거나 Free 라이센스 입니다.

https://web.archive.org/web/20070918014705/http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/mt19937ar.c에 공개된 내용은 아래와 같습니다.

#include <stdio.h>

/* Period parameters */  
#define N 624
#define M 397
#define MATRIX_A 0x9908b0dfUL   /* constant vector a */
#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
#define LOWER_MASK 0x7fffffffUL /* least significant r bits */

static unsigned long mt[N]; /* the array for the state vector  */
static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */

/* initializes mt[N] with a seed */
void init_genrand(unsigned long s)
{
    mt[0]= s & 0xffffffffUL;
    for (mti=1; mti<N; mti++) {
        mt[mti] = 
	    (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti); 
        /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
        /* In the previous versions, MSBs of the seed affect   */
        /* only MSBs of the array mt[].                        */
        /* 2002/01/09 modified by Makoto Matsumoto             */
        mt[mti] &= 0xffffffffUL;
        /* for >32 bit machines */
    }
}

/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
void init_by_array(unsigned long init_key[], int key_length)
{
    int i, j, k;
    init_genrand(19650218UL);
    i=1; j=0;
    k = (N>key_length ? N : key_length);
    for (; k; k--) {
        mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL))
          + init_key[j] + j; /* non linear */
        mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
        i++; j++;
        if (i>=N) { mt[0] = mt[N-1]; i=1; }
        if (j>=key_length) j=0;
    }
    for (k=N-1; k; k--) {
        mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL))
          - i; /* non linear */
        mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
        i++;
        if (i>=N) { mt[0] = mt[N-1]; i=1; }
    }

    mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ 
}

/* generates a random number on [0,0xffffffff]-interval */
unsigned long genrand_int32(void)
{
    unsigned long y;
    static unsigned long mag01[2]={0x0UL, MATRIX_A};
    /* mag01[x] = x * MATRIX_A  for x=0,1 */

    if (mti >= N) { /* generate N words at one time */
        int kk;

        if (mti == N+1)   /* if init_genrand() has not been called, */
            init_genrand(5489UL); /* a default initial seed is used */

        for (kk=0;kk<N-M;kk++) {
            y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
            mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
        }
        for (;kk<N-1;kk++) {
            y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
            mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
        }
        y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
        mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];

        mti = 0;
    }
  
    y = mt[mti++];

    /* Tempering */
    y ^= (y >> 11);
    y ^= (y << 7) & 0x9d2c5680UL;
    y ^= (y << 15) & 0xefc60000UL;
    y ^= (y >> 18);

    return y;
}

/* generates a random number on [0,0x7fffffff]-interval */
long genrand_int31(void)
{
    return (long)(genrand_int32()>>1);
}

/* generates a random number on [0,1]-real-interval */
double genrand_real1(void)
{
    return genrand_int32()*(1.0/4294967295.0); 
    /* divided by 2^32-1 */ 
}

/* generates a random number on [0,1)-real-interval */
double genrand_real2(void)
{
    return genrand_int32()*(1.0/4294967296.0); 
    /* divided by 2^32 */
}

/* generates a random number on (0,1)-real-interval */
double genrand_real3(void)
{
    return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0); 
    /* divided by 2^32 */
}

/* generates a random number on [0,1) with 53-bit resolution*/
double genrand_res53(void) 
{ 
    unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6; 
    return(a*67108864.0+b)*(1.0/9007199254740992.0); 
} 
/* These real versions are due to Isaku Wada, 2002/01/09 added */

int main(void)
{
    int i;
    unsigned long init[4]={0x123, 0x234, 0x345, 0x456}, length=4;
    init_by_array(init, length);
    printf("1000 outputs of genrand_int32()\n");
    for (i=0; i<1000; i++) {
      printf("%10lu ", genrand_int32());
      if (i%5==4) printf("\n");
    }
    printf("\n1000 outputs of genrand_real2()\n");
    for (i=0; i<1000; i++) {
      printf("%10.8f ", genrand_real2());
      if (i%5==4) printf("\n");
    }
    return 0;
}

https://web.archive.org/web/20070828104849/http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/eversions.html에는 다양한 언어로 구현된 내용도 공개되어 있습니다.

 

메르센 트위스터에서 생성된 의사 난수가 분포성이나 주기 길이는 좋아졌지만 아직 보안적으로 안전하게 사용할 수는 없습니다.

참고:

https://namu.wiki/w/%EB%82%9C%EC%88%98%EC%83%9D%EC%84%B1

https://en.m.wikipedia.org/wiki/Mersenne_Twister

https://web.archive.org/web/20070828025507/http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html

댓글