After some amount of effort, I managed to come up with an implementation of O'Neill's "XSH-RR" family of permuted congruential generators. The following covers the 8-, 16-, 32-, and 64-bit generators, and the mcg
, oneseq
, and setseq
variants. (I'll leave the modification to handle the unique
variant as an exercise for the interested reader.) A similar approach can be done with the other PCG families.
First, here is a routine to rotate bits to the right:
BitRotateRight[n_Integer, r_Integer?NonNegative, bits : (_Integer?Positive) : 32] /;
BitAnd[bits, bits - 1] == 0 := BitShiftRight[n, r] +
BitShiftLeft[BitAnd[n, BitShiftLeft[1, r] - 1], bits - r]
(This effectively generalizes Daniel's code here.)
Here is a set of default multipliers and increments for the underlying linear congruential generators. These and the following were adapted from O'Neill's code here:
$PCGDefaultMultipliers = {141, 12829, 747796405, 6364136223846793005,
47026247687942121848144207491837523525};
$PCGDefaultIncrements = {77, 47989, 2891336453, 1442695040888963407,
117397592171526113268558934119004209487};
A few error messages:
PermutedCongruential::bw = "The value of the option \"BitWidth\"\[Rule]`1`
should be a power of two or Automatic.";
PermutedCongruential::nm = "No built-in multiplier available for
\"BitWidth\"\[Rule]`1`. Using \"BitWidth\"\[Rule]32 instead.";
PermutedCongruential::ni = "No built-in increment available for
\"BitWidth\"\[Rule]`1`. Using \"BitWidth\"\[Rule]32 instead.";
Some options:
Options[PermutedCongruential] =
{"BitWidth" -> Automatic, "Increment" -> Automatic, "Multiplier" -> Automatic};
Initialize the generator:
PermutedCongruential /: Random`InitializeGenerator[PermutedCongruential,
opts___] :=
Module[{flops = Flatten[{opts, Options[PermutedCongruential]}],
bw, idx, inc, mul},
bw = "BitWidth" /. flops;
If[bw === Automatic, bw = 32,
If[! (IntegerQ[bw] && Positive[bw] && BitAnd[bw, bw - 1] == 0),
Message[PermutedCongruential::bw, bw]; Throw[$Failed]]];
idx = BitLength[bw] - 2;
mul = "Multiplier" /. flops;
If[mul === Automatic,
If[1 <= idx <= 5, mul = $PCGDefaultMultipliers[[idx]],
Message[PermutedCongruential::nm, bw]; bw = 32;
mul = $PCGDefaultMultipliers[[4]]]];
inc = "Increment" /. flops;
If[inc === Automatic,
If[1 <= idx <= 5, inc = $PCGDefaultIncrements[[idx]],
Message[PermutedCongruential::ni, bw]; bw = 32;
inc = $PCGDefaultIncrements[[4]]],
inc = BitOr[BitShiftLeft[inc], 1]];
idx = Min[Max[1, idx], 5];
PermutedCongruential[bw, mul, inc, {BitShiftRight[bw + idx + 1],
bw - idx - 1, 2 bw - idx - 1}, 0]]
We define PCG as a bit generator:
PermutedCongruential[___]["GeneratesBitsQ"] := True
PermutedCongruential[bw_, rest__]["BitWidth"] := bw
This handles seeding:
PermutedCongruential[bw_, mul_, inc_, const_, state_]["SeedGenerator"[seed_]] :=
Module[{ss = seed + BitAnd[inc, BitShiftLeft[1, 2 bw] - 1]},
PermutedCongruential[bw, mul, inc, const,
BitAnd[mul ss + inc, BitShiftLeft[1, 2 bw] - 1]]]
Finally, here is the bit generator:
PermutedCongruential[bw_, mul_, inc_, const_, state_]["GenerateBits"[bits_]] :=
Module[{bot, ns, res, rot, xsh},
{xsh, bot, rot} = const;
ns = BitAnd[mul state + inc, BitShiftLeft[1, 2 bw] - 1];
res = BitShiftRight[BitXor[state, BitShiftRight[state, xsh]], bot];
res = BitRotateRight[BitAnd[res, BitShiftLeft[1, bw] - 1],
BitShiftRight[state, rot], bw];
{res, PermutedCongruential[bw, mul, inc, const, ns]}]
As a test of the generator, let's reproduce some sample values given here:
BlockRandom[SeedRandom[42, Method -> {PermutedCongruential, "Increment" -> 54}];
IntegerString[RandomInteger[{0, BitShiftLeft[1, 32] - 1}, 6], 16]]
{"a15c02b7", "7b47f409", "ba1d3330", "83d2f293", "bfa4784b", "cbed606e"}
Test uniformity at various bit widths:
Table[DistributionFitTest[BlockRandom[SeedRandom[42,
Method -> {PermutedCongruential, "BitWidth" -> BitShiftLeft[1, k]}];
RandomReal[1, 1*^5]], UniformDistribution[]], {k, 3, 6}]
{0.988711, 0.951478, 0.228108, 0.181171}
Table[Histogram[BlockRandom[SeedRandom[42,
Method -> {PermutedCongruential, "BitWidth" -> BitShiftLeft[1, k]}];
RandomReal[1, 1*^5]], Automatic, "PDF",
PlotLabel -> StringForm["``-bit PCG", BitShiftLeft[1, k]]],
{k, 3, 6}] // Partition[#, 2] & // GraphicsGrid

At this point, I'd like to take this opportunity to present another method to visualize the randomness of a sequence. This is Pickover's "noise sphere" visualization:
noiseSphere[vals_?VectorQ, opts___] :=
Graphics3D[{RGBColor[##],
Sphere[Sqrt[#3] Append[Through[{Cos, Sin}[2 π #1]] Sin[π #2],
Cos[π #2]], 1/50]} & @@@ Partition[vals, 3, 1],
opts, Boxed -> False, Lighting -> "Neutral"]
BlockRandom[SeedRandom[42, Method -> {PermutedCongruential, "Increment" -> 54}];
noiseSphere[RandomReal[1, 1*^4]]]

The lack of structure and uniform filling of the sphere visually demonstrate the (apparent) lack of correlation in the sequence generated by PCG.
Developer`MachineIntegerQ
. $\endgroup$