Kerby Shedden
Kerby Shedden

Reputation: 664

Save random state in golang

Is there a way to save the random state in the golang math/rand package?

I would like to serialize it and store it for later use, but the random state is an interface and the concrete struct underneath the interface is unexported (so json.Marshall apparently cannot be used).

As an alternative to saving the rand.Source object, I thought about just saving the underlying int64 seed value. You can set it with rand.Seed, but I don't see a way to obtain the seed value so that it can be retained for later use.

Upvotes: 4

Views: 1484

Answers (3)

ionous
ionous

Reputation: 11

go 1.22 added package rand/v2 which supports saving and restoring state.

you first create a source and a generator from that source. you can then marshal the source to capture its current state, and unmarshal into it to restore the state.

// create a source ( with better seed values than these ) 
src := rand.NewPCG(1, 2)
// create a generator
// and use r.Int(), etc. to generate numbers
r := rand.New(src)

// when you need, save the state:
b, err := src.MarshalBinary()
val := r.Int()

// and later... restore the state:
err = src.UnmarshalBinary(b)
newVal:= r.Int()

the val and newVal will be the same.

complete example: https://play.golang.com/p/lV46qHX5WeL

Upvotes: 1

icza
icza

Reputation: 417837

As you already noted, the math/rand package does not give you insight into the current state (seed) of the (pseudo-)random number generator. If you would need this, you would have to implement it on your own (as mentioned by Anonymous).

If you would: Ok, let's say you know that the internal state of the random number generator is equivalent to where a seed value of 1234 would be set. How is this better than if the seed is any other concrete or "random" number?

Here's a tip how to "simulate" access to the seed value of the generator:

Let's say you've already created your Rand object, you've set it up and used it (already generated some random numbers). This Rand object may as well be the default/global of the math/rand package, it doesn't have to be a distinct Rand.

You reach a point where you would like to save the "sate" of the random number generator in order so that later you can repeat the exact pseudo-random sequence from this point. This would need to get the current seed, and later when you want to repeat the sequence from this point, you would just set the stored seed to the Rand object. Problem: the seed you can't access.

But what you can do is set a new seed and then you know that the internal seed is the one you just set! So to simulate a GetSeed() (on the default Rand of the math/rand package):

func GetSeed() int64 {
    seed := time.Now().UnixNano() // A new random seed (independent from state)
    rand.Seed(seed)
    return seed
}

To simulate a GetSeed() or any Rand (not the default one):

func GetSeed2(r rand.Rand) int64 {
    seed := time.Now().UnixNano() // A new random seed (independent from state)
    r.Seed(seed)
    return seed
}

Preserving the "pseudo" part of the randomness

The above proposed GetSeed() uses the current time to "re-seed" the Rand object. This will alter the pseudo-random sequence based on the time it was called at. This may or may not be ok (in most of the times this is not a problem).

But if it is, we can avoid this if we use the Rand object itself to designate the new seed like this (by doing this the new seed will only depend on the current state - the current seed):

func GetSeed() int64 {
    seed := rand.Int63() // A new pseudo-random seed: determined by current state/seed
    rand.Seed(seed)
    return seed
}

func GetSeed2(r rand.Rand) int64 {
    seed := r.Int63() // A new pseudo-random seed: determined by current state/seed
    r.Seed(seed)
    return seed
}

Notes:

The GetSeed() functionality is designed to used occasionally, e.g. when you want to save a game. Normal usage would be to generate thousands of random numbers and call GetSeed() once only.

As Anonymous pointed out, with the current algorithm of the pseudo-random number generator, in some extreme situations (for example you call GetSeed() after each generated random number) this may result in a cycle in the generated random numbers and would return the previously generated sequence of random numbers. The length of the sequence may be around a couple of thousands. Here is an example of this where the length of the sequence is 8034: Repetition Go Playground Example.

But again: this is not a normal usage, GetSeed() is called intentionally after each random number generation; and this only applies if you use Rand itself to generate the new seed. If you re-seed your Rand object with the current time for example, repetition will not occur.

Upvotes: 2

Paul Hankin
Paul Hankin

Reputation: 58280

You can make your own random number source which can be marshalled.

One way would be to simply copy the code from http://golang.org/src/math/rand/rng.go and adapt it by adding code that can marshal the state in whatever way you want. That'll give you your own rand.Source that you can use in rand.New(myRandSource) to generate random numbers.

Upvotes: 1

Related Questions