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


372

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


異なるテスト実行で同じ結果を繰り返し得るようにシードするのか、使用ごとのランダム性を高めるためにユーザーごとに「ユニークなもの」をシードするのかは明確ではありません。
simbo1905 2014年

2
いいえ、残念ながらできません。jsrandは、シード可能なPRNGが必要になったときに書いた小さなライブラリです。あなたがそれのためにグーグルを見つけることができる他のより複雑なライブラリもあります。
ドメニコデフェリーチェ

4
質問に追加してください:それをシードする手段なしでPRNGを提供することはどのようにして良い考えでしょうか?これには正当な理由がありますか?
アラン

回答:


188

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

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


159

注:簡潔さおよび見かけ上の優雅さにもかかわらず(またはむしろそのため)、このアルゴリズムは決してランダム性に関して高品質のものではありません。より良い結果を得るために、たとえばこの回答にリストされているものを探してください。

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

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

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

このソリューションの優雅さは、私の意見では、あなたは奇妙なパターンを避けるために捨てなければならない数字の最小量について表し、10000以外の(任意の「マジック」の数字の欠如から来ている-値の結果を参照101001000)。簡潔さもいいです。

Math.random()よりも少し遅い(2倍または3倍)が、JavaScriptで作成された他のソリューションとほぼ同じ速さだと思います。


20
このRNGが均一に分散された数値を生成することを証明する方法はありますか?実験的には:jsfiddle.net/bhrLT
Nathan Breit

6
1秒あたり6,000,000 opsはかなり高速です。クリックあたり最大3,000,000以上を生成する予定はありません。冗談です、これは素晴らしいです。
AMK

59
-1、これはまったく同じサンプラーではありません。0と1にかなり偏っています(jsfiddle.net/bhrLT/17を参照してください。計算に時間がかかる場合があります)。連続する値には相関があります。355の値ごとに、さらに710の値ごとに関連があります。もっと考え抜かれたものをご利用ください!
スペンサーネルソン2014年

37
問題は、暗号化された安全な乱数ジェネレータを作成することではなく、JavaScriptで機能し、クイックデモなどに役立つものです。この目的のために、100万個以上の乱数に見栄えの良い分布を与える、すばやく簡単なものを取り上げます。
Jason Goemaat 2014年

15
注意してください。Math.sin()は、クライアントとサーバーで異なる結果を与える可能性があります。Meteorを使用しています(クライアントとサーバーでJavaScriptを使用しています)。
オビワーン2015年

145

プレーンなJavaScriptで、多くの優れた短くて高速な擬似乱数ジェネレーター(PRNG)関数を実装しました。それらのすべてを播種し、良質の数を提供することができます。

まず、PRNGを適切に初期化するように注意してください。以下のほとんどのジェネレータには、シード生成手順が組み込まれていない(簡単にするため)が、PRNGの初期状態として1つ以上の32ビット値を受け入れます。類似のシード(たとえば、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;
    }
}

各後続の呼復帰機能のは、xmur3PRNGシードとして使用される新しい「ランダム」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();

または、シードを埋め込むダミー​​データを選択し、ジェネレータを数回(12〜20回)進めて、初期状態を完全に混合します。これは、PRNGのリファレンス実装でよく見られますが、初期状態の数を制限します。

var seed = 1337 ^ 0xDEADBEEF; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
var rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
for (var i = 0; i < 15; i++) rand();

これらのPRNG関数の出力は、32ビットの正の数値(0から2 32 -1)を生成しMath.random()、乱数が必要な場合は、と同等の0-1(0を含む、1を含まない)の間の浮動小数点数に変換されます。特定の範囲のMDNに関するこの記事を読んでください。生ビットのみが必要な場合は、最後の除算演算を削除してください。

注意すべきもう1つの点は、JSの制限です。数値は、53ビットの解像度までの整数のみを表すことができます。また、ビット単位の演算を使用する場合、これは32に削減されます。これにより、64ビットの数値を使用するCまたはC ++で記述されたアルゴリズムの実装が困難になります。64ビットコードの移植には、パフォーマンスを大幅に低下させる可能性のあるシムが必要です。そのため、単純さと効率のために、JSと直接互換性があるため、32ビット演算を使用するアルゴリズムのみを検討しました。

次に、ジェネレーターに進みます。(私はここに参照付きの完全なリストを維持しています


sfc32(シンプルな高速カウンター)

sfc32は、PractRand乱数テストスイート(もちろん合格)の一部です。sfc32は128ビットの状態を持ち、JSでは非常に高速です。

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は32ビット状態のシンプルなジェネレーターですが、非常に高速で品質が優れています(著者は、gjrandテストスイートのすべてのテストに合格し、完全な2 32期間を持っていますが、確認していません)。

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;
    }
}

