시드 가능한 JavaScript 난수 생성기


150

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

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


1
참고 :이 질문을 짧고 집중적으로 유지하기 위해 위의 질문에있는 코드를 커뮤니티 위키 답변으로 나누었습니다 .
Ilmari Karonen

답변:


124

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


18
David Bau의 씨앗은 이후 github에서 유지하기에 충분히 인기를 얻었습니다 . ECMAScript가 너무 오래 전부터 너무 오래되어서 언어에 포함되지 않은 것은 부끄러운 일입니다. 진심으로, 시드 없음!
Joes

2
@EatatJoes, 이것이 필요하고 가능하다는 것은 JS의 부끄러움이자 영광입니다. 하나의 파일을 포함하고 Math 객체에 대해 이전 버전과 호환되는 변경 사항을 얻을 수 있다는 것이 매우 좋습니다. Brendan Eich는 10 일 동안 나쁘지 않습니다.
Bruno Bronosky

2
이 프로젝트에 대한 npm 페이지를 찾는 모든 사람 : npmjs.com/package/seedrandom
Kip

27

시딩 기능이 필요하지 않은 경우 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));


2
계수는 2 ^ 31이 아니어야합니까? 나는 위키 에서이 알고리즘을 읽었다 .
Trantor Liu

3
당신도 알다시피, 이것은 수학이 지시하는 것을 출력하지 않는다는 의미에서 "정확한"것은 아닙니다. 다시 말해, 많은 수를 처리 할 수있는 언어는 다른 결과를 낳을 것입니다. JS는 큰 숫자를 질식하고 정밀도를 자릅니다 (결국 수레입니다).
DDS

4
-1이 LCG 구현 this.a * this.state은 2 ^ 53보다 큰 숫자를 초래할 수 있으므로 JavaScript의 정확한 정수에 대한 제한을 무효화합니다 . 결과는 제한된 출력 범위이며 일부 종자의 경우 매우 짧은 기간입니다. 또한 일반적으로 2의 거듭 제곱을 사용하면 m몇 가지 명백한 패턴을 얻을 수 있습니다. 어쨌든 단순한 잘림이 아닌 모듈러스 연산을 소비하는 경우 소수를 사용하지 않을 이유가 없습니다.
aaaaaaaaaaaa

22

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

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

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

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

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


1
Mersenne Twister의 JS 구현에 대한 링크 : math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
orip

1
@orip 3600 초기 상태에 대한 소스가 있습니까? Mersenne Twister는 32 비트 숫자로 시드되므로 PRNG는 초기 시드가 실제로 임의 인 경우에만 40 억 개의 초기 상태를 가져야합니다.
Tobias P.

2
@TobiasP. getSeconds ()와 getMinutes (), 60 * 60 == 3600 가능한 초기 상태의 조합으로 시드 제안을 언급했습니다. 나는 Mersenne Twister를 언급하지 않았습니다.
orip

3
@orip Ok, 명확하지 않았습니다. 당신은 Mersenne Twister와 다음 inital 상태에 대한 다음 문장에서 복용했다;)
Tobias P.

2
질문 asker는 모든 종류의 암호에 민감한 응용 프로그램에 대해 "적절한"난수 생성이 필요하다는 언급을하지 않습니다. 모든 대답은 사실이지만 첫 번째 단락 만 실제로 질문과 관련이 있습니다. 아마도 제안 된 솔루션의 코드 스 니펫을 추가하십시오.
V. Rubinetti 2016 년


8

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

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


7

다음은 커스텀 시드가 공급 될 수있는 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 회 실행됩니다.

2
이것이 왜 패턴을 생성합니까? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Timothy Kanski

@TimothyKanski 잘못 사용하고 있기 때문입니다. 나는 전문가가 아니지만 이것은 각 반복에서 생성기를 초기화하고 시드를 기준으로 첫 번째 값 만보 고 생성기의 후속 숫자를 반복하지 않기 때문에 발생합니다. 나는 이것이 지정된 간격에 걸쳐 시드를 해시하지 않는 모든 PRNG에서 발생한다고 생각합니다.
bryc

5

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]

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


0

참고 : 이 코드는 원래 위의 질문에 포함되었습니다. 질문을 짧고 집중적으로 유지하기 위해이 커뮤니티 Wiki 답변으로 옮겼습니다.

이 코드가 발동되는 것을 발견하고 난수를 얻은 다음 시드를 사용하는 것이 잘 작동하는 것처럼 보이지만 논리가 어떻게 작동하는지 잘 모르겠습니다 (예 : 2345678901, 48271 & 2147483647 숫자의 출처).

