web-dev-qa-db-ja.com

Javascriptでの乱数ジェネレーターのシード

Javascriptで乱数ジェネレーター(Math.random)をシードすることは可能ですか?

311
weepy

いいえ、そうではありませんが、独自のジェネレーターを作成するか、既存のジェネレーターを使用する方がかなり簡単です。チェックアウト: この関連する質問

また、 シードの詳細 についてはDavid Bauのブログを参照してください。

166
PeterAllenWebb

注:簡潔さと見かけの優雅さにもかかわらず(というよりむしろ)、このアルゴリズムはランダム性に関して高品質なものではありません。を探しますより良い結果を得るために this answer にリストされているもの。

(元々、コメントで提示された巧妙なアイデアから別の回答へと適応しました。)

var seed = 1;
function random() {
    var x = Math.sin(seed++) * 10000;
    return x - Math.floor(x);
}

seedを任意の数に設定できますが、ゼロ(またはMath.PIの倍数)を避けてください。

私の意見では、この解決策の優雅さは、「魔法の」数字がないことに起因しています(10000を除く。これは、奇妙なパターンを避けるために捨てなければならない最小桁数を表します-値の結果を参照してください 1110 )。簡潔さも素晴らしい。

Math.random()より少し遅い(2または3倍)が、JavaScriptで書かれた他のソリューションとほぼ同じくらい速いと思う。

142

プレーンなJavaScriptで、コピー、貼り付け可能なPRNG関数をいくつか実装しました。それらはすべて播種でき、良質の数値を提供できます。

まず、PRNGを適切に初期化するように注意してください。ほとんどのジェネレーターには組み込みのシード生成手順はありませんが、1つ以上の32ビット値を初期値として受け入れますPRNGの状態。同様のシード(たとえば、1と2のシード)は、より弱いPRNGで相関を引き起こす可能性があり、その結果、出力が類似した特性(ランダムに生成されたレベルが類似しているなど)になります。これを回避するには、十分に分散されたシードでPRNGを初期化することがベストプラクティスです。

ありがたいことに、ハッシュ関数は短い文字列からPRNGのシードを生成するのに非常に優れています。優れたハッシュ関数は、2つの文字列が似ている場合でも、非常に異なる結果を生成します。 MurmurHash3のミキシング機能に基づいた例を次に示します。

