191

The JavaScript Math.random() function returns a random value between 0 and 1, automatically seeded based on the current time (similar to Java I believe). However, I don't think there's any way to set you own seed for it.

How can I make a random number generator that I can provide my own seed value for, so that I can have it produce a repeatable sequence of (pseudo)random numbers?

3

11 Answers 11

31

If you don't need the seeding capability just use Math.random() and build helper functions around it (eg. randRange(start, end)).

I'm not sure what RNG you're using, but it's best to know and document it so you're aware of its characteristics and limitations.

Like Starkii said, Mersenne Twister is a good PRNG, but it isn't easy to implement. If you want to do it yourself try implementing a LCG - it's very easy, has decent randomness qualities (not as good as Mersenne Twister), and you can use some of the popular constants.

EDIT: consider the great options at this answer for short seedable RNG implementations, including an LCG option.

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

3
  • 2
    Shouldn't the modulus be 2^31? I read this algorithm from wiki. Commented May 8, 2013 at 8:43
  • 3
    Just so you are aware, this is not "correct" in the sense that it doesn't output what the math dictates. On other words, a language that can handle those large numbers will have a different result. JS chokes on the big numbers and chops precision (they are floats, after all).
    – DDS
    Commented Oct 14, 2013 at 10:42
  • 6
    -1 This LCG implementation busts the limit for exact integers in JavaScript as this.a * this.state is likely to result in a number greater than 2^53. The result is a limited output range, and for some seeds possibly a very short period. Further in general using a power of two for m result in some pretty obvious patterns, when you are expending a modulus operation rather than a simple truncation anyway there is no reason not to use a prime. Commented Mar 15, 2014 at 21:53
23

If you want to be able to specify the seed, you just need to replace the calls to getSeconds() and getMinutes(). You could pass in an int and use half of it mod 60 for the seconds value and the other half modulo 60 to give you the other part.

That being said, this method looks like garbage. Doing proper random number generation is very hard. The obvious problem with this is that the random number seed is based on seconds and minutes. To guess the seed and recreate your stream of random numbers only requires trying 3600 different second and minute combinations. It also means that there are only 3600 different possible seeds. This is correctable, but I'd be suspicious of this RNG from the start.

If you want to use a better RNG, try the Mersenne Twister. It is a well tested and fairly robust RNG with a huge orbit and excellent performance.

EDIT: I really should be correct and refer to this as a Pseudo Random Number Generator or PRNG.

"Anyone who uses arithmetic methods to produce random numbers is in a state of sin."
                                                                                                                                                          --- John von Neumann

8
  • 1
    A link to JS implementations of Mersenne Twister: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
    – orip
    Commented Jan 8, 2009 at 15:11
  • 1
    @orip Do you have a source for the 3600 initial states? Mersenne Twister is seeded by a 32 bit number, so the PRNG should have 4 billion initial states - only if the initial seed is truly random.
    – Tobias P.
    Commented Aug 1, 2012 at 14:46
  • 2
    @TobiasP. I was referring to the suggestion to seed with a combination of getSeconds() and getMinutes(), 60 * 60 == 3600 possible initial states. I wasn't referring to Mersenne Twister.
    – orip
    Commented Aug 1, 2012 at 14:49
  • 3
    @orip Ok, was not clear. You were taking about Mersenne Twister and in the next sentence about inital states ;)
    – Tobias P.
    Commented Aug 1, 2012 at 15:00
  • 2
    The question asker doesn't make any mention that they need "proper" random number generation for any kind of cryptographically sensitive application. While all of the answer is true, only the first paragraph is actually relevant to the question asked. Perhaps add a code snippet of the suggested solution. Commented Jun 1, 2015 at 4:42
15

I use a JavaScript port of the Mersenne Twister: https://gist.github.com/300494 It allows you to set the seed manually. Also, as mentioned in other answers, the Mersenne Twister is a really good PRNG.

9

The code you listed kind of looks like a Lehmer RNG. If this is the case, then 2147483647 is the largest 32-bit signed integer, 2147483647 is the largest 32-bit prime, and 48271 is a full-period multiplier that is used to generate the numbers.

If this is true, you could modify RandomNumberGenerator to take in an extra parameter seed, and then set this.seed to seed; but you'd have to be careful to make sure the seed would result in a good distribution of random numbers (Lehmer can be weird like that) -- but most seeds will be fine.

8