function nextRandomNumber(){
  var hi = this.seed / this.Q;
  var lo = this.seed % this.Q;
  var test = this.A * lo - this.R * hi;
  if(test > 0){
    this.seed = test;
  } else {
    this.seed = test + this.M;
  }
  return (this.seed * this.oneOverM);
}

function RandomNumberGenerator(){
  var d = new Date();
  this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
  this.A = 48271;
  this.M = 2147483647;
  this.Q = this.M / this.A;
  this.R = this.M % this.A;
  this.oneOverM = 1.0 / this.M;
  this.next = nextRandomNumber;
  return this;
}

function createRandomNumber(Min, Max){
  var rand = new RandomNumberGenerator();
  return Math.round((Max-Min) * rand.next() + Min);
}

//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];

alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");

/*
  If I could pass my own seed into the createRandomNumber(min, max, seed);
  function then I could reproduce a random output later if desired.
*/

3
와우 RandomNumberGeneratornextRandomNumber기능은 실제로는 레머 / LCG RNG 있어야하는데 1996 년에 모든 방법을 다시 날짜. 32 비트 정수에 대해 모듈로 산술을 수행하기 위해 영리한 수학을 사용합니다. 그렇지 않으면 중간 값을 포함하기에는 너무 작습니다. 문제는 JavaScript가 32 비트 정수를 구현하지 않고 64 비트 부동 소수점을 구현한다는 것입니다.이 코드와 같이 나누기가 정수 나누기가 아니기 때문에 결과는 Lehmer 생성기가 아니라고 가정합니다. 무작위로 보이는 일부 결과를 생성하지만 Lehmer 생성기의 보장은 적용되지 않습니다.
aaaaaaaaaaaa

1
createRandomNumber함수는 나중에 추가되며 거의 모든 것을 잘못합니다. 특히 호출 할 때마다 새로운 RNG를 인스턴스화합니다. 즉, 빠른 연속 호출은 모두 동일한 부동 소수점을 사용합니다. 주어진 코드에서 그것은 거의 불가능하다 'a'아무것도하지만,와 결합 될 '1''red'.
aaaaaaaaaaaa

-2

좋아, 여기에 내가 정착 한 해결책이 있습니다.

먼저 "newseed ()"함수를 사용하여 시드 값을 만듭니다. 그런 다음 시드 값을 "srandom ()"함수에 전달합니다. 마지막으로 "srandom ()"함수는 0과 1 사이의 의사 난수 값을 반환합니다.

중요한 것은 시드 값이 배열 안에 저장된다는 것입니다. 단순히 정수 또는 부동 소수점 인 경우 정수, 부동 소수점, 문자열 등의 값은 배열의 경우와 같이 포인터 만 스택에 직접 저장되므로 함수가 호출 될 때마다 값을 덮어 씁니다. 다른 물체. 따라서 시드 값이 지속적으로 유지 될 수 있습니다.

마지막으로, "srandom ()"함수를 정의하여 "Math"객체의 메소드가되도록 할 수 있지만, 알아낼 수 있도록 남겨 두겠습니다. ;)

행운을 빕니다!

자바 스크립트 :

// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000

// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
    return [seednum]
}

// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
    seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
    return seedobj[0] / (seedobjm - 1)
}

// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)

// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)

루아 4 (개인 목표 환경) :

-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000

-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
    return {seednum}
end

-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    return seedobj[1] / (seedobjm - 1)
end

-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)

-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)

추신-아직 스택 오버플로에 익숙하지는 않지만 게시물이 시간순으로 표시되지 않는 이유는 무엇입니까?
posfan12

안녕 @ posfan12-질문에 대한 답변은 일반적으로 "크림이 맨 위로 올라 오도록"upvotes 순서로 나열됩니다. 그러나 동일한 점수로 답변을 공정하게 볼 수 있도록 무작위 순서로 표시됩니다. 이것은 원래 내 질문 이었기 때문에 ;-) 나는 확실히 그것을 곧 확인할 것입니다. 본인 (또는 다른 사람)이이 답변이 도움이된다고 판단되면,이 답변을 올바로 답변합니다. "올바른"답변 인 경우이 답변에 녹색 확인 표시가 추가 된 것을 볼 수 있습니다. -StackOverflow에 오신 것을 환영합니다!
scunliffe

2
-1이 LCG 구현 seedobj[0] * seedobja은 2 ^ 53보다 큰 숫자를 초래할 수 있으므로 JavaScript의 정확한 정수에 대한 제한을 무효화합니다 . 결과는 제한된 출력 범위이며 일부 종자의 경우 매우 짧은 기간입니다.
aaaaaaaaaaaa
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.