pgregory.net/rand@v1.0.3-0.20230808192358-a0b8ce02f4da/rand.go (about)

     1  // Copyright 2022 Gregory Petrosyan <gregory.petrosyan@gmail.com>
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, v. 2.0. If a copy of the MPL was not distributed with this
     5  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
     6  
     7  // Package rand implements pseudo-random number generators unsuitable for
     8  // security-sensitive work.
     9  //
    10  // Top-level functions that do not have a [Rand] parameter, such as [Float64] and [Int],
    11  // use non-deterministic goroutine-local pseudo-random data sources that produce
    12  // different sequences of values each time a program is run. These top-level functions
    13  // are safe for concurrent use by multiple goroutines, and their performance does
    14  // not degrade when the parallelism increases. [Rand] methods and functions with
    15  // [Rand] parameter are not safe for concurrent use, but should generally be preferred
    16  // because of determinism, higher speed and quality.
    17  //
    18  // This package is considerably faster and generates higher quality random
    19  // than the [math/rand] package. However, this package's outputs might be
    20  // predictable regardless of how it's seeded. For random numbers
    21  // suitable for security-sensitive work, see the [crypto/rand] package.
    22  package rand
    23  
    24  import (
    25  	"encoding/binary"
    26  	"io"
    27  	"math"
    28  	"math/bits"
    29  )
    30  
    31  const (
    32  	int24Mask = 1<<24 - 1
    33  	int31Mask = 1<<31 - 1
    34  	int53Mask = 1<<53 - 1
    35  	int63Mask = 1<<63 - 1
    36  	intMask   = math.MaxInt
    37  
    38  	f24Mul = 0x1.0p-24
    39  	f53Mul = 0x1.0p-53
    40  
    41  	randSizeof = 8*4 + 8 + 1
    42  )
    43  
    44  // Rand is a pseudo-random number generator based on the [SFC64] algorithm by Chris Doty-Humphrey.
    45  //
    46  // SFC64 has 256 bits of state, average period of ~2^255 and minimum period of at least 2^64.
    47  // Generators returned by [New] (with empty or distinct seeds) are guaranteed
    48  // to not run into each other for at least 2^64 iterations.
    49  //
    50  // [SFC64]: http://pracrand.sourceforge.net/RNG_engines.txt
    51  type Rand struct {
    52  	sfc64
    53  	val uint64
    54  	pos int
    55  }
    56  
    57  // New returns an initialized generator. If seed is empty, generator is initialized to a non-deterministic state.
    58  // Otherwise, generator is seeded with the values from seed. New panics if len(seed) > 3.
    59  func New(seed ...uint64) *Rand {
    60  	var r Rand
    61  	r.new_(seed...)
    62  	return &r
    63  }
    64  
    65  func (r *Rand) new_(seed ...uint64) {
    66  	switch len(seed) {
    67  	case 0:
    68  		r.init0()
    69  	case 1:
    70  		r.init1(seed[0])
    71  	case 2:
    72  		r.init3(seed[0], seed[1], 0)
    73  	case 3:
    74  		r.init3(seed[0], seed[1], seed[2])
    75  	default:
    76  		panic("invalid New seed sequence length")
    77  	}
    78  }
    79  
    80  // Seed uses the provided seed value to initialize the generator to a deterministic state.
    81  func (r *Rand) Seed(seed uint64) {
    82  	r.init1(seed)
    83  	r.val = 0
    84  	r.pos = 0
    85  }
    86  
    87  // MarshalBinary returns the binary representation of the current state of the generator.
    88  func (r *Rand) MarshalBinary() ([]byte, error) {
    89  	var data [randSizeof]byte
    90  	r.marshalBinary(&data)
    91  	return data[:], nil
    92  }
    93  
    94  func (r *Rand) marshalBinary(data *[randSizeof]byte) {
    95  	binary.LittleEndian.PutUint64(data[0:], r.a)
    96  	binary.LittleEndian.PutUint64(data[8:], r.b)
    97  	binary.LittleEndian.PutUint64(data[16:], r.c)
    98  	binary.LittleEndian.PutUint64(data[24:], r.w)
    99  	binary.LittleEndian.PutUint64(data[32:], r.val)
   100  	data[40] = byte(r.pos)
   101  }
   102  
   103  // UnmarshalBinary sets the state of the generator to the state represented in data.
   104  func (r *Rand) UnmarshalBinary(data []byte) error {
   105  	if len(data) < randSizeof {
   106  		return io.ErrUnexpectedEOF
   107  	}
   108  	r.a = binary.LittleEndian.Uint64(data[0:])
   109  	r.b = binary.LittleEndian.Uint64(data[8:])
   110  	r.c = binary.LittleEndian.Uint64(data[16:])
   111  	r.w = binary.LittleEndian.Uint64(data[24:])
   112  	r.val = binary.LittleEndian.Uint64(data[32:])
   113  	r.pos = int(data[40])
   114  	return nil
   115  }
   116  
   117  // Float32 returns, as a float32, a uniformly distributed pseudo-random number in the half-open interval [0.0, 1.0).
   118  func (r *Rand) Float32() float32 {
   119  	return float32(r.next32()&int24Mask) * f24Mul
   120  }
   121  
   122  // Float64 returns, as a float64, a uniformly distributed pseudo-random number in the half-open interval [0.0, 1.0).
   123  func (r *Rand) Float64() float64 {
   124  	return float64(r.next64()&int53Mask) * f53Mul
   125  }
   126  
   127  // Int returns a uniformly distributed non-negative pseudo-random int.
   128  func (r *Rand) Int() int {
   129  	return int(r.next64() & intMask)
   130  }
   131  
   132  // Int31 returns a uniformly distributed non-negative pseudo-random 31-bit integer as an int32.
   133  func (r *Rand) Int31() int32 {
   134  	return int32(r.next32() & int31Mask)
   135  }
   136  
   137  // Int31n returns, as an int32, a uniformly distributed non-negative pseudo-random number
   138  // in the half-open interval [0, n). It panics if n <= 0.
   139  func (r *Rand) Int31n(n int32) int32 {
   140  	if n <= 0 {
   141  		panic("invalid argument to Int31n")
   142  	}
   143  	return int32(r.Uint32n(uint32(n)))
   144  }
   145  
   146  // Int63 returns a uniformly distributed non-negative pseudo-random 63-bit integer as an int64.
   147  func (r *Rand) Int63() int64 {
   148  	return int64(r.next64() & int63Mask)
   149  }
   150  
   151  // Int63n returns, as an int64, a uniformly distributed non-negative pseudo-random number
   152  // in the half-open interval [0, n). It panics if n <= 0.
   153  func (r *Rand) Int63n(n int64) int64 {
   154  	if n <= 0 {
   155  		panic("invalid argument to Int63n")
   156  	}
   157  	return int64(r.Uint64n(uint64(n)))
   158  }
   159  
   160  // Intn returns, as an int, a uniformly distributed non-negative pseudo-random number
   161  // in the half-open interval [0, n). It panics if n <= 0.
   162  func (r *Rand) Intn(n int) int {
   163  	if n <= 0 {
   164  		panic("invalid argument to Intn")
   165  	}
   166  	if math.MaxInt == math.MaxInt32 {
   167  		return int(r.Uint32n(uint32(n)))
   168  	} else {
   169  		return int(r.Uint64n(uint64(n)))
   170  	}
   171  }
   172  
   173  // Perm returns, as a slice of n ints, a pseudo-random permutation of the integers in the half-open interval [0, n).
   174  func (r *Rand) Perm(n int) []int {
   175  	p := make([]int, n)
   176  	r.perm(p)
   177  	return p
   178  }
   179  
   180  func (r *Rand) perm(p []int) {
   181  	n := len(p)
   182  	b := n
   183  	if b > math.MaxInt32 {
   184  		b = math.MaxInt32
   185  	}
   186  	i := 1
   187  	for ; i < b; i++ {
   188  		j := r.Uint32n(uint32(i) + 1)
   189  		p[i] = p[j]
   190  		p[j] = i
   191  	}
   192  	for ; i < n; i++ {
   193  		j := r.Uint64n(uint64(i) + 1)
   194  		p[i] = p[j]
   195  		p[j] = i
   196  	}
   197  }
   198  
   199  // Read generates len(p) pseudo-random bytes and writes them into p. It always returns len(p) and a nil error.
   200  func (r *Rand) Read(p []byte) (n int, err error) {
   201  	pos := r.pos
   202  	for ; n < len(p) && n < pos; n++ {
   203  		p[n] = byte(r.val)
   204  		r.val >>= 8
   205  		r.pos--
   206  	}
   207  	for ; n+8 <= len(p); n += 8 {
   208  		binary.LittleEndian.PutUint64(p[n:n+8], r.next64())
   209  	}
   210  	if n < len(p) {
   211  		r.val, r.pos = r.next64(), 8
   212  		for ; n < len(p); n++ {
   213  			p[n] = byte(r.val)
   214  			r.val >>= 8
   215  			r.pos--
   216  		}
   217  	}
   218  	return
   219  }
   220  
   221  // Shuffle pseudo-randomizes the order of elements. n is the number of elements. Shuffle panics if n < 0.
   222  // swap swaps the elements with indexes i and j.
   223  //
   224  // For shuffling elements of a slice, prefer the top-level [ShuffleSlice] function.
   225  func (r *Rand) Shuffle(n int, swap func(i, j int)) {
   226  	if n < 0 {
   227  		panic("invalid argument to Shuffle")
   228  	}
   229  	i := n - 1
   230  	for ; i > math.MaxInt32-1; i-- {
   231  		j := int(r.Uint64n(uint64(i) + 1))
   232  		swap(i, j)
   233  	}
   234  	for ; i > 0; i-- {
   235  		j := int(r.Uint32n(uint32(i) + 1))
   236  		swap(i, j)
   237  	}
   238  }
   239  
   240  // Uint32 returns a uniformly distributed pseudo-random 32-bit value as an uint32.
   241  func (r *Rand) Uint32() uint32 {
   242  	return uint32(r.next32())
   243  }
   244  
   245  // next32 has a bit lower inlining cost because of uint64 return value
   246  func (r *Rand) next32() uint64 {
   247  	// unnatural code to fit into inlining budget of 80
   248  	if r.pos < 4 {
   249  		r.val, r.pos = r.next64(), 4
   250  		return r.val >> 32
   251  	} else {
   252  		r.pos = 0
   253  		return r.val
   254  	}
   255  }
   256  
   257  // Uint32n returns, as an uint32, a uniformly distributed pseudo-random number in [0, n). Uint32n(0) returns 0.
   258  func (r *Rand) Uint32n(n uint32) uint32 {
   259  	// much faster 32-bit version of Uint64n(); result is unbiased with probability 1 - 2^-32.
   260  	// detecting possible bias would require at least 2^64 samples, which we consider acceptable
   261  	// since it matches 2^64 guarantees about period length and distance between different seeds.
   262  	// note that 2^64 is probably a very conservative estimate: scaled down 16-bit version of this
   263  	// algorithm passes chi-squared test for at least 2^42 (instead of 2^32) values, so
   264  	// 32-bit version will likely require north of 2^80 values to detect non-uniformity.
   265  	res, _ := bits.Mul64(uint64(n), r.next64())
   266  	return uint32(res)
   267  }
   268  
   269  // Uint64 returns a uniformly distributed pseudo-random 64-bit value as an uint64.
   270  func (r *Rand) Uint64() uint64 {
   271  	return r.next64()
   272  }
   273  
   274  // Uint64n returns, as an uint64, a uniformly distributed pseudo-random number in [0, n). Uint64n(0) returns 0.
   275  func (r *Rand) Uint64n(n uint64) uint64 {
   276  	// "An optimal algorithm for bounded random integers" by Stephen Canon, https://github.com/apple/swift/pull/39143
   277  	res, frac := bits.Mul64(n, r.next64())
   278  	if n <= math.MaxUint32 {
   279  		// we don't use frac <= -n check from the original algorithm, since the branch is unpredictable.
   280  		// instead, we effectively fall back to Uint32n() for 32-bit n
   281  		return res
   282  	}
   283  	hi, _ := bits.Mul64(n, r.next64())
   284  	_, carry := bits.Add64(frac, hi, 0)
   285  	return res + carry
   286  }