The following is a PRNG that may be fed a custom seed. Calling SeedRandom will return a random generator function. SeedRandom can be called with no arguments in order to seed the returned random function with the current time, or it can be called with either 1 or 2 non-negative inters as arguments in order to seed it with those integers. Due to float point accuracy seeding with only 1 value will only allow the generator to be initiated to one of 2^53 different states.

The returned random generator function takes 1 integer argument named limit, the limit must be in the range 1 to 4294965886, the function will return a number in the range 0 to 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
}

Example use:

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.

This generator exhibit the following properties:

  • It has approximately 2^64 different possible inner states.
  • It has a period of approximately 2^63, plenty more than anyone will ever realistically need in a JavaScript program.
  • Due to the mod values being primes there is no simple pattern in the output, no matter the chosen limit. This is unlike some simpler PRNGs that exhibit some quite systematic patterns.
  • It discards some results in order to get a perfect distribution no matter the limit.
  • It is relatively slow, runs around 10 000 000 times per second on my machine.

Bonus: typescript version

5
  • 2
    Why does this produce a pattern? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); } Commented Dec 1, 2016 at 2:10
  • @TimothyKanski Because you are using it wrong. I am no expert but this occurs because you are initializing the generator on each iteration, only seeing its first value based on the seed, and NOT iterating on the generator's subsequent numbers. I believe this will happen in any PRNG that doesn't hash the seed across the specified interval.
    – bryc
    Commented Nov 30, 2017 at 8:32
  • @bryc - I think @TimothyKanski's was testing to see if different seeds produced different random numbers and it appears that there was a pattern - which is odd. I tested too and anything pretty well gives a pattern: let count = 0; setInterval(() => { console.log(SeedRandom(count++,count++)(10)); },500); Yields repeating 3,5,7,9,1
    – Dan Zen
    Commented Dec 22, 2021 at 17:22
  • @DanZen The point is, that the testing method is flawed and does not prove the quality of randomness. Not that the SeedRandom function is good, it's likely not. But plenty of good PRNGs will fail this test because of insufficient entropy. Using your test with different algorithms I can make a bad function pass and a good one fail: paste2.org/AkhJfgvh. The bad one only passes because it uses big multipliers.
    – bryc
    Commented Dec 23, 2021 at 22:39
  • I see, so what you are saying is that there needs to be some randomness in the seed rather than increasing it by 1 each time or something like that. Did not realize that. Cheers.
    – Dan Zen
    Commented Dec 31, 2021 at 20:11
7

If you program in Typescript, I adapted the Mersenne Twister implementation that was brought in Christoph Henkelmann's answer to this thread as a typescript class:

/**
 * 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);
    }
}

you can than use it as follows:

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]

check the source for more methods.

4

Here is quite an effective but simple javascript PRNG function that I like to use:

// The seed is the base number that the function works off
// The modulo is the highest number that the function can return

function PRNG(seed, modulo) {
    str = `${(2**31-1&Math.imul(48271,seed))/2**31}`
    .split('')
    .slice(-10)
    .join('') % modulo

    return str
}

I hope this is what you're looking for.

4
  • 1
    Thanks, this isn't what I was looking for, but it is interesting none the less. From what I can tell, it returns a "random" number for any given seed, within the max range of modulo specified. e.g PRNG(37, 1000000); always returns 863796 and PRNG(24, 1000000); always returns 911652... now wondering what this might be useful for... hmm...
    – scunliffe
    Commented Feb 7, 2022 at 15:32
  • 1
    This is great for simple use cases where you only need one number and not a series! Thank you.
    – JohnDoe
    Commented Jul 20, 2022 at 12:05
  • On closer inspection, it seems like it's not quite uniform
    – JohnDoe
    Commented Jul 20, 2022 at 12:53
  • @scunliffe you could always start the seed at zero and make it increment by one every time the function is called. This would allow for seemingly "random" sets of numbers, while you will still be able to predict them. By the way, I've now migrated to the function function randomDecimal(seed) {const whole = 2038074743; seed *= 15485863; return ((seed * seed * seed % whole + whole) % whole) / whole} because it's a whole lot less for the computer to process while still achieving randomness.
    – Joachim
    Commented Jun 8 at 7:23
0

Thank you, @aaaaaaaaaaaa (Accepted Answer)

I really needed a good non-library solution (easier to embed)

so... i made this class to store the seed and allow a Unity-esque "Next" ... but kept the initial Integer based results

class randS {
    constructor(seed=null) {
        if(seed!=null) {
            this.seed = seed;
        } else {
            this.seed = Date.now()%4645455524863;
        }
        this.next = this.SeedRandom(this.seed);
        this.last = 0;
    }
    Init(seed=this.seed) {
        if (seed = this.seed) {
            this.next = this.SeedRandom(this.seed);
        } else {
            this.seed=seed;
            this.next = this.SeedRandom(this.seed);
        }
    }
    SeedRandom(state1,state2){
        var mod1=4294967087;
        var mod2=4294965887;
        var mul1=65539;
        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){
                this.last = random;
                return random(limit);
            }
            this.last = (state1+state2)%limit;
            return (state1+state2)%limit;
        }
        this.last = random;
        return random;

    }
}

And then checked it with these... seems to work well with random (but queryable) seed value (a la Minecraft) and even stored the last value returned (if needed)

var rng = new randS(9005646549);
console.log(rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20));
console.log(rng.next(20) + ' ' + rng.next(20) + ' ' + rng.last);

which should output (for everybody)

6 7 8 14 1 12 6
9 1 1

EDIT: I made the init() work if you ever needed to reseed, or were testing values (this was necessary in my context as well)

0

Note: This code was originally included in the question above. In the interests of keeping the question short and focused, I've moved it to this Community Wiki answer.

I found this code kicking around and it appears to work fine for getting a random number and then using the seed afterward but I'm not quite sure how the logic works (e.g. where the 2345678901, 48271 & 2147483647 numbers came from).

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.
*/

