Skip to content

Commit

Permalink
Prefer high bits (#36)
Browse files Browse the repository at this point in the history
* Add preferHighBits property

* replace $(D) with ``

they are the same

* preferHighBits only if isSaturatedRandomEngine

* Make preferHighBits!T public and move to mir.random.engine

* Update package.d
  • Loading branch information
n8sh authored and 9il committed Oct 24, 2017
1 parent d409f43 commit e66e7de
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 5 deletions.
16 changes: 12 additions & 4 deletions source/mir/random/engine/linear_congruential.d
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct LinearCongruentialEngine(Uint, Uint a, Uint c, Uint m)
///
enum isRandomEngine = true;

/// Highest generated value ($(D modulus - 1 - bool(c == 0))).
/// Highest generated value (`modulus - 1 - bool(c == 0)`).
enum Uint max = m - 1 - bool(c == 0);
/**
The parameters of this distribution. The random number is $(D_PARAM x
Expand All @@ -34,6 +34,14 @@ The parameters of this distribution. The random number is $(D_PARAM x
static assert(m == 0 || c < m);
static assert(m == 0 || (cast(ulong)a * (m-1) + c) % m == (c < a ? c - a + m : c - a));

/++
The low bits of a linear congruential generator whose modulus is a
power of 2 have a much shorter period than the high bits.
Note that for LinearCongruentialEngine, `modulus == 0` signifies
a modulus of `2 ^^ Uint.sizeof` which is not representable as `Uint`.
+/
enum bool preferHighBits = 0 == (modulus & (modulus - 1));

@disable this();
@disable this(this);

Expand Down Expand Up @@ -107,7 +115,7 @@ The parameters of this distribution. The random number is $(D_PARAM x

/**
Constructs a $(D_PARAM LinearCongruentialEngine) generator seeded with
$(D x0).
`x0`.
Params:
x0 = seed, must be positive if c equals to 0.
*/
Expand Down Expand Up @@ -171,10 +179,10 @@ Params:

/**
Define $(D_PARAM LinearCongruentialEngine) generators with well-chosen
parameters. $(D MinstdRand0) implements Park and Miller's "minimal
parameters. `MinstdRand0` implements Park and Miller's "minimal
standard" $(HTTP
wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator,
generator) that uses 16807 for the multiplier. $(D MinstdRand)
generator) that uses 16807 for the multiplier. `MinstdRand`
implements a variant that has slightly better spectral behavior by
using the multiplier 48271. Both generators are rather simplistic.
*/
Expand Down
26 changes: 26 additions & 0 deletions source/mir/random/engine/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ template isSaturatedRandomEngine(T)
enum isSaturatedRandomEngine = false;
}

/++
Are the high bits of the engine's output known to have
better statistical properties than the low bits of the
output? This property is set by checking the value of
an optional enum named `preferHighBits`. If the property
is missing it is treated as false.
This should be specified as true for:
<ul>
<li>linear congruential generators with power-of-2 modulus</li>
<li>xorshift+ family</li>
<li>xorshift* family</li>
<li>in principle any generator whose final operation is something like
multiplication or addition in which the high bits depend on the low bits
but the low bits are unaffected by the high bits.</li>
</ul>
+/
template preferHighBits(G)
if (isSaturatedRandomEngine!G)
{
static if (__traits(compiles, { enum bool e = G.preferHighBits; }))
private enum bool preferHighBits = G.preferHighBits;
else
private enum bool preferHighBits = false;
}

/**
A "good" seed for initializing random number engines. Initializing
with $(D_PARAM unpredictableSeed) makes engines generate different
Expand Down
20 changes: 20 additions & 0 deletions source/mir/random/engine/xorshift.d
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ if (isIntegral!StateUInt && isIntegral!OutputUInt
static assert(multiplier != 1 && multiplier % 2 != 0,
typeof(this).stringof~": multiplier must be an odd number other than 1!");

/++
Note that when StateUInt is the same size as OutputUInt the two lowest bits
of this generator are
$(LINK2 https://en.wikipedia.org/wiki/Linear-feedback_shift_register,
LSFRs), and thus will fail binary rank tests. We suggest to use a sign test
to extract a random Boolean value, and right shifts to extract subsets of bits.
+/
enum bool preferHighBits = true;

private:
enum uint N = nbits / (StateUInt.sizeof * 8);
enum bool usePointer = N > 3;
Expand Down Expand Up @@ -432,6 +441,17 @@ struct Xoroshiro128Plus
+/
ulong[2] s = void;

/++
The lowest bit of this generator is an
$(LINK2 https://en.wikipedia.org/wiki/Linear-feedback_shift_register,
LSFR). The next bit is not an LFSR, but in the long run it will fail
binary rank tests, too. The other bits have no LFSR artifacts.
We suggest to use a sign test to extract a random Boolean value, and
right shifts to extract subsets of bits.
+/
enum bool preferHighBits = true;

@disable this();
@disable this(this);

Expand Down
8 changes: 7 additions & 1 deletion source/mir/random/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ T rand(T, G)(ref G gen)
(cast(R*)(&ret))[p] = gen();
return ret;
}
else static if (preferHighBits!G && P == 0)
{
version(LDC) pragma(inline, true);
return cast(T) (gen() >>> ((R.sizeof - T.sizeof) * 8));
}
else
{
version(LDC) pragma(inline, true);
Expand All @@ -89,7 +94,8 @@ Returns:
bool rand(T : bool, G)(ref G gen)
if (isSaturatedRandomEngine!G)
{
return gen() & 1;
import std.traits : Signed;
return 0 > cast(Signed!(EngineReturnType!G)) gen();
}

///
Expand Down

0 comments on commit e66e7de

Please sign in to comment.