75 lines
1.6 KiB
Go
75 lines
1.6 KiB
Go
// Package rng provides a small deterministic PRNG (xoshiro256**) seeded via
|
|
// splitmix64. Streams are derived from a seed plus labels so that independent
|
|
// parts of the simulation never share (and thus never perturb) each other's
|
|
// random sequence.
|
|
package rng
|
|
|
|
import (
|
|
"math/bits"
|
|
|
|
"wh/fixed"
|
|
)
|
|
|
|
type R struct{ s [4]uint64 }
|
|
|
|
func splitmix(x *uint64) uint64 {
|
|
*x += 0x9e3779b97f4a7c15
|
|
z := *x
|
|
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9
|
|
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
|
|
return z ^ (z >> 31)
|
|
}
|
|
|
|
// New returns a generator for the given seed.
|
|
func New(seed uint64) *R {
|
|
r := &R{}
|
|
for i := range r.s {
|
|
r.s[i] = splitmix(&seed)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Derive returns a generator for an independent stream identified by seed and
|
|
// labels, e.g. Derive(worldSeed, day, shipID).
|
|
func Derive(seed uint64, labels ...uint64) *R {
|
|
h := seed
|
|
for _, l := range labels {
|
|
h ^= l + 0x9e3779b97f4a7c15 + (h << 6) + (h >> 2)
|
|
splitmix(&h)
|
|
}
|
|
return New(h)
|
|
}
|
|
|
|
func (r *R) Uint64() uint64 {
|
|
s := &r.s
|
|
res := bits.RotateLeft64(s[1]*5, 7) * 9
|
|
t := s[1] << 17
|
|
s[2] ^= s[0]
|
|
s[3] ^= s[1]
|
|
s[1] ^= s[2]
|
|
s[0] ^= s[3]
|
|
s[2] ^= t
|
|
s[3] = bits.RotateLeft64(s[3], 45)
|
|
return res
|
|
}
|
|
|
|
// Intn returns a uniform value in [0, n). n must be > 0.
|
|
func (r *R) Intn(n uint64) uint64 {
|
|
hi, lo := bits.Mul64(r.Uint64(), n)
|
|
if lo < n {
|
|
thresh := -n % n
|
|
for lo < thresh {
|
|
hi, lo = bits.Mul64(r.Uint64(), n)
|
|
}
|
|
}
|
|
return hi
|
|
}
|
|
|
|
// Fixed returns a uniform value in [0, 1).
|
|
func (r *R) Fixed() fixed.F { return fixed.F(r.Uint64() >> 32) }
|
|
|
|
// FixedRange returns a uniform value in [lo, hi).
|
|
func (r *R) FixedRange(lo, hi fixed.F) fixed.F {
|
|
return lo + r.Fixed().Mul(hi-lo)
|
|
}
|