2
  • 3
    Wow, the RandomNumberGenerator and nextRandomNumber functions actually date all the way back to 1996. It is supposed to be a Lehmer/LCG RNG. It uses some clever maths to perform modulo arithmetic on 32 bit integers that would otherwise be too small to contain some intermediate values. The thing is, JavaScript does not implement 32 bit integers, but rather 64 bit floats, and since the division is not integer division like this code presumes the result is not a Lehmer generator. It does produce some outcome that seem random, but the guarantees of a Lehmer generator do not apply. Commented Mar 16, 2014 at 9:28
  • 1
    The createRandomNumber function is a later addition, it does pretty much everything wrong, most notably it instantiates a new RNG every time it is called, which means that calls in rapid succession will all use the same float. In the given code it is almost impossible for 'a' to be paired with anything but '1' and 'red'. Commented Mar 16, 2014 at 9:42
0

The following IIFE generates a long sequence of reproducible random 31-bit integers. It uses two 15-bit primes to avoid overflowing JS integers.

let random = (function () {
  let a = 1, b = 1;
  return {
    nextInt: function () {
      a = (a * 67307) & 0xffff;
      b = (b * 67427) & 0xffff;
      return a ^ (b << 15);
    },
    reset(seed) {
      a = b = seed | 0;
    }
  };
})();

The following code shows how to use it...

random.reset(2); // Reset to start of sequence

// Log sequence of random numbers
for (let i = 0; i < 100; i++)
  console.log(random.nextInt());
-3

OK, here's the solution I settled on.

First you create a seed value using the "newseed()" function. Then you pass the seed value to the "srandom()" function. Lastly, the "srandom()" function returns a pseudo random value between 0 and 1.

The crucial bit is that the seed value is stored inside an array. If it were simply an integer or float, the value would get overwritten each time the function were called, since the values of integers, floats, strings and so forth are stored directly in the stack versus just the pointers as in the case of arrays and other objects. Thus, it's possible for the value of the seed to remain persistent.

Finally, it is possible to define the "srandom()" function such that it is a method of the "Math" object, but I'll leave that up to you to figure out. ;)

Good luck!

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 (my personal target environment):

-- 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)
3
  • PS - I'm not that familiar with Stack Overflow yet, but why aren't posts in chronological order?
    – posfan12
    Commented Dec 23, 2010 at 5:05
  • Hi @posfan12 - answers to questions are typically listed in order by "upvotes" such that the "cream rises to the top". However to ensure fair viewing of answers with the same score, they are shown in random order. Since this was my question originally ;-) I'll certainly be sure to check it out shortly. If I (or others) find this answer helpful we'll upvote it, and if I find it to be the "correct" answer, you'll see a green checkmark added to this answer as well. - Welcome to StackOverflow!
    – scunliffe
    Commented Dec 23, 2010 at 5:14
  • 3
    -1 This LCG implementation busts the limit for exact integers in JavaScript as seedobj[0] * seedobja is likely to result in a number greater than 2^53. The result is a limited output range, and for some seeds possibly a very short period. Commented Mar 15, 2014 at 21:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.