github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/perf/fastrand/pcg.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fastrand
     6  
     7  import "math/bits"
     8  
     9  // https://numpy.org/devdocs/reference/random/upgrading-pcg64.html
    10  // https://github.com/imneme/pcg-cpp/commit/871d0494ee9c9a7b7c43f753e3d8ca47c26f8005
    11  
    12  // NewPCG returns a new PCG generator seeded with the given values.
    13  func NewPCG(seed1, seed2 uint64) *Rand {
    14  	return &Rand{
    15  		src: &pcgSource{seed1, seed2},
    16  	}
    17  }
    18  
    19  // A pcgSource is a PCG generator with 128 bits of internal state.
    20  // A zero pcgSource is equivalent to pcgSource{0, 0}.
    21  type pcgSource struct {
    22  	hi uint64
    23  	lo uint64
    24  }
    25  
    26  func (p *pcgSource) next() (hi, lo uint64) {
    27  	// https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L161
    28  	//
    29  	// Numpy's pcgSource multiplies by the 64-bit value cheapMul
    30  	// instead of the 128-bit value used here and in the official pcgSource code.
    31  	// This does not seem worthwhile, at least for Go: not having any high
    32  	// bits in the multiplier reduces the effect of low bits on the highest bits,
    33  	// and it only saves 1 multiply out of 3.
    34  	// (On 32-bit systems, it saves 1 out of 6, since Mul64 is doing 4.)
    35  	const (
    36  		mulHi = 2549297995355413924
    37  		mulLo = 4865540595714422341
    38  		incHi = 6364136223846793005
    39  		incLo = 1442695040888963407
    40  	)
    41  
    42  	// state = state * mul + inc
    43  	hi, lo = bits.Mul64(p.lo, mulLo)
    44  	hi += p.hi*mulLo + p.lo*mulHi
    45  	lo, c := bits.Add64(lo, incLo, 0)
    46  	hi, _ = bits.Add64(hi, incHi, c)
    47  	p.lo = lo
    48  	p.hi = hi
    49  	return hi, lo
    50  }
    51  
    52  // Uint64 return a uniformly-distributed random uint64 value.
    53  func (p *pcgSource) Uint64() uint64 {
    54  	hi, lo := p.next()
    55  
    56  	// XSL-RR would be
    57  	//	hi, lo := p.next()
    58  	//	return bits.RotateLeft64(lo^hi, -int(hi>>58))
    59  	// but Numpy uses DXSM and O'Neill suggests doing the same.
    60  	// See https://github.com/golang/go/issues/21835#issuecomment-739065688
    61  	// and following comments.
    62  
    63  	// DXSM "double xorshift multiply"
    64  	// https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L1015
    65  
    66  	// https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L176
    67  	const cheapMul = 0xda942042e4dd58b5
    68  	hi ^= hi >> 32
    69  	hi *= cheapMul
    70  	hi ^= hi >> 48
    71  	hi *= (lo | 1)
    72  	return hi
    73  }