シード可能なJavaScript乱数ジェネレータ


149

JavaScript Math.random()関数は、0〜1のランダムな値を返します。現在の時刻に基づいて自動的にシードされます(私が信じているJavaに似ています)。ただし、自分で種を設定する方法はないと思います。

独自のシード値を提供できる乱数ジェネレータを作成して、(疑似)乱数の反復可能なシーケンスを生成できるようにするにはどうすればよいですか?


1
注:この質問を短く集中しておくために上記の質問にあったコードを以下のコミュニティWikiの回答に分割しまし
Ilmari Karonen 14年

回答:


123

1つのオプションはhttp://davidbau.com/seedrandomで、これはシード可能なRC4ベースのMath.random()ドロップイン置換で、素敵なプロパティを備えています。


18
デビッド・バウのシードランダムはそれ以来、彼がgithubでそれを維持するのに十分人気が出てきました。ECMAScriptが非常に長い間バックシーンで使用されており、このようなものが言語に含まれていないのは残念です。真剣に、シードなし!!!
Joes

2
@EatatJoes、これが必要であり可能であるのは、JSの恥と栄光の両方です。1つのファイルをインクルードし、Mathオブジェクトに下位互換性のある変更を加えることができるのは、とてもクールです。ブレンダンアイヒ、10日間の作業で悪くない。
Bruno Bronosky、2015年

2
このプロジェクトのnpmページをお探しの方:npmjs.com/package/seedrandom
Kip

27

シード機能が必要ない場合は、そのMath.random()周りにヘルパー関数を使用して構築します(例:)randRange(start, end)

どのRNGを使用しているかはわかりませんが、その特性と制限を認識できるように、それを知って文書化することをお勧めします。

Starkiiが言ったように、Mersenne Twisterは優れたPRNGですが、実装は簡単ではありません。自分でやりたい場合は、LCGを実装してみてください。これは非常に簡単で、まともなランダム性(メルセンヌツイスターほど良くありません)であり、人気のある定数のいくつかを使用できます。

編集: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にすべきではありませんか?私はこのアルゴリズムをwikiから読みました。
Trantor Liu 2013

3
ご存じのとおり、これは数学が指示する内容を出力しないという意味で「正しい」ものではありません。つまり、これらの大きな数を処理できる言語では、結果が異なります。JSは大きな数をチョークし、精度をチョップします(結局、これらはフロートです)。
DDS

4
-1このLCG実装this.a * this.stateは、2 ^ 53より大きい数値になる可能性が高いため、JavaScriptの正確な整数の制限を破ります。その結果、出力範囲が制限され、一部のシードでは非常に短い期間になる可能性があります。さらに、一般に2の累乗を使用すると、mかなり明白なパターンが得られますが、単純な切り捨てではなくモジュラス演算を使用する場合は、いずれにしても素数を使用しない理由はありません。
aaaaaaaaaaaa 2014年

22

あなたは、種子を指定できるようにしたい場合は、あなただけの呼び出しを交換する必要があるgetSeconds()getMinutes()。intを渡して、秒の値にmod 60の半分を使用し、残りの半分を60を法として使用して、他の部分を取得できます。

そうは言っても、このメソッドはゴミのように見えます。適切な乱数生成を行うことは非常に困難です。これの明らかな問題は、乱数シードが秒と分に基づいていることです。シードを推測して乱数のストリームを再作成するには、3600種類の秒と分の組み合わせを試すだけです。また、可能なシードは3600しか存在しないことも意味します。これは修正可能ですが、このRNGは最初から疑わしいと思います。

より良いRNGを使用したい場合は、Mersenne Twisterを試してください。これは十分にテストされた非常に堅牢なRNGであり、巨大な軌道と優れたパフォーマンスを備えています。

編集:私は本当に正しいはずであり、これを疑似乱数ジェネレータまたはPRNGと呼びます。

「算術演算を使用して乱数を生成する人は誰でも罪の状態にあります。」
                                                                                                                                                          ---ジョンフォンノイマン


1
メルセンヌツイスターの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の可能な初期状態です。私はメルセンヌ・ツイスターについて言及していませんでした。
orip

3
@orip OK、はっきりしていませんでした。あなたはメルセンヌツイスターについて、そして次の文で初期状態について考えていました;)
トビアスP.

