lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



On Mar 21, 2018, at 8:57 PM, Gé Weijers <ge@weijers.org> wrote:

I've been taking a look at the random number generator in Lua 5.4.0-work1.
I would like to suggest a different implementation of the algorithm
'project', for two reasons:

 - it does not fully eliminate the bias for small numbers that are not a
 power of two
 - more importantly: when used to flip a coin (e.g. random(0,1) =3D=3D 0)=
it
 generates a short sequence of 128 values.

The xorshift128+ RNG produces a sequence whose low-order bits are an LFSR
sequence. This is a known problem with most pseudo-random number generators
of this type. It would be better to not use the low-order bit in the way
'project' uses them.

The algorithm I use most of the time (in C99). It removes all biases, at
the cost of two divisions:


uint64_t project(uint64_t lo, uint64_t hi)
{
  assert(lo <=3D hi);
  // handle edge cases first:
  switch (hi - lo) {
  case 0:
      return lo; // one-element range
  case UINT64_MAX: // lo =3D=3D 0 and hi =3D=3D UINT64_MAX
      return xorshift128plus();
  }
  //  number of possible values
  uint64_t n =3D max - min + 1u;
  //  scale =3D=3D 2**64 / n, calculated as (2**64 - n) / n + 1u
  //  'n * scale' is the largest multiple of 'n' <=3D 2**64
  uint64_t scale =3D (uint64_t)(-n) / n + 1u;
  for (;;) {
      uint64_t r =3D xorshift128plus() / scale;
      if (r < n)
          return lo + r;
  }
}


In the case of 'n' being a power of two the high-order bits will be used
('scale' will be a power of two as well)

Similarly: the 'I2d' function should probably shift a 64-bit random value
right by 11 bits in stead of masking the value, this would get rid of the
dodgy low order bit.

BTW: the author of the xorshift128+ algorithm now recommends replacing it
with a slightly different algorithm, which is ever so slightly faster, and
has better statistical properties. It has some downsides as well, the two
low-order bits both produce an LFSR sequence, it's probably a wash.

It's here:

http://xoroshiro.di.unimi.it/


I had post the problem earlier: http://lua-users.org/lists/lua-l/2018-03/msg00332.html

Below version avoided 64-bits divisions, at the cost of possibly more generator calls.
My timings suggested this is simpler and faster, even with small n

// return unbiased random number ran, 0 <= ran <= n
// it tried ran = math.random(2 ^ (bits of n)) until ran <= n
// since the range is power of 2, it avoided the expensive 64-bits divisions

static lua_Unsigned project(lua_Unsigned ran, lua_Unsigned n, RanState *state)
{
    int bits = __builtin_clzll(n);
    while ((ran >>= bits) > n) ran = I2UInt( xorshift128plus(state->s) );
    return ran;
}