[javascript] 시드 가능한 JavaScript 난수 생성기

JavaScript Math.random()함수는 0에서 1 사이의 임의의 값을 반환하며 현재 시간을 기반으로 자동 시드됩니다 (내가 믿는 Java와 유사). 그러나 나는 당신이 그것을 위해 자신의 씨앗을 설정하는 방법이 없다고 생각합니다.

고유 한 시드 값을 제공 할 수있는 난수 생성기를 어떻게 만들어서 반복 가능한 (의사) 난수 시퀀스를 생성 할 수 있습니까?



답변

하나의 옵션은 http://davidbau.com/seedrandom 인데 이는 훌륭한 속성을 가진 시드 가능한 RC4 기반 Math.random () 드롭 인 대체입니다.


답변

시딩 기능이 필요하지 않은 경우 Math.random()주변에서 도우미 기능을 사용 하고 빌드하십시오 (예 🙂 randRange(start, end).

어떤 RNG를 사용하고 있는지 잘 모르겠지만 그 특성과 한계를 알기 위해 RNG를 알고 문서화하는 것이 가장 좋습니다.

Starkii가 말했듯이 Mersenne Twister는 좋은 PRNG이지만 구현하기 쉽지 않습니다. 당신이 그것을 원한다면 LCG를 구현해보십시오. 매우 쉽고, 괜찮은 임의의 특성 (Mersenne Twister만큼 좋지는 않음)을 가지고 있으며 인기있는 상수 중 일부를 사용할 수 있습니다.

편집 : LCG 옵션을 포함하여 짧은 시드 가능한 RNG 구현에 대한 이 답변 의 훌륭한 옵션을 고려하십시오 .

function RNG(seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state;
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
  // returns in range [start, end): including start, excluding end
  // can't modulu nextInt because of weak randomness in lower bits
  var rangeSize = end - start;
  var randomUnder1 = this.nextInt() / this.m;
  return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
  return array[this.nextRange(0, array.length)];
}

var rng = new RNG(20);
for (var i = 0; i < 10; i++)
  console.log(rng.nextRange(10, 50));

var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
  console.log(rng.choice(digits));


답변

시드를 지정하려면 getSeconds()and에 대한 호출을 바꾸면됩니다 getMinutes(). int를 전달하고 mod 60의 절반을 초 값으로 사용하고 나머지 절반 modulo 60을 사용하면 다른 부분을 얻을 수 있습니다.

즉,이 방법은 쓰레기처럼 보입니다. 적절한 난수 생성을 수행하는 것은 매우 어렵습니다. 이것의 명백한 문제는 난수 시드가 초와 분을 기반으로한다는 것입니다. 시드를 추측하고 난수 스트림을 재생성하려면 3600 개의 다른 초 및 분 조합 만 시도하면됩니다. 또한 3600 개의 다른 가능한 씨앗 만 있음을 의미합니다. 이것은 수정이 가능하지만 처음부터이 RNG가 의심됩니다.

더 나은 RNG를 사용하려면 Mersenne Twister를 시도하십시오 . 궤도가 우수하고 성능이 우수한 잘 테스트되고 상당히 강력한 RNG입니다.

편집 : 나는 정말로 정확해야하며 이것을 의사 난수 생성기 또는 PRNG라고합니다.

“산술 방법을 사용하여 난수를 생성하는 사람은 누구나 죄 상태입니다.”
                                                                                                                                                          — 존 폰 노이만


답변

Mersenne Twister의 JavaScript 포트를 사용합니다.
https://gist.github.com/300494
시드를 수동으로 설정할 수 있습니다. 또한 다른 답변에서 언급했듯이 Mersenne Twister는 정말 좋은 PRNG입니다.


답변

나열된 코드는 Lehmer RNG 와 같습니다 . 이 경우, 2147483647가장 큰 32 비트 부호있는 정수이고 2147483647가장 큰 32 비트 소수이며 48271숫자를 생성하는 데 사용되는 전체주기 승수입니다.

이것이 사실이라면, 당신은 수정할 수 RandomNumberGenerator추가 매개 변수에 걸릴 seed다음 설정 this.seedseed; 그러나 씨앗이 난수를 잘 분배하도록주의를 기울여야합니다 (Lehmer는 이상 할 수 있습니다). 그러나 대부분의 씨앗은 괜찮을 것입니다.


답변

다음은 커스텀 시드가 공급 될 수있는 PRNG입니다. 호출 SeedRandom하면 임의 생성기 함수가 반환됩니다. SeedRandom현재 시간으로 반환 된 임의 함수를 시드하기 위해 인수없이 호출 할 수 있으며, 정수로 시드하기 위해 음수가 아닌 1 또는 2 개의 인수를 인수로 호출 할 수 있습니다. 값이 1 인 부동 소수점 정확도 시딩으로 인해 생성기는 2 ^ 53 개의 다른 상태 중 하나로 시작될 수 있습니다.

반환 된 난수 생성기 함수는라는 1 개의 정수 인수를 사용 limit하며, 한계는 1-4294965886 범위에 있어야하며, 함수는 0-한계 -1 범위의 숫자를 반환합니다.

function SeedRandom(state1,state2){
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    if(typeof state1!="number"){
        state1=+new Date()
    }
    if(typeof state2!="number"){
        state2=state1
    }
    state1=state1%(mod1-1)+1
    state2=state2%(mod2-1)+1
    function random(limit){
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
            return random(limit)
        }
        return (state1+state2)%limit
    }
    return random
}

