6
\$\begingroup\$

This is a basic implementation of the non-cryptographic Xorgens4096 pseudo random number generator with a global, thread safe seeder. The goal is to provide a high quality generator that can be instantiated in multiple threads while still guaranteeing that the seed for each generator is unique. To ensure uniqueness of each generator's state, SHA512 is used in feedback mode (if that's the right word).

Is this the right approach? Any criticism and tips are very much appreciated.

using System;
using System.Diagnostics;
using System.Text;
using System.Security.Cryptography;

// big state non-cryptographic PRNG with a global seeder

class Xorgens4096
{
    // seeder part

    static private SHA512 sha512;

    static public byte[] hash;

    // set master seed using some common data for uniqueness

    static Xorgens4096()
    {
        sha512 = SHA512.Create();

        SetMasterSeed(String.Format("{0}-{1}-{2}",
            DateTime.UtcNow.Ticks,
            Process.GetCurrentProcess().Id,
            Guid.NewGuid()));
    }

    // set master seed used to derive states of all generator instances

    static public void SetMasterSeed(string seed)
    {
        hash = Encoding.Unicode.GetBytes(seed);
    }

    // create generator state using a seed or master seed (thread safe)

    static private void Seed(string seed, ulong[] state)
    {
        lock (hash)
        {
            if (String.IsNullOrEmpty(seed) == false)
                hash = Encoding.Unicode.GetBytes(seed);

            for (int i = 0; i < 8; i++)
            {
                hash = sha512.ComputeHash(hash);
                Buffer.BlockCopy(hash, 0, state, i * 64, 64);
            }
        }
    }

    // generator part

    public ulong[] state = new ulong[64];
    public ulong index;
    public ulong weyl;

    public Xorgens4096(string seed = "")
    {
        Seed(seed);
    }

    public void Seed(string seed)
    {
        Seed(seed, state);
        index = 0;
        weyl = 0;
    }

    // actual xorgens4096 generator

    public ulong Gen()
    {
        ulong t = state[index = (index + 1) & 63];
        t ^= t << 33;
        t ^= t >> 26;

        ulong v = state[(index + 11) & 63];
        v ^= v << 27;
        v ^= v >> 29;

        state[index] = (v ^= t);

        weyl += 0x61c8864680b583eb;

        return v + (weyl ^ (weyl >> 27));
    }
}
\$\endgroup\$
  • \$\begingroup\$ Do you have a link to the specification of Xorgens4096? I couldn't find anything useful. \$\endgroup\$ – t3chb0t Nov 3 '17 at 17:02
  • \$\begingroup\$ @t3chb0t: maths-people.anu.edu.au/~brent/random.html \$\endgroup\$ – Thorham Nov 4 '17 at 10:16
  • \$\begingroup\$ This observation is not worth full answer, but why are state, index, weyl and hash public? \$\endgroup\$ – firda Nov 14 '18 at 16:42
7
\$\begingroup\$

The only thing I see that needs correcting is the locking. Remember that

lock (hash) {
    // body
}

is syntactic sugar for:

var local = hash;
try
{
    System.Threading.Monitor.Enter(local);
    // body
}
finally
{
    System.Threading.Monitor.Exit(local);
}

When you update the hash variable in one thread, the next thread to come in will have a different object that it locks, and so the blocks will execute in parallel. It's usually a mistake to try to reuse part of your model as a lock. I always recommend a dedicated object like

static readonly Object _lock = new Object();

Other than that, I only suggest:

if (String.IsNullOrEmpty(seed) == false)

should be:

if (!String.IsNullOrEmpty(seed))
\$\endgroup\$
  • \$\begingroup\$ And I make every lock readonly (if possible), but am not against reusing parts, so, readonly ulong[] state would be a candidate (btw: why are these public?) \$\endgroup\$ – firda Nov 14 '18 at 16:40
  • \$\begingroup\$ Yeah, those are both good points \$\endgroup\$ – Pierre Menard Nov 14 '18 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.