Reputation: 664
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
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
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
}
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
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