あなただけの単純だが必要な場合、私はこれをお勧めしますまともな PRNGをし、(参照乱数の十億を必要としない誕生日の問題を)。

xoshiro128 **

2018年5月の時点で、xoshiro128 **はVigna / Blackman(Chromeで使用されるxoroshiroも書いた)による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;
    }
}

著者は、ランダム性テストによく合格していると主張しています(ただし、警告があります)。他の研究者は、TestU01の一部のテスト(特に、LinearCompおよびBinaryRank)に失敗することを指摘しています。実際には、フロートが使用されている場合(これらの実装など)に問題が発生することはありませんが、生の下位ビットに依存している場合は問題が発生する可能性があります。

JSF(ジェンキンスのスモールファスト)

これは、ISAACSpookyHashを作成したBob Jenkins(2007)によるJSFまたは「smallprng」です。それは合格ではないが早くSFCとして、PractRandテストをして、非常に高速である必要があります。

function jsf32(a, b, c, d) {
    return function() {
        a |= 0; b |= 0; c |= 0; d |= 0;
        var t = a - (b << 27 | b >>> 5) | 0;
        a = b ^ (c << 17 | c >>> 15);
        b = c + d | 0;
        c = d + t | 0;
        d = a + t | 0;
        return (d >>> 0) / 4294967296;
    }
}

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

LCGは非常に高速でシンプルですが、そのランダム性の品質は非常に低いため、不適切に使用するとプログラムにバグが発生する可能性があります。それにもかかわらず、使用に示唆いくつかの答えよりも有意に良好ですMath.sinMath.PI!ワンライナーですが、素晴らしいです:)。

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

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

それは機能しますが、本当に速度必要で、ランダム性の品質(とにかくランダムとは何ですか?)を気にしない限り、私はそれを使用しません。ゲームジャムやデモなどに最適です。LCG はシード相関の影響を受けるため、LCGの最初の結果破棄するのが最善です。また、LCGの使用を主張する場合は、増分値を追加すると結果が向上する可能性がありますが、はるかに優れたオプションが存在する場合、それはおそらく無益な練習になります。

32ビットの状態を提供する他の乗算器があるようです(状態空間の増加)。

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

これらのLCG値は次のとおりです。P。L'Ecuyer:1997年4月30日、さまざまなサイズと適切な格子構造の線形合同生成器の表。


5
これは素晴らしい答えです。私は確かにこれに戻ってくるでしょう。
DavidsKanal

1
Pierre L'ecuyerによる「Table of Linear Congruential Generators ...」から引用した値は、Javascriptの最大整数サイズを超える可能性があると思います。(2 ^ 32-1)* 741103597 74 3e18の最大シード。これは、JavaScriptの最大intサイズである≈9e15より大きいです。ピエールの本の次の値は、ネイティブの制限内で最大の期間を持っていると思いますseed = (seed * 185852 + 1) % 34359738337
Lachmanski

1
@Lachmanskiはtrueですが、これらは32ビット(およびPark-Miller 31ビット)によってバインドされています。を使用Math.imulすると、32ビット整数でCの乗算を使用する場合と同様にオーバーフローできます。あなたが提案しているのは、JSの整数空間の全範囲を利用するLCGです。これは、同様に探求する興味深い領域です。:)
bryc

1
これはすごい!sfc32をLGPLプログラムにコピーできますか?
user334639

4
@ blobber2はどういう意味かわかりませんが、元のコードはgithub.com/bryc/code/blob/master/jshash/PRNGs.mdです。リポジトリ内の要点:-)
bryc

39

いいえ、ただし、これは単純な疑似ランダムジェネレーターです。ウィキペディアから改造したMultiply-with-carryの実装です(その後削除されました)。

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をリセットすることでシード関数を修正しました編集
2:重大な実装の欠陥が修正されました


3
この関数をランダム性についてテストした人はいますか?
Justin

3
これは、かなり長い周期を持つキャリー付き乗算(MWC)ランダムジェネレーターです。wikipedia Random Number Generators
Michael_Scharf 14

10
seedので、この関数は、乱数発生器をリセットしていないmz_zとき、変数が変更されるrandom()と呼ばれています。したがって、次のように設定しますmz_z = 987654321(またはその他の値)seed
Michael_Scharf 14

ランダムカラージェネレーター(HSL)で使用すると、緑色とシアン色のみが生成されます。元のランダムジェネレーターはすべての色を生成します。したがって、同じではないか、機能しません。
Tomas Kubes 14

@Michael_Scharf 1)m_wではなく、シードの変更m_z。2)両方m_wm_zそれらの以前の値に基づいて変化するので、結果を変更しません。
ESL 2015

26

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にはない別の機能が提供されます。複数の独立したランダムジェネレーターです。複数の再現可能なシミュレーションを同時に実行したい場合、これは特に重要です。


