rand: allow serialization of PRNG state

Fixes golang/go#35886.

Change-Id: If46dfc2bcbf4a7db84cb66c57f2d377c49d2efc0
Reviewed-on: https://go-review.googlesource.com/c/exp/+/209377
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/rand/rand_test.go b/rand/rand_test.go
index 545b7dd..4383e4f 100644
--- a/rand/rand_test.go
+++ b/rand/rand_test.go
@@ -14,6 +14,7 @@
 	"runtime"
 	"testing"
 	"testing/iotest"
+	"time"
 )
 
 const (
@@ -457,6 +458,34 @@
 	}
 }
 
+func TestPCGSourceRoundTrip(t *testing.T) {
+	var src PCGSource
+	src.Seed(uint64(time.Now().Unix()))
+
+	src.Uint64() // Step PRNG once to makes sure high and low are different.
+
+	buf, err := src.MarshalBinary()
+	if err != nil {
+		t.Errorf("unexpected error marshaling state: %v", err)
+	}
+
+	var dst PCGSource
+	// Get dst into a non-zero state.
+	dst.Seed(1)
+	for i := 0; i < 10; i++ {
+		dst.Uint64()
+	}
+
+	err = dst.UnmarshalBinary(buf)
+	if err != nil {
+		t.Errorf("unexpected error unmarshaling state: %v", err)
+	}
+
+	if dst != src {
+		t.Errorf("mismatch between generator states: got:%+v want:%+v", dst, src)
+	}
+}
+
 // Benchmarks
 
 func BenchmarkSource(b *testing.B) {
diff --git a/rand/rng.go b/rand/rng.go
index f4b0e19..17cee10 100644
--- a/rand/rng.go
+++ b/rand/rng.go
@@ -4,7 +4,11 @@
 
 package rand
 
-import "math/bits"
+import (
+	"encoding/binary"
+	"io"
+	"math/bits"
+)
 
 // PCGSource is an implementation of a 64-bit permuted congruential
 // generator as defined in
@@ -67,3 +71,21 @@
 	pcg.low = lo
 	pcg.high = hi
 }
+
+// MarshalBinary returns the binary representation of the current state of the generator.
+func (pcg *PCGSource) MarshalBinary() ([]byte, error) {
+	var buf [16]byte
+	binary.BigEndian.PutUint64(buf[:8], pcg.high)
+	binary.BigEndian.PutUint64(buf[8:], pcg.low)
+	return buf[:], nil
+}
+
+// UnmarshalBinary sets the state of the generator to the state represented in data.
+func (pcg *PCGSource) UnmarshalBinary(data []byte) error {
+	if len(data) < 16 {
+		return io.ErrUnexpectedEOF
+	}
+	pcg.low = binary.BigEndian.Uint64(data[8:])
+	pcg.high = binary.BigEndian.Uint64(data[:8])
+	return nil
+}