How Math.random() works in Javascript

Dave Wisecarver
5 min readJan 27, 2021

As someone who has spent time developing games, I have made regular use of random number generators. Whether simulating a roll of the die, or the flip of a coin, the sense of random chance and luck plays a vital role in many games. Javascript has a built-in function for generating a random number. You can create one in your browser right now by opening up the console (right-clicking the page and selecting ‘Inspect’) and typing in: Math.random()

> Math.random()
<- 0.09637747691338427

As you can see the output is a float that will land between 0 and 1, which one can then take and apply logic to determine the various probabilities of a certain outcome.

Something that always confused me about random number generation though, is exactly what is happening under the hood that allows a computer to produce an output with seemingly no input. How can there be a truly random number if there is no starting reference point? Turns out there can’t be. At least with Math.random in Javascript, algorithmic number generation cannot be truly random as the math and logic used remains the same and so after enough generations, eventually patterns and repetition can be detected.

Math.random is actually classified as pseudo-random number generator (PRNGs), as it can only simulate randomness. There are a few important elements of building these PRNGs. The quality of the randomness is measured through it’s period, or how many iterations can be made before patterns emerge. The larger this period is, the higher the quality of the randomness. This has typically been achieved by making the algorithms more complex, and in doing so, making the randomness more difficult to predict. Though since a PRNG can never be truly random, they should not be used for security purposes.

The complexity and arithmetic used for PRNG algorithms have evolved over the years. One of the earliest and most influential PRNGs is the linear congruential generator. Another very popular PRGN algorithm is Mersenne Twister, which was developed in 1997 by Makoto Matsumoto and Takuji Nishimura, and improved upon the common PRNGs of the day and is still in use in some languages today. But which one does Javascript use? None, actually.

According to the ECMAScript 2021 Language Specification: 21.3.2.27 Math.random returns a number value with positive sign, greater than or equal to 0 but strictly less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-defined algorithm or strategy. Implementation-defined in this case basically means that Javascript defers to the browser which method of RNG to use, as long as it meets these specifications.

The most common PRNG algorithm used by modern browsers is some variant of Xorshift. Very basically, the algorithm takes two seed values, and uses bitwise operators to change the binary values and produce new numbers. The example below uses small numbers and would make for a very short period, though it will be easier to understand

let state0 = 5
let state1 = 8
function xorshift() {
let s1 = state0;
let s0 = state1;
state0 = s0;
s1 ^= s1 << 4; // Shift left 4 places and Xor
s1 ^= s1 >> 9; // Shift right 9 and Xor
s1 ^= s0;
s1 ^= s0 >> 6; // Shift right 6 places and Xor
state1 = s1;
return state0 + state1;
}

This function starts off with taking two seed numbers and assigning variables to them. If you were to pass in a 5 and 8 as arguments, you’d see s1 = 5, s0 = 8, state0 = 8, and state1 = 8.

On lines 6–9, these variables are being given new values by two bitwise operators. The two bitwise operators in use here are the xor operator: ^=, and the shift operator. Bitwise operators are used on the bit level, referencing and changing 1s and 0s. The shift operator will take the binary representation of a number and shift the 1s and 0s a specified number of places.

So on line 6, s1 = 5, which in binary is 00000101. That is then shifted 4 places to the left, resulting in 01010000 which is the binary number for 80.

101 is shifted to the left 4 places and backfilled with zeros

Then, the xor operator compares compares the binary representations of the original s1 to the shifted version of s1, and outputs 0 where corresponding bits match and outputs 1 where corresponding bits do not match. So then 5 xor 80 returns 01010101, or 85.

Xor operator comparing two binary representations of numbers.

Line 7 does the same thing as 6, but uses the right shift operator. This does the inverse of the left shift operator. Then the xor operator is applied. The inputs get shifted and xor’d again for more complexity and the output is state0 + state1. You can copy and paste that code above into the browser console and see the size of the random numbers generated from it. The first four times running the xorshift() function, I got back 101, 305, 1580, and 3639. While these numbers are pretty small, increasing the size of the shift values as well as the input values will improve the quality of the PRNG.

Pseudo random number generators may not be creating truly random numbers, but the algorithms behind them can be complex enough to produce an output that seems random enough by human perception, and can be used for many cool and interesting applications.

--

--

Dave Wisecarver

Software dev student and free-time game dev. Currently enrolled in Flatiron Software Engineering Program.