visit
Open up your dev tools’ (Mac: cmd + option + i / Windows: ctrl + shift + i), go to the Console, type Math.random()
, and hit return.
Surprise surprise, the answer is that Math.random()
doesn’t really generate a random number. Not exactly. It just does a really good job of simulating randomness.
But some PRNGs are better than others. The quality of a PRNG depends on a number of factors, a very significant factor being something called its period; the number of iterations a PRNG goes through before it starts repeating itself. Not only does a PRNG with a long period seem more random to us humans but it’s also harder (i.e. more resource intensive) for a computer to crack / predict; a fact that has security implications even though no one should be using Math.random()
for encryption — but it happens anyways.
So now the question is: what PRNG does JavaScript use?
The answer: none.JavaScript doesn’t decide how Math.random()
gets implemented, your browser does. There’s no hard-coded PRNG algorithm that ships with JavaScript. Instead, the engineers who created your browser decided on an algorithm to use that conforms to the ECMAScript spec, which is as follows:
[Math.random] Returns a Number value with positive sign, greater than or equal to 0 but less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-dependent algorithm or strategy. This function takes no arguments.
Each Math.random function created for distinct code Realms must produce a distinct sequence of values from successive calls.
Those are the instructions, it’s up to the browser to decide how to follow them. Until recently, different browsers used slightly different methods for accomplishing this. The algorithms that they used had sexy names like Marsenne-Twister, Multiply With Carry, or Linear Congruential Generator. Don’t worry, though, it’s not really important for you to understand what all of those things mean (although I’m completely impressed if you do).
The important thing to know about all this is that (1) browsers decide which algorithm they want to use to calculate Math.random()
and (2) in 2015 pretty much every browser (the major ones at least) ditched their old PRNG algorithms and now they all use the same one: called xorshift128+.
uint64_t state0 = 1;uint64_t state1 = 2;
uint64_t xorshift128plus() {uint64_t s1 = state0;uint64_t s0 = state1;state0 = s0;s1 ^= s1 << 23;s1 ^= s1 >> 17;s1 ^= s0;s1 ^= s0 >> 26;state1 = s1;return state0 + state1;}
If you’re like me (with a front-end background and no CS degree) you look at this and think “Ok, variable assignment, variable assignment, function… simple enough…” but then you come to s1 ^= s1 << 23;
and say “what the shit?”
These are bitwise operators. They manipulate data at the bit level (1’s and 0’s) and they form the heart and soul of the algorithm that we’re looking at. They’re also something that your average web developer rarely (if ever) has an occasion to work with. So in order to explain what this algorithm is doing, I’m going to quickly go over the three bitwise operators shown above and how they work.
The first operator, <<
, is called a left-shift. Here’s an example: 12 << 4
. In this example, you’d take a binary representation of the number 12 and shift it to the left by 4 places; hence left-shift. Here’s how that works:
The inverse of this, called a right shift >>
, does the same thing but shifts right instead of left.
The second operator, =^
is the xor assignment operator. Xor (short for exclusive or) compares the binary representations of two numbers and outputs a 0 where corresponding bits match and outputs a 1 where corresponding bits are different. You can think of xor as ‘one or the other but not both’. Here’s a visualization of a random xor 53^18
(xor without the assignment)
Now that you know what all the operators do, you can start to make sense of the xorshift algorithm above. That baffling bit that I mentioned earlier ( s1 ^= s1 << 23;
) is just left shifting s1 by 23 places and then xor-ing the result with s1, resulting in s1’s newly-assigned value. Or, in other words, it’s xor shifting.
Question: how does JavaScript’s Math.random()
generate random numbers?
Answer:
For anyone that’s interested, I have a JS implementation of xorshift128+ available on GitHub (linked below) that gives you a visualization of the algorithm’s ‘randomness’ and lets you play with seed and shift values. Thanks for reading!