사용 예 :

var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
                                //1 because of the specific seed.

이 발전기는 다음과 같은 속성을 나타냅니다.

  • 대략 2 ^ 64 개의 서로 다른 가능한 내부 상태가 있습니다.
  • 약 2 ^ 63의 기간이 있으며, JavaScript 프로그램에서 현실적으로 필요한 것보다 훨씬 많은 시간이 있습니다.
  • 로 인해 mod소수 인 값 출력의 간단한 패턴은 없다 어떠한 선택된 한계 상관 없다. 이것은 꽤 체계적인 패턴을 보이는 단순한 PRNG와는 다릅니다.
  • 한계에 관계없이 완벽한 분포를 얻기 위해 일부 결과를 버립니다.
  • 상대적으로 느리고 내 컴퓨터에서 초당 약 10,000 회 실행됩니다.

답변

Typescript로 프로그래밍하면 Christoph Henkelmann의 답변에 대한 Mersenne Twister 구현을이 스레드에 대한 typescript 클래스로 채택했습니다.

/**
 * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
 * all rights reserved to him.
 */
export class Random {
    static N = 624;
    static M = 397;
    static MATRIX_A = 0x9908b0df;
    /* constant vector a */
    static UPPER_MASK = 0x80000000;
    /* most significant w-r bits */
    static LOWER_MASK = 0x7fffffff;
    /* least significant r bits */

    mt = new Array(Random.N);
    /* the array for the state vector */
    mti = Random.N + 1;
    /* mti==N+1 means mt[N] is not initialized */

    constructor(seed:number = null) {
        if (seed == null) {
            seed = new Date().getTime();
        }

        this.init_genrand(seed);
    }

    private init_genrand(s:number) {
        this.mt[0] = s >>> 0;
        for (this.mti = 1; this.mti < Random.N; this.mti++) {
            var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
            this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                + this.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             */
            this.mt[this.mti] >>>= 0;
            /* for >32 bit machines */
        }
    }

    /**
     * generates a random number on [0,0xffffffff]-interval
     * @private
     */
    private _nextInt32():number {
        var y:number;
        var mag01 = new Array(0x0, Random.MATRIX_A);
        /* mag01[x] = x * MATRIX_A  for x=0,1 */

        if (this.mti >= Random.N) { /* generate N words at one time */
            var kk:number;

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

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

            this.mti = 0;
        }

        y = this.mt[this.mti++];

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

        return y >>> 0;
    }

    /**
     * generates an int32 pseudo random number
     * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
     * @return {number}
     */
    nextInt32(range:[number, number] = null):number {
        var result = this._nextInt32();
        if (range == null) {
            return result;
        }

        return (result % (range[1] - range[0])) + range[0];
    }

    /**
     * generates a random number on [0,0x7fffffff]-interval
     */
    nextInt31():number {
        return (this._nextInt32() >>> 1);
    }

    /**
     * generates a random number on [0,1]-real-interval
     */
    nextNumber():number {
        return this._nextInt32() * (1.0 / 4294967295.0);
    }

    /**
     * generates a random number on [0,1) with 53-bit resolution
     */
    nextNumber53():number {
        var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
        return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
    }
}

다음과 같이 사용할 수 있습니다.

var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]

더 많은 방법은 소스를 확인하십시오.