function xmur3(str) {
    for(var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
        h = Math.imul(h ^ str.charCodeAt(i), 3432918353),
        h = h << 13 | h >>> 19;
    return function() {
        h = Math.imul(h ^ h >>> 16, 2246822507);
        h = Math.imul(h ^ h >>> 13, 3266489909);
        return (h ^= h >>> 16) >>> 0;
    }
}

Return関数への以降の各呼び出しは、PRNGのシードとして使用される新しい「ランダムな」32ビットハッシュ値を生成します。使用方法は次のとおりです。

// Create xmur3 state:
var seed = xmur3("apples");
// Output four 32-bit hashes to provide the seed for sfc32.
var Rand = sfc32(seed(), seed(), seed(), seed());

// Output one 32-bit hash to provide the seed for mulberry32.
var Rand = mulberry32(seed());

// Obtain sequential random numbers like so:
Rand();
Rand();

これはもちろん機能的なJSですが、オブジェクト化することもできます。

もう1つ注意すべき点は、これらはすべて32ビットジェネレーターであるということです。つまり、すべてが32ビットCコードをシミュレートする32ビット操作にバインドされています。 32ビット整数は最新のJSエンジンで少し最適化を促進するため、これはまともな妥協策であることがわかりました。 JSは32ビットのビット演算のみを実行でき、とにかく64ビットの演算も実行できません。 JSには53ビット整数の制限がありますが、それでも効率的に利用するには多くの策略が必要です。そのため、Baagøeの超高速53ビット Alea generator は、これらの実装よりも遅いslowになります。

商品(発電機)に向かって。


sfc32

このgemは、PractRand乱数テストスイートから来ており、問題なく合格しています。 PractRandは、TestU01よりもさらに厳しいと言われています。 sfc32 は128ビット状態であり、JSでも非常に高速です(xoshiro128 **はわずかに高速ですが、品質は劣ります)。おそらく私のPRNGが選択されます。

function sfc32(a, b, c, d) {
    return function() {
      a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; 
      var t = (a + b) | 0;
      a = b ^ b >>> 9;
      b = c + (c << 3) | 0;
      c = (c << 21 | c >>> 11);
      d = d + 1 | 0;
      t = t + d | 0;
      c = c + t | 0;
      return (t >>> 0) / 4294967296;
    }
}

マルベリー32

Mulberry32は非常に高速で、品質も優れています(著者は gjrand のすべてのテストに合格しています)。シンプルだがまともなPRNGが必要な場合にこれをお勧めします。

32ビットの状態と2の全期間があります32。 1つの32ビット整数でシードするだけで、 誕生日の問題 を気にしない場合に最適です。 Mulberry32には、sfc32/xoshiro128 **の340 undecillionに対して43億の州があります。

function mulberry32(a) {
    return function() {
      var t = a += 0x6D2B79F5;
      t = Math.imul(t ^ t >>> 15, t | 1);
      t ^= t + Math.imul(t ^ t >>> 7, t | 61);
      return ((t ^ t >>> 14) >>> 0) / 4294967296;
    }
}

xoshiro128 **

2018年5月現在、 xoshiro128 ** Xorshiftファミリ の新しいメンバーです。 128ビットの状態を提供し、超高速です。

function xoshiro128ss(a, b, c, d) {
    return function() {
        var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9;
        c ^= a; d ^= b;
        b ^= c; a ^= d; c ^= t;
        d = d << 11 | d >>> 21;
        return (r >>> 0) / 4294967296;
    }
}

このPRNGは、2015年にGoogle Chromeで使用されていたPRNG xorshift128 +およびxoroshiroを書いたBlackman/Vignaによる最新のものです。 32ビットバージョン。 xoroshiro64 ** も有望なオプションですが、64ビット状態しかなく、ほとんどがxoshiroに置き換えられました。

著者は、ランダム性テストに十分合格していると主張しています( 警告あり) 。他の研究者は、BigCrush(特にLinearCompとBinaryRank)のいくつかのテストに失敗することを指摘しています。ただし、実際には、これらのPRNGのように32ビット値が0〜1の浮動小数点数に変換される場合は特に問題になりません。ただし、下位ビットに依存している場合は問題が発生する可能性があります。

JSF

これはJSF、または ISAACSpookyHash を作成したBob Jenkins(2007)による「smallprng」です。それは、PractRandテストで うまく機能する であり、非常に高速であるはずです。平均期間の長さは2 ^ 126であると想定されていますが、「正式に決定されていません」。

function JSF(seed) {
    function jsf() {
        var e = s[0] - (s[1]<<27 | s[1]>>>5);
         s[0] = s[1] ^ (s[2]<<17 | s[2]>>>15),
         s[1] = s[2] + s[3],
         s[2] = s[3] + e, s[3] = s[0] + e;
        return (s[3] >>> 0) / 4294967296; // 2^32
    }
    seed >>>= 0;
    var s = [0xf1ea5eed, seed, seed, seed];
    for(var i=0;i<20;i++) jsf();
    return jsf;
}

このバージョンには、個別のシード関数は必要ありません。ただし、結果として、シードできるのは32ビットのみであり、このバージョンはjsf()を20回事前実行して初期状態を分散させますが、これにはコストがかかる場合があります。

必要に応じて、128ビット状態全体を直接初期化して、forループを削除できます。筆者は、与えられた構成で可能なすべての32ビットシードのサイクル長を検証したため、元の構成を維持することにしました。

LCG(別名Lehmer/Park-Miller RNGまたはMLCG)

これは、プラットフォーム間で一貫性や信頼性が低いMath.sinMath.PIメソッドなど、他の回答で言及されているオプションに対するより優れた代替手段を提供するためだけのものです。このLCGの実装は非常に高速ですが、31ビットの状態しかなく、前述のジェネレーターが飛行色で合格する統計テストの一部に失敗します。しかし、それはワンライナーです。これはニースです:)。

var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;

これは、 Park–Miller in 1988&199 によって提案され、C++ 11でminstd_Randとして実装された最小標準RNGです。状態と期間は31ビットのみであることに注意してください(31ビットは20億の可能な状態を与え、32ビットはその2倍を与えます)。これは、他の人が置き換えようとしているPRNGのまさにタイプです。

