
There's Math.random(), and then there's Math.random() - v33ra
http://v8project.blogspot.com/2015/12/theres-mathrandom-and-then-theres.html
======
ifcologne
A worth to read article by [Mike Malone]([https://medium.com/@betable/tifu-by-
using-math-random-f1c308...](https://medium.com/@betable/tifu-by-using-math-
random-f1c308c4fd9d#.a7ewychse)) explains the problem with Math.random() in
more detail.

Quoting Donald Knuth / The Art of Computer Programming:

> “Many random number generators in use today are not very good. There is a
> tendency for people to avoid learning anything about such subroutines; quite
> often we find that some old method that is comparatively unsatisfactory has
> blindly been passed down from one programmer to another, and today’s users
> have no understanding of its limitations.”

~~~
archgoon
Possibly ironically, here was the comment above the previous implementation

[https://chromium.googlesource.com/v8/v8/+/2755c5a1b1cf7fc4c5...](https://chromium.googlesource.com/v8/v8/+/2755c5a1b1cf7fc4c5c614378e5231636e6dcff5%5E%21/#F1)

    
    
      //
      // This class is used to generate a stream of pseudorandom numbers. The class
      // uses a 48-bit seed, which is modified using a linear congruential formula.
      // (See Donald Knuth, The Art of Computer Programming, Volume 3, Section 3.2.1.)
    

It's still not entirely clear why this particular algorithm was chosen (it'd
been cool if some explanation had been given in this blog post). I suspect the
reason was related to the fact that, in javascript, you can only multiply two
16 bit numbers without fear of loss of precision (without doing a fair amount
of gymnastics), and they wanted something they could easily port into pure
javascript to be used as a optimizer test. As partial support for that theory,
the new code has now been put back almost entirely into the C++ layer.

Then again, the author might be looking at that commit and wondering "Why
_did_ we pick that algorithm?" :)

~~~
TazeTSchnitzel
> you can only multiply two 16 bit numbers without fear of loss of precision
> (without doing a fair amount of gymnastics)

You can do signed and unsigned 32-bit integer multiplication, but there's
modulo/overflow there.

~~~
Franciscouzo
You can use 26 bits integers for multiplication without fear of overflowing
with IEEE 754 doubles.

------
dspillett
Do be sure to note the mention that the new algorithm is not considered a
cryptographically secure PRNG.

Also because the specification has very little by way of requirements in that
regard, no matter how good _some_ implementations may be you should always
assume you code may end up being used in an environment where Math.random() is
no better than the worst generator you can think of.

If you need specific properties in your PRNG then you still need to provide
something in place of Math.random().

~~~
danbolt
This is good to think about! Often when programming games that need certain
properties, I'll write my own toy PRNG, but I'll admit I'd love to learn more
about the craft.

Would you have any suggested reading material on the topic?

~~~
resc1440
As an outsider to the field, I really enjoyed reading this paper:
[http://www.pcg-random.org/paper.html](http://www.pcg-random.org/paper.html)

It's advocating for a particular new kind of PRNG, but it also includes a lot
of great explanations and citations for more reading.

~~~
yoklov
As a shameless plug, I wrote a JS port of this RNG algorithm a while back:
[https://www.npmjs.com/package/pcg-random](https://www.npmjs.com/package/pcg-
random) .

------
zeveb
It seems to me that defaulting to a non-CSPRNG these days is a premature
optimization: for many purposes a decent CSPRNG (e.g. Fortuna) is fast enough,
and avoids all the pitfalls of a non-secure or poorly-random generator.

Maybe it's time to have Math.random and equivalents call a CSPRNG, with a
Math.insecurerandom when performance matters?

~~~
geofft
Are there any use cases for which a good CSPRNG, with a fixed 256-bit seed at
process startup from /dev/urandom or equivalent, is not fast enough?

Last time this came up on HN, I measured individual reads from /dev/urandom,
and _those_ are fast enough for many large-scale applications, like one per
tweet:
[https://news.ycombinator.com/item?id=10608843](https://news.ycombinator.com/item?id=10608843)

(Obviously there are bad CSPRNGs, but there are also bad non-CS PRNGs. So
let's not count those.)

~~~
mmalone
It's complicated. For starters, if you're looking for a _secure_ PRNG most
practitioners would strongly advise _against_ the system you described[1].
There's a difference between a cryptographically secure algorithm and a secure
system. For the overall system to be secure you need to make sure the seed
data is secured, which you're bound to fail at unless you really know what
you're doing.

Providing a "cryptographically secure but not really secure" alternative
implemented in user space alongside a "really secure" alternative that's been
thoroughly vetted would likely just add to the confusion. If you _really_ need
secure numbers you should be using the really secure one. The really secure
one is almost definitely going to require a syscall worth of overhead, at
least, so it's _definitely_ going to be slower than a PRNG like xorshift128+
which literally takes a single digit number of CPU cycles.

Now to answer your question, if you're just talking about a CSPRNG algorithm
they're probably fast enough for almost every use case. I think there's still
an argument for some things like array shuffling, but those implementations
could go out of their way to use the non-CS algorithm as an optimization.
Unfortunately in Javascript land there's aren't many batteries included, so if
you switched Math.random() to use a CSPRNG most people would end up using it
for trivial stuff like shuffling arrays. If you're talking about a secure
system... it's probably still fast enough for most things, but it's less
obvious.

[1][http://sockpuppet.org/blog/2014/02/25/safely-generate-
random...](http://sockpuppet.org/blog/2014/02/25/safely-generate-random-
numbers/)

~~~
Dylan16807
"good CSPRNG, with a fixed 256-bit seed at process startup from /dev/urandom"
does describe a secure system. It doesn't take a syscall, and it's not fake-
secure.

The failures come when you try to seed in userspace. Not when you use urandom
to seed a standard algorithm, with no customizations.

~~~
geofft
To be fair, the failures in that article are from userspace CSPRNGs that got
even that part wrong. Debian's problem was commenting out the lines that
seeded the RNG from /dev/urandom, and Android's problem was developers that
used the RNG unseeded. They trouble wasn't re-seeding -- they weren't seeding
in the first place.

However, if we are going to decide as an industry that we need a userspace
CSPRNG, it's not _that_ hard of a job to write a single, high-quality, cross-
platform implementation of a seeded-once CSPRNG that introduces no
vulnerabilities beyond the kernel CSPRNG.

~~~
Dylan16807
You could do that, but the code to do something like create an instance of
ChaCha20, fill the parameters from urandom, and pull numbers from it is a
handful of trivial lines.

------
nraynaud
>"Please keep in mind, if you find areas of improvement in V8 and Chrome, even
ones that—like this one—do not directly affect spec compliance, stability, or
security, please file an issue on our bug tracker."

Worst idea ever, this not-a-real-bug got a correction in just a few days
without even being in the bug tracker, while there are real bugs stalled for
years in the tracker. Writing a blog post and making a lot of noise on the
internet works way better than using the bug tracker.

~~~
nolok
Wrong, making a bug report then being taken up on it is much better than
publishing the issue as:is

~~~
kevingadd
This RNG problem had a bug report with hundreds of comments where a v8 dev
downplayed its importance and insisted it shouldn't be fixed. So, bad example.

~~~
magicalist
Where is that? There have been other issues where this has happened (like the
infamous trig optimization bug), but I'm not aware of one for Math.random().

It's also worth noting that a good number of people (like [1]) outside of the
V8 team were also skeptical because of lack of specification for the RNG,
speed downsides to switching, and the availability of a CSPRNG in the browser
and node. You can argue that they're wrong, but that's not immediately clear,
Bug reports sometimes become that site of those arguments, and sometimes even
the right arguments end up losing a round and having to come back in when the
right people are noticing. Working as intended.

Regardless, the blog post or bug report dichotomy is a false one anyways, as
obviously you can write a blog post and file a bug (as Mike later said he
meant to). It's not like he was assailing the V8 team or something. The post
was written a little dramatically, but he was engaged with the team in the
followup bug (filed by a V8 dev) and elsewhere.

[1]
[https://mobile.twitter.com/sleevi_/status/667636624256344064](https://mobile.twitter.com/sleevi_/status/667636624256344064)

------
nathan_long
I wondered how the "static" visualizations were produced, then noticed the
link under the image:
[http://bl.ocks.org/mmalone/bf59aa2e44c44dde78ac](http://bl.ocks.org/mmalone/bf59aa2e44c44dde78ac)

There you can see the code and watch it run. Neat!

------
Someone1234
This article[0] hints that Microsoft's Edge browser might also being using
MWC1616, or something else that has a lot of the same limitations. Hopefully
they jump on the xorshift128+ ship.

[0] [https://lwn.net/Articles/666407/](https://lwn.net/Articles/666407/)

~~~
sanxiyn
The article actually hints that Firefox (not Chrome) and Edge used the same
RNG. The article says "LCG imported from Java decades ago", but actually
Java(OpenJDK)'s java.util.Random still uses the same RNG today, and it's not
that surprising Edge also used it.

The generator (LCG with multiplier 0x5DEECE66D) is not bad. Its main problem
is that it has only 48 bits of state.

------
dvt
Somewhat related, I wrote a blog post about (P)RNGs a few years ago:
[http://dvt.name/2010/clock-drift-hardware-prng/](http://dvt.name/2010/clock-
drift-hardware-prng/). It's interesting to se V8 favor accuracy over speed.

I'd think that having a "secure" random number generator isn't that important
of a deal given the fact that all code runs client-side anyway (so why the
need for cryptographic security?).

~~~
zeta0134
That's actually not always true. Any application running on node.js is using
the V8 engine as it's serverside backend, and might thusly be affected. There
are also a handful of applications that do clientside encryption. (MEGA comes
to mind, someone here should chime in with a better example.) Just because the
use case is uncommon doesn't make it invalid.

~~~
alanh
> _Just because the use case is uncommon doesn 't make it invalid._

Yes! Designers of systems, APIs, and user interfaces should adopt this as a
sort of mantra.

------
mring33621
Pretty sure I saw the 'After' picture in the mall, circa 1992. If you cross
your eyes and look 'through' it just right, you'll see the sailboat.

~~~
AaronBBrown
It's not a schooner, it's a sailboat!

------
evilpie
Jan also wrote about this [http://jandemooij.nl/blog/2015/11/27/math-random-
and-32-bit-...](http://jandemooij.nl/blog/2015/11/27/math-random-and-32-bit-
precision/) in the context of Spidermonkey.

------
blixt
For people concerned with cross-browser reproducibility as well as resuming a
PRNG between sessions (e.g., to reproduce random sequences in replays or
multiplayer), check out arbit, an NPM package I made. It performs close to
Math.random and uses floats for state internally for max resolution (i.e.,
length and number of unique sequences):

[https://github.com/blixt/js-arbit](https://github.com/blixt/js-arbit)

I would also recommend running the provided DieHarder test, which is crafted
to measure the quality of PRNGs.

------
jakub_g
Previous discussion (from one month ago) on the referred article with
discovery of the bug:

[https://news.ycombinator.com/item?id=10598065](https://news.ycombinator.com/item?id=10598065)

------
jacobolus
Is there an explanation anywhere of what technical or other criteria they used
to pick xorshift128+? I haven’t seen any from the handful of blog posts, etc.
I’ve seen about the change. _“[...] having understood the problem and after
some research, we decided [...]”_ is hardly a persuasive analysis.

Were any professional experts on PRNGs asked for advice?

------
stevebmark
The first one looks more random to me? It has more runs. There was some
article (can't remember the source) about generating a random coin flip - the
way to tell the difference between a real physical coin flip and a computer is
that the real physical flip will have a lot of runs, like 10 times in a row
where you get heads. A computer random will attempt to "even out" the
spectrum. The two images presented in the article looked similar to these but
were reversed, true random looked more like a pattern, while computer
generated random looked more like even noise.

TL;DR long strings of repeated results are a sign of true randomness. Am I
misinterpreting the relationship between that and this article?

~~~
dchest
Randomness looks like white noise. You can't distinguish computer randomness
(provided that it's a proper PRNG) from physical coin flips. Computers don't
"even out spectrum". The longer the coin flip run, the less likely it is to
happen, e.g. you would need on average 2046 coin throws to get 10 heads in a
row.

TL;DR probability theory

(If you have free time and want to have some fun, throw a coin and draw a
picture recording throws. I once generated a password by throwing a coin for
more than hundred or times — each bit taking more than 1 throw to avoid biases
—
[https://en.wikipedia.org/wiki/Fair_coin#Fair_results_from_a_...](https://en.wikipedia.org/wiki/Fair_coin#Fair_results_from_a_biased_coin))

~~~
pakitan
> Computers don't "even out spectrum"

Not "on purpose", of course. But with a bad PRNG, the probability of 10 heads
in a row is lower compared to a proper PRNG so in a sense the bad one is
"evening out spectrum"

~~~
dchest
Yeah, it can also be higher. Or lower. Or bad PRNG can give the same number
every time. Flipping a biased coin can also produce bad randomness. I'm not
going to mention every edge case in my comment ;-)

Computers are trying not to produce results different from fair coin flips.

------
cdnsteve
This kind of stuff seems scary to me. One JavaScript engine decides to use
this algorithm, another that. This type of change could lead to _higher
potential_ of bugs and unexpected behaviour, the average developer just can't
figure out when say, using Firefox or Chrome for testing.

When algorithms are getting tinkered with behind the scenes, this leads me to
believe there's still way too much churn in the JS space.

~~~
geofft
It's a random-number generator. It's not _supposed_ to have expected behavior.

~~~
TheOtherHobbes
If it's a seedable generator, it certainly is.

There are applications in creative coding and gaming where it's _critical_ to
have a reproducible random sequence that gives the same results on all
platforms.

~~~
alanh
> reproducible random sequence

That’s a hell of an oxymoron.

 _Edit_ Yes, I know obviously the intent was to refer to a _pseudorandom_
sequence, but that’s not what was said. It’s also not what Math.random
promises. It promises random-seeming output, nothing more, nothing less. It
did _not_ promise a cross-platform, reliably reproducible stream of
pseudorandom numbers, and it would be in error to abuse a black-box "random"
API by considering it to be a fully specified pseudorandom stream.

~~~
dchest
[https://en.wikipedia.org/wiki/Pseudorandom_number_generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator)

