Seeding Random Number Generators in JavaScript

Nov 21, 2025 · Programming · 7 views · 7.8

Keywords: JavaScript | Random Number | Seeding | PRNG | Math.random

Abstract: This article explores the inability to seed the built-in Math.random() function in JavaScript and provides comprehensive solutions using custom pseudorandom number generators (PRNGs). It covers seed initialization techniques, implementation of high-quality PRNGs like sfc32 and splitmix32, and performance considerations for applications requiring reproducible randomness.

Introduction

In many programming contexts, such as game development, simulations, and automated testing, the ability to generate reproducible sequences of random numbers is crucial. This is typically achieved by seeding the random number generator (RNG), which initializes its internal state to produce predictable outputs. However, in JavaScript, the standard Math.random() function does not support seeding, necessitating alternative approaches for controlled randomness.

Limitations of Math.random()

The Math.random() function in JavaScript returns a floating-point number between 0 (inclusive) and 1 (exclusive), but the ECMAScript specification intentionally omits any provision for seeding. This means that implementations across different browsers may use varying algorithms, and developers cannot rely on Math.random() for repeatable random sequences. As a result, for scenarios requiring seeded randomness, external or custom solutions must be employed.

Custom PRNG Solutions

To address the lack of seeding in Math.random(), developers can implement their own pseudorandom number generators (PRNGs) or utilize existing libraries. PRNGs are deterministic algorithms that generate sequences of numbers that appear random but are derived from an initial seed value. By controlling the seed, the same sequence can be reproduced across multiple runs, which is essential for debugging, testing, and consistent user experiences.

Seed Initialization

Proper seed initialization is vital for ensuring high-quality randomness in PRNGs. Seeds with low entropy, such as simple numeric values, can lead to correlations and predictable patterns in the output. To mitigate this, it is recommended to use well-distributed, high-entropy seeds. One effective method involves employing hash functions to generate seeds from strings, as they can produce significantly different outputs even for similar inputs. For instance, the cyrb128 hash function can compute a 128-bit hash from a string, which serves as a robust seed for PRNGs.

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

This hash output can then be used to initialize PRNG states. Alternatively, seeds can be padded with constants like those derived from mathematical constants (e.g., phi, pi) and advanced through several iterations to mix the state thoroughly, though this may limit the diversity of initial states.

PRNG Implementations

Several high-quality PRNGs can be implemented in JavaScript to provide seeded randomness. Below are examples of commonly used generators, each with unique characteristics in terms of state size, speed, and randomness quality. These implementations use 32-bit operations for compatibility with JavaScript's number system, where bitwise operations are optimized for 32-bit integers.

sfc32 (Simple Fast Counter)

sfc32 is a PRNG with a 128-bit state that passes the PractRand test suite and offers high performance in JavaScript. It is suitable for applications requiring a large period and good statistical properties.

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

Usage example: Initialize with a seed from cyrb128 and generate random numbers.

const seed = cyrb128("example");
const rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
console.log(rand()); // Outputs a number between 0 and 1

splitmix32

splitmix32 is a 32-bit state PRNG known for its simplicity and high randomness quality, derived from hash function principles. It is efficient for most non-cryptographic uses.

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

Other PRNGs, such as mulberry32, xoshiro128**, and jsf32, provide additional options with varying trade-offs in speed and quality. For instance, mulberry32 is extremely fast with a 32-bit state, while xoshiro128** offers a 128-bit state and high speed but may have limitations in certain statistical tests.

Performance and Quality Considerations

JavaScript handles numbers as 64-bit floating-point values, but bitwise operations are limited to 32-bit integers, which can impact performance if not optimized. The PRNGs discussed here leverage 32-bit operations to maintain efficiency, as switching between float and integer modes can cause slowdowns. It is important to note that these generators are not suitable for cryptographic purposes, as they lack the security guarantees of cryptographically secure PRNGs (CSPRNGs). For applications demanding high entropy and security, alternatives like ISAAC should be considered. Additionally, the quality of randomness can be assessed through testing suites like PractRand or TestU01, and developers should avoid common pitfalls such as using low-entropy seeds or insufficient state mixing.

Conclusion

While JavaScript's Math.random() does not support seeding, developers can effectively implement seeded PRNGs using custom functions. By focusing on proper seed initialization and selecting appropriate algorithms, such as sfc32 or splitmix32, it is possible to achieve reproducible and high-quality random sequences for various applications. Libraries like seedrandom can further simplify this process, offering pre-built solutions for seeded randomness. This approach ensures flexibility and control in scenarios where deterministic random behavior is required.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.