Open up your dev tools’ (Mac: cmd + option + i / Windows: ctrl + shift + i), go to the Console, type Math.random()
, and hit return.
Bam. You get a random number.
I got 0.6199322557631561.
I’ve always wondered where on earth these numbers come from. And, more importantly, how can they possibly be random? After all, don’t computers just take in some input, swirl it around with some math, and then spit it back out? Seems like a pretty predictable process. So what happens when you want to generate a ‘random’ number? How does that even work and what’s happening behind the scenes?
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.
Algorithmic random number generation can’t exactly be random, per se; which is why they’re more aptly called pseudo-random number generators (PRNGs). If you’re using math and formulae to create a sequence of numbers, random though they might seem, those numbers will eventually repeat and reveal a non-random pattern.
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+.
As it turns out, xorshift128+ does a significantly better job at being pretend-random than the older algorithms; plus it’s extremely light weight and computationally fast. So it was adopted pretty much across the board, which speaks volumes to its effectiveness when you consider that there had previously been a lot of differing opinions on the matter.
But how exactly does it work?
While there are slight variations in the way that each browser implements the algorithm, we can look at a kind of “vanilla” version of it to understand how it works.
First of all, I’m just going to show you the algorithm so that you can soak it all in (shown in C), then we’ll take a closer look:
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.
So, to totally oversimplify things, the algorithm takes two seed values, switches them around, shuffles their bit values, puts their bit values through a logic gate, repeats this a few times, then adds them together and…
Bam. You get a ‘random’ number.
To package everything up neatly, here’s an overview.
Question: how does JavaScript’s Math.random()
generate random numbers?
Answer:
So, as it turns out, all we are really doing here is taking some input, swirling it around with some math, and spitting out a result. A completely predictable, nonrandom process. But one that feels random enough to us that it serves its purposes as our casual source of chaos in JavaScript.
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!
lordpoint/xorshift-sandbox-and-visualizer_xorshift-sandbox-and-visualizer - An implementation of the xorshift+ pseudo-random number generation (PRNG) algorithm…_github.com