20

For testing purposes I'm creating random numbers with a given seed (i.e. not based on the current time).

Thus the whole program is deterministic.

If something happens, I'd like to be able to quickly restore a point "shortly before" the incident.

Therefore I need to be able to restore a System.Random to a previous state.

Is there a way to extract a seed which I can use to recreate the random generator?

Onur
  • 5,017
  • 5
  • 38
  • 54
  • 1
    You could keep track of the number of calls made to System.Random, so you'd have a count for when the failure occurred. To restore the state, just set the same original seed, and call the generator the right number of times. – Baldrick Oct 22 '13 at 08:02
  • I'm not exactly sure what you mean. Do you mean I shall store the sequence of random numbers, e.g. in a file and read it from file later on? – Onur Oct 22 '13 at 08:03
  • 4
    [`System.Random`](http://msdn.microsoft.com/en-us/library/system.random.aspx) is marked as Serializable. – Damien_The_Unbeliever Oct 22 '13 at 08:04
  • 6
    You might find this answer helpful: http://stackoverflow.com/a/8188878/2609288 – Baldrick Oct 22 '13 at 08:04
  • 1
    You could write a custom RNG. Has the added benefit that it can produce better quality outputs than `System.Random`. – CodesInChaos Oct 22 '13 at 08:18
  • 3
    Definitely just serialize it, as in the answer linked by Baldrick. It is indeed a cunning plan. – Matthew Watson Oct 22 '13 at 08:22
  • Maybe I'm being overly picky, but I don't like the proposed solution of counting the number of call and then restoring by looping. You may very well end up looping billions of times, even though all you had to do was save the state and restore it. – harold Oct 22 '13 at 08:46
  • If you don't like the idea of serializing it on every call and don't like the idea of looping potentially billions of times, why not both? Keep a count of the number of calls since you last serialized, and only serialize it ever 100 or 1000 calls. – SLuck49 Oct 22 '13 at 12:27
  • I have even less ambitions. I'm willing to let it run from the start up to this point and extract the state at the given point and start from this point on several times. I have to do this, since I also need to extract the state from other objects. – Onur Oct 22 '13 at 12:35
  • @Baldrick I would advise against your suggestion of iterating x number of times. Suppose x times was a few billion, which is actually feasible for RNG heavy games. This means you would need to iterate a billion or more times in a for loop, which naturally, could take some time. I would suggest instead to create your own random number class, and simply extract the current state of the seed array, and the two exp fields. You can store this in an array of 58 indices, 59 if you want to save the seed, too. Doing it this way would mean a constant load time, without looping x billion+ times. – Krythic Sep 15 '17 at 06:39

7 Answers7

15

In line with the answer given here, I wrote a small class to help with saving and restoring the state.

void Main()
{
    var r = new Random();

    Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("before save");
    var s = r.Save();
    Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("after save");
    r = s.Restore();
    Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("after restore");

    s.Dump();
}

public static class RandomExtensions
{
    public static RandomState Save(this Random random)
    {
        var binaryFormatter = new BinaryFormatter();
        using (var temp = new MemoryStream())
        {
            binaryFormatter.Serialize(temp, random);
            return new RandomState(temp.ToArray());
        }
    }

    public static Random Restore(this RandomState state)
    {
        var binaryFormatter = new BinaryFormatter();
        using (var temp = new MemoryStream(state.State))
        {
            return (Random)binaryFormatter.Deserialize(temp);
        }
    }
}

public struct RandomState
{
    public readonly byte[] State;
    public RandomState(byte[] state)
    {
        State = state;
    }
}

You can test this code in LINQPad.

Community
  • 1
  • 1
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • This way, you have to store the state of the random number generator every time you generate a number. Otherwise, you won't be able to easily jump back to a previous state. – sloth Oct 22 '13 at 08:46
  • That is correct. The other way you have to restart at the beginning and call it N times. It depends entirely on what is easiest to handle. – Lasse V. Karlsen Oct 22 '13 at 09:02
  • If you would add the code for your "Dump" extension method, others could copy/paste this code. – Onur Oct 24 '13 at 11:25
  • It's a [LINQPad](http://linqpad.net) extension, not mine, hence my final comment that you can test that code in LINQPad. You don't need that extension to use the code anyway, that's just to show the result of saving and restoring the state, something you wouldn't do in the real code anyway. – Lasse V. Karlsen Oct 24 '13 at 11:37
  • Although this is the chosen answer, and it would work beautifully, I feel as though a different approach could be faster, easier to dynamically store in any file, and just generally be a better option. My answer has been added and is located below. – Krythic Sep 16 '17 at 19:36
8

This is what I came up:

Basically it extracts the private seed array. You just need to be careful to restore an "unshared" array.

var first = new Random(100);

// gain access to private seed array of Random
var seedArrayInfo = typeof(Random).GetField("SeedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var seedArray = seedArrayInfo.GetValue(first) as int[];

var other = new Random(200); // seed doesn't matter!

var seedArrayCopy = seedArray.ToArray(); // we need to copy since otherwise they share the array!

seedArrayInfo.SetValue(other, seedArrayCopy);


for (var i = 10; i < 1000; ++i)
{
    var v1 = first.Next(i);
    var v2 = other.Next(i);

    Debug.Assert(v1 == v2);

}
Onur
  • 5,017
  • 5
  • 38
  • 54
  • This has the following upsides: 1. It appears the most straightforward and not computationally expensive 2. It moves all the load to rarely executed code, i.e. at `Random` instantiation and when you need the actual state restoration. Zero overhead otherwise. I also find the proposition with re-plugging Random into itself ingenious but I still prefer this one as it is ~150 times more performant. – mireazma Nov 03 '20 at 08:17
  • It's doesn't work in dotnet 7, in dotnet 7 `SeedArray` doesn't exists, the `new Random(seed)` will be like : `public Random(int Seed) => this._impl = this.GetType() == typeof (Random) ? (Random.ImplBase) new Random.Net5CompatSeedImpl(Seed) : (Random.ImplBase) new Random.Net5CompatDerivedImpl(this, Seed);` I think it's best to use a nuget library that actively provides state values. – justTryIt Jul 24 '23 at 05:32
4

I'm aware this question has already been answered, however, I wanted to provide my own implementation, which is currently in use for a game that I am creating. Essentially, I created my own Random class, using the code of .NET's Random.cs. Not only did I add more functionality, but I also added a way to save and load the current generator state into and from an array of just 59 indices. It is better to do it this way instead of how some other comments suggest to "Iterate x number of times to restore the state manually. This is a bad idea because in RNG heavy games your Random generator state could theoretically get into the billions of calls, meaning you would—according to them—need to iterate a billion times to restore the state of the last play session during each startup. Granted, this may still only take a second, tops, but it's still too dirty in my opinion, especially when you could simply extract the current state of the Random Generator and reload it when required, and only taking up 1 array (59 indices of memory).

This is just an idea, so take from my code what you will.

Here is the full source, which is much too large to post here:

GrimoireRandom.cs

And for anyone who just wants the implementation for the question, I will post it here.

        public int[] GetState()
        {
            int[] state = new int[59];
            state[0] = _seed;
            state[1] = _inext;
            state[2] = _inextp;
            for (int i = 3; i < this._seedArray.Length; i++)
            {
                state[i] = _seedArray[i - 3];
            }
            return state;
        }

        public void LoadState(int[] saveState)
        {
            if (saveState.Length != 59)
            {
                throw new Exception("GrimoireRandom state was corrupted!");
            }
            _seed = saveState[0];
            _inext = saveState[1];
            _inextp = saveState[2];
            _seedArray = new int[59];
            for (int i = 3; i < this._seedArray.Length; i++)
            {
                _seedArray[i - 3] = saveState[i];
            }
        }

My code is completely stand-alone, besides the DiceType enumeration, and the OpenTK Vector3 struct. Both of those functions can just be deleted and it will work for you.

Krythic
  • 4,184
  • 5
  • 26
  • 67
  • Thanks for this. Appears to be working very well for what I need. Saving the state of the Random Number generator will help immensely when it comes to recording my game and replaying it in addition to saving and restoring game state to make sure everything is consistent. – Brien King Apr 28 '20 at 16:40
  • @BrienKing Really glad I could help! Happy coding! – Krythic Apr 30 '20 at 00:04
3

System.Random is not sealed and its methods are virtual, so you could create a class that counts the number of numbers generated to keep track of the state, something like:

class StateRandom : System.Random
{
    Int32 _numberOfInvokes;

    public Int32 NumberOfInvokes { get { return _numberOfInvokes; } }

    public StateRandom(int Seed, int forward = 0) : base(Seed)
    {
        for(int i = 0; i < forward; ++i)
            Next(0);
    }

    public override Int32 Next(Int32 maxValue)
    {
        _numberOfInvokes += 1;
        return base.Next(maxValue);
    }
}

Example usage:

void Main()
{
    var a = new StateRandom(123);
    a.Next(100);
    a.Next(100);
    a.Next(100);

    var state = a.NumberOfInvokes;
    Console.WriteLine(a.Next(100));
    Console.WriteLine(a.Next(100));
    Console.WriteLine(a.Next(100));

    // use 'state - 1' to be in the previous state instead
    var b = new StateRandom(123, state);
    Console.WriteLine(b.Next(100));
    Console.WriteLine(b.Next(100));
    Console.WriteLine(b.Next(100));

}

Output:

81
73
4
81
73
4
sloth
  • 99,095
  • 21
  • 171
  • 219
  • I don't think these arguments influence the internal state, but I have to admit I have not tested it. – Onur Oct 22 '13 at 08:25
  • This is not useful. It merely demonstrates that using the same seed for Random generates the same random numbers. The OP already knows this. – Hans Passant Oct 22 '13 at 08:44
  • 1
    @HansPassant I know the OP knows what the point of using the same seed is. He wants to know a way to create a random number generator with the same seed that is already advanced so he can go to *a point "shortly before" the incident*, which is what my answer shows. – sloth Oct 22 '13 at 08:50
3

There is an alternative solution that (1) avoids the need to remember all previously generated numbers; (2) does not involve accessing the private fields of Random; (3) does not require serialization; (4) does not require looping back through Random as many times as it had been called; and (5) does not require creating a replacement for the built-in Random class.

The trick is to get state by generating a random number, and then reseeding the random number generator to this value. Then, in the future, one can always return to this state by reseeding the random number generator to this value. In other words, we "burn" a number in the random number sequence for the purpose of saving state and reseeding.

The implementation follows. Note that one would access the Generator property to actually generate numbers.

public class RestorableRandom
{
    public Random Generator { get; private set; }

    public RestorableRandom()
    {
        Generator = new Random();
    }

    public RestorableRandom(int seed)
    {
        Generator = new Random(seed);
    }

    public int GetState()
    {
        int state = Generator.Next();
        Generator = new Random(state);
        return state;
    }

    public void RestoreState(int state)
    {
        Generator = new Random(state);
    }
}

And here is a simple test:

[Fact]
public void RestorableRandomWorks()
{
    RestorableRandom r = new RestorableRandom();
    double firstValueInSequence = r.Generator.NextDouble();
    int state = r.GetState();
    double secondValueInSequence = r.Generator.NextDouble();
    double thirdValueInSequence = r.Generator.NextDouble();
    r.RestoreState(state);
    r.Generator.NextDouble().Should().Be(secondValueInSequence);
    r.Generator.NextDouble().Should().Be(thirdValueInSequence);
}
mbabramo
  • 2,573
  • 2
  • 20
  • 24
  • This would effectively reseed the generator quite often. I'm not sure if the properties of the generator are still preserved this way. But yes this makes the state easily saveable. – Onur Jul 22 '18 at 14:26
  • Yes. Of course, if the underlying generator were sufficiently random, then this would be random too. But there is an interesting question whether this might magnify any imperfections in the underlying algorithm. If it does (and I'm not sure either), how big a problem that is might depend on how often one reseeds. – mbabramo Jul 22 '18 at 14:58
  • This approach does not produce the same sequence had the state not been saved between the first and second calls to NextDouble(); – JohnC Apr 25 '20 at 17:21
  • You can never "capture" the state here. You can only set it. Being able to arbitrarily save it at anytime required making 2 calls every time you want a random number. – Ted Bigham Jul 14 '20 at 13:24
-2

Here is a refined version picking up from some of the answers here, just add this to your project.

public class RandomState
    {

        private static Lazy<System.Reflection.FieldInfo> _seedArrayInfo = new Lazy<System.Reflection.FieldInfo>(typeof(System.Random).GetField("_seedArray", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        private static Lazy<System.Reflection.FieldInfo> _inextInfo = new Lazy<System.Reflection.FieldInfo>(typeof(System.Random).GetField("_inext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        private static Lazy<System.Reflection.FieldInfo> _inextpInfo = new Lazy<System.Reflection.FieldInfo>(typeof(System.Random).GetField("_inextp", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        private static System.Reflection.FieldInfo seedArrayInfo {get { return _seedArrayInfo.Value; }}
        private static System.Reflection.FieldInfo inextInfo { get { return _inextInfo.Value; } }
        private static System.Reflection.FieldInfo inextpInfo { get { return _inextpInfo.Value; } }

        private int[] seedState;
        private int inext;
        private int inextp;
        public static RandomState GetState(Random random)
        {
            var state = new RandomState() { seedState = ((int[])seedArrayInfo.GetValue(random)).ToArray(), inext = (int)inextInfo.GetValue(random), inextp = (int)inextpInfo.GetValue(random) };
            return state;
        }
        public static void SetState(Random random, RandomState state)
        {
            seedArrayInfo.SetValue(random, state.seedState.ToArray());
            inextInfo.SetValue(random, state.inext);
            inextpInfo.SetValue(random, state.inextp);
        }
    }
    public static class RandomExtensions
    {
        public static RandomState GetState (this System.Random random)
        {

            return RandomState.GetState(random);
        }
        public static void ApplyState (this System.Random random, RandomState state)
        {

            RandomState.SetState(random, state);
        }
    }

Example of using it, trying to replicate this.

    public class Program
    {
        public static void Main (string[] args)
        {
            System.Random rnd = new System.Random (255);
            var firststate = rnd.GetState();
            Console.WriteLine("Saved initial state...");
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom("Step ", rnd);
            var oldState = rnd.GetState();
            Console.WriteLine("Saved second state....");
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom("Step ", rnd);
            PrintRandom("Step ", rnd);
            PrintRandom("Step ", rnd);
            rnd.ApplyState(oldState);
            Console.WriteLine("Re-applied second state state....");
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            rnd.ApplyState(firststate);
            Console.WriteLine("Re-applied initial state state....");
            PrintRandom("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
        }

        static void PrintRandom (string label, Random rnd)
        {
            System.Console.WriteLine(string.Format ("{0} - RandomValue {1}", label, rnd.Next (1, 100)));
        }
    }

Output:

Saved initial state...
Step  - RandomValue 94
Step  - RandomValue 64
Step  - RandomValue 1
Saved second state....
Step  - RandomValue 98
Step  - RandomValue 34
Step  - RandomValue 40
Step  - RandomValue 16
Step  - RandomValue 37
Re-applied second state state....
Step  - RandomValue 98
Step  - RandomValue 34
Step  - RandomValue 40
Step  - RandomValue 16
Step  - RandomValue 37
Re-applied initial state state....
Step  - RandomValue 94
Step  - RandomValue 64
Step  - RandomValue 1
-3

Store the amount of times the random number generator ran like Xi Huan wrote.

Then simply loop to restore the old state.

Random rand= new Random();
int oldRNGState = 439394;

for(int i = 1; i < oldRNGState-1; i++) {
    rand.Next(1)
}

Now just do

int lastOldRNGValue = rand.Next(whateverValue);

There is no way around this you have to loop to get back to where you left off.

SSpoke
  • 5,656
  • 10
  • 72
  • 124