3
設定する代わりに関数を返すと、Math.random複数の独立したジェネレータを持つことができますよね?
Jason Goemaat

1
あなたにその事柄ならばランダム性の程度分布上記のコメントを参照してください:stackoverflow.com/questions/521295/...
jocull

これによって生成されたランダムはどのように繰り返すことができますか?それは毎回新しい数字を出し続けます
SMUsamaShah

あなたがするたびにMath.seed(42);、それはあなたがそうならば、機能をリセットし、var random = Math.seed(42); random(); random();あなたが得る0.70...それから、0.38...var random = Math.seed(42);もう一度電話をかけてリセットした場合は、次に電話random()をかけたときに0.70...再び電話がかかり、次に電話をかけたときに電話がかかります0.38...
WOUNDEDStevenJones 2017

1
これは使用しないでください。random代わりに、ネイティブのJavaScript関数を上書きするのではなく、名前付きのローカル変数を使用してください。上書きMath.randomすると、JISTコンパイラがすべてのコードを最適化しなくなる可能性があります。
ジャックギ

11

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

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

フィル・トロイ


1
すばらしい答えですが、javascriptとは関係ありません:)
Nikolay Fominyh 2017年

3
L'Ecuyer教授の研究を実装するためのコードはjavaで公開されており、ほとんどのプログラマーがJavascriptに簡単に変換できます。
user2383235 2017年

6

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

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();

4
これにより、異なるシードを使用してシーケンスの最初に非常に類似した結果が生成されます。たとえば、をMath.seed(0)()返し0.2322845458984375、をMath.seed(1)()返します0.23228873685002327。両方m_wを変更m_zし、シードに応じて変更すると効果があるようです。 var m_w = 987654321 + s; var m_z = 123456789 - s;異なるシードを持つ最初の値の適切な分布を生成します。
未定義の

1
@undefinedあなたが説明した問題は、最後の編集の時点で修正されています。これは、MWC実装のバグでした。
bryc

2020年1月現在、正常に機能しています。0をシードとして、0.7322976540308446を取得します。1、0.16818441334180534、2:0.6040864314418286、3:0.03998844954185188のシード。あなたがた両方に感謝します!
ユーレカ

3

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

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

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


私は専門家ではありませんが、シーケンシャルシードは一定のパターンに従います。色付きのピクセルは0.5以上です。私はその半径を何度も何度も繰り返していると思いますか?
bryc 2017年



-1

シードされた乱数を返す関数を作成しました。Math.sinを使用して長い乱数を取得し、シードを使用してそこから数値を選択します。

使用する :

seedRandom("k9]:2@", 15)

最初のパラメータが任意の文字列値であるシードされた数を返します。あなたの種。2番目のパラメーターは、返される桁数です。

     function seedRandom(inputSeed, lengthOfNumber){

           var output = "";
           var seed = inputSeed.toString();
           var newSeed = 0;
           var characterArray = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','y','x','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','U','R','S','T','U','V','W','X','Y','Z','!','@','#','$','%','^','&','*','(',')',' ','[','{',']','}','|',';',':',"'",',','<','.','>','/','?','`','~','-','_','=','+'];
           var longNum = "";
           var counter = 0;
           var accumulator = 0;

           for(var i = 0; i < seed.length; i++){
                var a = seed.length - (i+1);
                for(var x = 0; x < characterArray.length; x++){
                     var tempX = x.toString();
                     var lastDigit = tempX.charAt(tempX.length-1);
                     var xOutput = parseInt(lastDigit);
                     addToSeed(characterArray[x], xOutput, a, i); 
                }                  
           }

                function addToSeed(character, value, a, i){
                     if(seed.charAt(i) === character){newSeed = newSeed + value * Math.pow(10, a)}
                }
                newSeed = newSeed.toString();

                var copy = newSeed;
           for(var i=0; i<lengthOfNumber*9; i++){
                newSeed = newSeed + copy;
                var x = Math.sin(20982+(i)) * 10000;
                var y = Math.floor((x - Math.floor(x))*10);
                longNum = longNum + y.toString()
           }

           for(var i=0; i<lengthOfNumber; i++){
                output = output + longNum.charAt(accumulator);
                counter++;
                accumulator = accumulator + parseInt(newSeed.charAt(counter));
           }
           return(output)
      }

1
これによって生成される数列は、乱数列の特性を実際には近似しません。それを使用して15の数値を生成すると、たとえば、ほとんどすべてのキーに対して、結果の文字列はほとんど常に7で始まります。
ガブリエル


-6

0から100までの数値。

Number.parseInt(Math.floor(Math.random() * 100))

3
問題は、同じシードがシードさMath.randomれるたびにMath.random、同じ一連の乱数が生成されるようにシードすることです。この質問は、言うまでもなく、の実際の使用法/デモについてではありませんMath.random
ジャックギフィン2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.