動作しますが、本当に速度が必要でランダム性の品質(とにかくランダムとは何ですか?)または31ビットの場合を除いて、私はそれを使用しません状態/期間のサイズがわずらわしい。ゲームジャムやデモなどに最適です。また、LCGはシード相関の影響を受けるため、 LCGの最初の結果を破棄するのが最善です。

完全な32ビット状態を提供する他の乗算器があるようです。これらがPark-Millerよりも統計的に良い/悪いのかどうかはわかりませんが、ここでは完全を期しています。

var LCG=s=>()=>((s=Math.imul(741103597,s))>>>0)/2**32;
var LCG=s=>()=>((s=Math.imul(1597334677,s))>>>0)/2**32;

これらの乗数はPからのものです。 L'Ecuyer:さまざまなサイズと良好な格子構造の線形合同生成器の表、1997年4月30日。

65
bryc

いいえ、しかし、これは単純な擬似乱数ジェネレーターであり、 Multiply-with-carry の実装です Wikipedia から適応しました(以降削除されました):

var m_w = 123456789;
var m_z = 987654321;
var mask = 0xffffffff;

// Takes any integer
function seed(i) {
    m_w = (123456789 + i) & mask;
    m_z = (987654321 - i) & mask;
}

// Returns number between 0 (inclusive) and 1.0 (exclusive),
// just like Math.random().
function random()
{
    m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
    m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
    var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
    result /= 4294967296;
    return result;
}

編集:m_zをリセットすることによりシード関数を修正
EDIT2:重大な実装の欠陥が修正されました

38

AnttiSykäriのアルゴリズムは素晴らしく、短いです。最初に、Math.seed(s)を呼び出すときにJavascriptのMath.randomを置き換えるバリエーションを作成しましたが、Jasonは関数を返す方が良いとコメントしました。

Math.seed = function(s) {
    return function() {
        s = Math.sin(s) * 10000; return s - Math.floor(s);
    };
};

// usage:
var random1 = Math.seed(42);
var random2 = Math.seed(random1());
Math.random = Math.seed(random2());

これにより、Javascriptにはない別の機能が提供されます。複数の独立したランダムジェネレーターです。これは、複数の反復可能なシミュレーションを同時に実行する場合に特に重要です。

25

1980年代後半から1990年代初頭に遡るPierre L'Ecuyerの作品をご覧ください。他にもあります。専門家ではない場合、自分で(擬似)乱数ジェネレーターを作成することは、結果が統計的にランダムではないか、短い期間である可能性が高いため、かなり危険です。 Pierre(およびその他)は、実装しやすいいくつかの優れた(疑似)乱数ジェネレーターを作成しました。私は彼のLFSRジェネレーターの1つを使用します。

https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf

フィルトロイ

10
user2383235

以前の回答のいくつかを組み合わせて、これはあなたが探しているシード可能なランダム関数です:

Math.seed = function(s) {
    var mask = 0xffffffff;
    var m_w  = (123456789 + s) & mask;
    var m_z  = (987654321 - s) & mask;

    return function() {
      m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
      m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;

      var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
      result /= 4294967296;
      return result;
    }
}

var myRandomFunction = Math.seed(1234);
var randomNumber = myRandomFunction();
3
user3158327

独自の擬似ランダムジェネレータを作成するのは非常に簡単です。

Dave Scoteseの提案は有用ですが、他の人が指摘したように、完全に均一に分布しているわけではありません。

ただし、sinの整数引数のためではありません。それは単に、罪の範囲が原因であり、これはたまたま円の1次元投影です。代わりに円の角度をとると、均一になります。

したがって、sin(x)の代わりにarg(exp(i * x))/(2 * PI)を使用します。

線形順序が気に入らない場合は、xorと少し混ぜてください。実際の要因もそれほど重要ではありません。

N個の擬似乱数を生成するには、次のコードを使用できます。

function psora(k, n) {
  var r = Math.PI * (k ^ n)
  return r - Math.floor(r)
}
n = 42; for(k = 0; k < n; k++) console.log(psora(k, n))

また、実際のエントロピーが必要な場合、擬似ランダムシーケンスを使用できないことに注意してください。

3
Lajos Bodrogi

最近JavaScriptでシード可能な乱数ジェネレータを必要とする多くの人々は、 David Bauのseedrandomモジュール を使用しています。

3
Martin Omander

Math.randomいいえ、しかし ranライブラリ はこれを解決します。想像できるほぼすべての分布があり、シード乱数生成をサポートしています。例:

ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
0
Ulf Aslak