From: Johannes Baagoe on
Like many other (semi-)numerical problems in javascript, writing an
efficient substitute for Math.random is not quite like if it were another
language. It is of course always possible to combine two pseudo-random
32-bit integers in order to provide the desired 53 significant fractional
bits, but doing everything directly in 53-bit floating point is an
interesting challenge.

So far, I had been using subtractive lagged Fibonacci generators -
if a and b are both 53-bit fractions between 0 and 1, you cannot
add them without risking to lose a bit to overflow, but you can
safely decrease a by b, and add 1 if the result is negative.

It is quite possible to make excellent PNRGs that way, but here,
I want to submit another approach: Marsaglia's Multiply-with-carry.
It has a rather fascinating theory - one essentially grinds out the
binary representation of the inverse of a large prime. It may not have
been *proved* that this does indeed provide good pseudo-random bits,
but any evidence of systematic regularities would surely constitute
a major mathematical discovery. More down to earth, MWCs fare very
well in empirical tests, actually better so than, e.g., the Mersenne
Twister. Besides, they are shorter and faster.

My idea is to equate the carries with the integer parts of the main
multiplicands, which, if there were to be converted into fixed point,
would have at most 21 bits before the "decimal" point and exactly 32
after. (I have experimented with other distributions of the "carry"
and the "remainder" parts. Increasing the latter, say, to 38 bits
obviously improves the period, but the multiplier becomes too small
to affect enough bits. 21 + 32 seems to be a sensible compromise that
also makes testing on integer C versions easier.)

To the best of my knowledge, this idea of splitting the "carry" and
"remainder" in integer and fractional parts is new, which is why I
solicit criticism.

As a minor point, I would like to point out the use of the
Fowler-Noll-Vo hash in order to seed the generator with *any* kind
of arguments - Numbers, Strings, Dates, Objets, anything that has a
toString method.

Here is the code (sorry if it is long, but half of it is comments):

function MWC4() {
/* Usage:

var random = [new] MWC4([...]);

The resulting function, random, behaves like Math.random: successive
calls to random() return "random" numbers in [0, 1[. The generated
pseudo-random numbers should be much more "random", though.

If you provide arguments, calling MWC4 again with the same values for
all arguments will result in a function that returns the same sequence of
pseudo-random numbers across standard-compliant implementations. E.g.,

var random = MWC4(0);
for (var i = 0; i < 1000000; i++) {random();}
var x = random();

should always yield x === 0.1041667880930884

If you do provide no arguments, MWC4(+ new Date()) is silently assumed.

Using the empty string as sole argument(s) - MWC4('') - is a bad idea:
the first numbers won't be very random. Anything else should be OK.

Calling MWC4 with new is unnecessary but harmless.
*/

return (function (args) {
/*
Marsaglia's Multiply-with-carry ("Mother of All Random Number
Generators"), on a small array of four 32-bit numbers, adapted
to javascript's quaint notion of numbers.

http://groups.google.com/group/sci.stat.math/msg/dd90bad892f56640
http://interstat.statjournals.net/YEAR/2005/articles/0510005.pdf
http://en.wikipedia.org/wiki/Multiply-with-carry

The multiplier a (here, a = 2096715) must be such that a * s[k] + c
fits in 53 bits, and that a * (2 ^ 32) ^ 4 - 1 is a safe prime,
that is, a prime whose floored half is also a prime.

The period is close to 2 ^ 148, which should be sufficient for
most purposes. If you need something stronger, try MWC16: uncomment
the 4 lines thus marked below, delete the lines that precede them,
and change the name of the pseudo-constructor. That would give you a
period of more than 2 ^ 531; I cannot conceive any reasonable use of
a PRNG in javascript for which that would not be more than adequate.
*/

var norm = Math.pow(2, -32);
var s = [0, 0, 0, 0];
// MWC16: var s = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

var c = 0;
var k = 0;

var a = 2096715;
// MWC16: var a = 1994205;

function mwc() {
var t;

t = a * s[k] + c * norm;
c = t | 0;
t -= c;
s[k] = t;
k = (k + 1) & 3;
// MWC16: k = (k + 1) & 15;
return t + s[k] * norm;
}

// seed array s with arguments
// if no arguments, take present time as seed
if (args.length === 0 ) {
args = [+new Date()];
}

// Fowler/Noll/Vo FNV-1a hash
// http://www.isthe.com/chongo/tech/comp/fnv/index.html
var hash = 0x811c9dc5;
function fnv1a(x) {
x = x.toString();
for (var i = x.length - 1; i >= 0; i--) {
hash = ((hash ^ x.charCodeAt(i)) * 0x01000193) >>> 0;
}
return hash;
}

for (var i = 0; i < args.length; i++) {
for (var j = 0; j < 4; j++) {
// MWC16: for (var j = 0; j < 16; j++) {
s[j] -= fnv1a(args[i]) * norm;
if (s[j] < 0) {
s[j] += 1;
}
}
}

return mwc;
}(Array.prototype.slice.call(arguments)));
}

--
Johannes
From: Scott Sauyet on
Johannes Baagoe wrote:
> The resulting function, random, behaves like Math.random: successive
> calls to random() return "random" numbers in [0, 1[. The generated
> pseudo-random numbers should be much more "random", though.

I have no background in PRNGs to really comment on the quality. In
brief tests, it gave results that to my naive eyes look random. Have
you done performance tests to compare with other JS approaches?

What other restrictions are there on the multiplier ("a") than what
you describe in the comments?

-- Scott
From: Joe Nine on
Johannes Baagoe wrote:
> Like many other (semi-)numerical problems in javascript, writing an
> efficient substitute for Math.random is not quite like if it were another
> language. It is of course always possible to combine two pseudo-random
> 32-bit integers in order to provide the desired 53 significant fractional
> bits, but doing everything directly in 53-bit floating point is an
> interesting challenge.

What's wrong with Math.random that it needs replacing?
From: "Michael Haufe ("TNO")" on
On Apr 5, 8:23 am, Joe Nine <j...(a)yahoo.com> wrote:
> Johannes Baagoe wrote:
> > Like many other (semi-)numerical problems in javascript, writing an
> > efficient substitute for Math.random is not quite like if it were another
> > language. It is of course always possible to combine two pseudo-random
> > 32-bit integers in order to provide the desired 53 significant fractional
> > bits, but doing everything directly in 53-bit floating point is an
> > interesting challenge.
>
> What's wrong with Math.random that it needs replacing?

https://bugzilla.mozilla.org/show_bug.cgi?id=322529
From: Hans-Georg Michna on
On Mon, 05 Apr 2010 15:23:55 +0200, Joe Nine wrote:

>Johannes Baagoe wrote:

>> Like many other (semi-)numerical problems in javascript, writing an
>> efficient substitute for Math.random is not quite like if it were another
>> language. It is of course always possible to combine two pseudo-random
>> 32-bit integers in order to provide the desired 53 significant fractional
>> bits, but doing everything directly in 53-bit floating point is an
>> interesting challenge.

>What's wrong with Math.random that it needs replacing?

From the spec: "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 means the function is mostly undefined and has different
results and varying quality in different implementations.

If you need deterministic results from a program, for example,
for a series of tests, you cannot use the built-in Math.random()
method. You need a random number generator that, after the same
seed, produces exactly the same series of pseudo-random numbers.

On top of that, the built-in random number generators may be
poorly designed and may not have very good distribution or
cycles. Random numbers and their generators are something that,
in my experience, is often not well understood.

Hans-Georg