Seeding the random number generator in Javascript Ask Question

Seeding the random number generator in Javascript Ask Question

Is it possible to seed the random number generator (Math.random) in JavaScript?

ベストアンサー1

No, it is not possible to seed Math.random(). The ECMAScript specification is intentionally vague on the subject, providing no means for seeding nor require that browsers even use the same algorithm. So such a function must be externally provided, which thankfully isn't too difficult.

I've implemented a number of good, short and fast Pseudorandom number generator (PRNG) functions in plain JavaScript. All of them can be seeded and provide high quality numbers. These are not intended for security purposes--if you need a seedable CSPRNG, look into ISAAC.

First of all, take care to initialize your PRNGs properly. To keep things simple, the generators below have no built-in seed generating procedure, but accept one or more 32-bit numbers as the initial seed state of the PRNG. Similar or sparse seeds (e.g. a simple seed of 1 and 2) have low entropy, and can cause correlations or other randomness quality issues, sometimes resulting in the output having similar properties (such as randomly generated levels being similar). To avoid this, it is best practice to initialize PRNGs with a well-distributed, high entropy seed and/or advancing past the first 15 or so numbers.

There are many ways to do this, but here are two methods. Firstly, hash functions are very good at generating seeds from short strings. A good hash function will generate very different results even when two strings are similar, so you don't have to put much thought into the string. Here's an example hash function:

function cyrb128(str) {
    let h1 = 1779033703, h2 = 3144134277,
        h3 = 1013904242, h4 = 2773480762;
    for (let i = 0, k; i < str.length; i++) {
        k = str.charCodeAt(i);
        h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
        h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
        h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
        h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
    }
    h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
    h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
    h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
    h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
    h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
    return [h1>>>0, h2>>>0, h3>>>0, h4>>>0];
}
// Side note: Only designed & tested for seed generation,
// may be suboptimal as a general 128-bit hash.

Calling cyrb128 will produce a 128-bit hash value from a string which can be used to seed a PRNG. Here's how you might use it:

// Create cyrb128 state:
var seed = cyrb128("apples");
// Four 32-bit component hashes provide the seed for sfc32.
var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);

// Or... only one 32-bit component hash is needed for splitmix32.
var rand = splitmix32(seed[0]);

// You can now generate repeatable sequences of random numbers:
rand(); // 0.8865117691457272
rand(); // 0.24518639338202775

Note: If you want a slightly more robust 128-bit hash, consider MurmurHash3_x86_128, it's more thorough, but intended for use with large arrays. Since this operates on byte arrays, strings require conversion using something like Array.from("hello", c=>c.charCodeAt()).

Alternatively, simply choose some dummy data to pad the seed with, and advance the generator beforehand a few times (12-20 iterations) to mix the initial state thoroughly. This has the benefit of being simpler, and is often used in reference implementations of PRNGs, but it does limit the number of initial states:

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

Note: the output of these PRNG functions produce a positive 32-bit number (0 to 232-1) which is then converted to a floating-point number between 0-1 (0 inclusive, 1 exclusive) equivalent to Math.random(), if you want random numbers of a specific range, read this article on MDN. If you only want the raw bits, simply remove the final division operation.

JavaScript の数値は、53 ビット解像度までの整数しか表せません。ビット単位の演算を使用する場合、これは 32 に縮小されます。他の言語の最新の PRNG は、多くの場合 64 ビット演算を使用しますが、JS に移植するときにシムが必要になり、パフォーマンスが大幅に低下する可能性があります。ここでのアルゴリズムは、JS と直接互換性があるため (したがってパフォーマンスが高いため)、32 ビット演算のみを使用します。では、ジェネレーターに進みます。

(私は参考文献とライセンス情報を含む完全なリストを維持していますここ


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

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

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

const seedgen = () => (Math.random()*2**32)>>>0;
const getRand = sfc32(seedgen(), seedgen(), seedgen(), seedgen());
for(let i=0; i<10; i++) console.log(getRand());

補足: と| 0|= 0用途が何なのか疑問に思うかもしれません。これらは基本的に 32 ビット整数キャストであり、パフォーマンスの最適化に使用されます。JSNumberの は基本的に浮動小数点数ですが、ビット演算中に 32 ビット整数モードに切り替わります。このモードは JS エンジンによってより高速に処理されますが、乗算または加算を行うと浮動小数点数に戻り、パフォーマンスが低下します。

スプリットミックス32

MurmurHash3のミキシング関数を採用し、増分器を追加し、定数を調整して作成された32ビット状態PRNG。これは、これまでで最も優れた32ビットPRNGの1つになる可能性があります。Mulberry32の作者でさえ、それがより良い選択だと考える速度も同様に速いです。

function splitmix32(a) {
 return function() {
   a |= 0;
   a = a + 0x9e3779b9 | 0;
   let t = a ^ a >>> 16;
   t = Math.imul(t, 0x21f0aaad);
   t = t ^ t >>> 15;
   t = Math.imul(t, 0x735a2d97);
   return ((t = t ^ t >>> 15) >>> 0) / 4294967296;
  }
}

const prng = splitmix32((Math.random()*2**32)>>>0)
for(let i=0; i<10; i++) console.log(prng());

シンプルだが優れたPRNGが必要で、数十億の乱数を必要としない場合は、これをお勧めします(誕生日の問題)。

マルベリー32

Mulberry32は32ビットの状態を持つ単純なジェネレータですが、非常に高速で許容できる品質のランダム性を持っています(著者は、すべてのテストに合格したと述べています)。グランドテスト スイートには 2 32 の完全な期間がありますが、検証していません。

function mulberry32(a) {
  return function() {
    let 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;
  }
}

const getRand = mulberry32((Math.random()*2**32)>>>0)
for(let i=0; i<10; i++) console.log(getRand());

よっしゃ

xoshiro128** (Vigna & Blackman, 2018)は、オルシフト系譜 (Vigna は、ほとんどの実装を裏で動かす Xorshift128+ アルゴリズムも担当しましたMath.random)。これは、128 ビットの状態を提供する最速のジェネレータです。

function xoshiro128ss(a, b, c, d) {
  return function() {
    let t = b << 9, r = b * 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;
  }
}

const seedgen = () => (Math.random()*2**32)>>>0;
const getRand = xoshiro128ss(seedgen(), seedgen(), seedgen(), seedgen());
for (let i = 0; i < 10; i++) console.log(getRand());

著者らは、ランダム性テストに合格したと主張している(ただし、注意点がある)。他の研究者は、TestU01 のいくつかのテスト (特に LinearComp と BinaryRank) に失敗すると指摘しています。実際には、浮動小数点数が使用されている場合 (これらの実装など) は問題は発生しないはずですが、生の最下位ビットに依存する場合は問題が発生する可能性があります。

JSF (ジェンキンスの小さなファスト)

これはボブ・ジェンキンス(2007)によるJSFまたは「smallprng」であり、彼はまたアイザックそしてスプーキーハッシュ。 それパスPractRand でテストすると、sfc32 ほど速くはありませんが、かなり高速になるはずです。

function jsf32(a, b, c, d) {
  return function() {
    a |= 0; b |= 0; c |= 0; d |= 0;
    let 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;
  }
}

const seedgen = () => (Math.random()*2**32)>>>0;
const getRand = jsf32(seedgen(), seedgen(), seedgen(), seedgen());
for (let i = 0; i < 10; i++) console.log(getRand());

おすすめ記事