2
質問者は、暗号に敏感なあらゆる種類のアプリケーションに対して、「適切な」乱数の生成が必要であることについては触れていません。答えはすべて真実ですが、最初の段落だけが実際に質問に関連しています。おそらく、提案されたソリューションのコードスニペットを追加します。
V.ルビネッティ2015年


8

リストしたコードはLehmer RNGのように見えます。これが当てはまる場合、2147483647は最大の32ビット符号付き整数、2147483647最大の32ビット素数、および48271数値の生成に使用される全周期乗数です。

これに該当する場合はRandomNumberGenerator、追加のパラメーターを取り込むように変更してからseed、に設定this.seedできseedます。ただし、シードによって乱数が適切に分散されるように注意する必要があります(Lehmerはそのように奇妙な場合があります)。ただし、ほとんどのシードは問題ありません。


7

以下は、カスタムシードを供給できるPRNGです。呼び出すSeedRandomと、ランダムジェネレーター関数が返されます。SeedRandom返されるランダム関数に現在の時刻をシードするために引数なしで呼び出すことができます。または、整数としてシードするために、引数として1または2の非負のinterを指定して呼び出すことができます。浮動小数点精度のため、値を1つだけシードすると、ジェネレーターは2 ^ 53の異なる状態のいずれかでのみ開始できます。

返されるランダムジェネレーター関数はという名前の整数引数を1つ取ります。limit制限は1から4294965886の範囲でなければなりません。関数は0からlimit-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 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、2016

@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関数は、実際には1996年までさかのぼります。これは、Lehmer / LCG RNGであると想定されています。それはいくつかの巧妙な数学を使用して、さもなければいくつかの中間値を含めるには小さすぎる32ビット整数に対してモジュロ演算を実行します。問題は、JavaScriptは32ビット整数を実装せず、64ビット浮動小数点を実装することです。除算はこのコードのように整数除算ではないため、結果はレーマージェネレーターではないと推定されます。ランダムに見える結果を生成しますが、レーマージェネレーターの保証は適用されません。
aaaaaaaaaaaa 2014年

1
このcreateRandomNumber関数は後で追加されます。ほとんどすべてが間違っています。特に、呼び出されるたびに新しいRNGがインスタンス化されます。つまり、連続して呼び出されると、すべて同じフロートが使用されます。与えられたコードでは'a''1'および以外のものとペアにすることはほとんど不可能です'red'
aaaaaaaaaaaa 2014年

-2

OK、ここに私が解決した解決策があります。

最初に、「newseed()」関数を使用してシード値を作成します。次に、シード値を「srandom()」関数に渡します。最後に、「srandom()」関数は0と1の間の疑似ランダム値を返します。

重要なビットは、シード値が配列内に格納されることです。単に整数または浮動小数点数の場合、整数、浮動小数点数、文字列などの値は配列の場合のようにポインタだけではなくスタックに直接格納されるため、関数が呼び出されるたびに値が上書きされます。他のオブジェクト。したがって、シードの値が永続的である可能性があります。

最後に、「srandom()」関数を「Math」オブジェクトのメソッドとなるように定義することは可能ですが、それについてはユーザーが判断できるようにしておきます。;)

幸運を!

JavaScript:

// 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)

Lua 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)

PS-私はまだスタックオーバーフローに慣れていませんが、なぜ時系列で投稿されないのですか?
posfan12 2010

こんにちは@ posfan12-質問への回答は通常、「クリームが上に上がる」ように「賛成票」の順にリストされています。ただし、同じスコアの回答を公平に表示するために、ランダムな順序で表示されます。これはもともと私の質問だったので;-)間違いなくすぐに確認するでしょう。この回答が役に立ったと私(または他の人)が賛成票を投じ、「正しい」回答であるとわかった場合は、この回答に緑色のチェックマークも追加されます。-StackOverflowへようこそ!
scunliffe 2010

2
-1このLCG実装seedobj[0] * seedobjaは、2 ^ 53より大きい数値になる可能性が高いため、JavaScriptの正確な整数の制限を破ります。その結果、出力範囲が制限され、一部のシードでは非常に短い期間になる可能性があります。
aaaaaaaaaaaa 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.