github.com/cznic/mathutil@v0.0.0-20181122101859-297441e03548/rnd.go (about)

     1  // Copyright (c) 2014 The mathutil 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 mathutil
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"math/big"
    11  )
    12  
    13  // FC32 is a full cycle PRNG covering the 32 bit signed integer range.
    14  // In contrast to full cycle generators shown at e.g. http://en.wikipedia.org/wiki/Full_cycle,
    15  // this code doesn't produce values at constant delta (mod cycle length).
    16  // The 32 bit limit is per this implementation, the algorithm used has no intrinsic limit on the cycle size.
    17  // Properties include:
    18  //	- Adjustable limits on creation (hi, lo).
    19  //	- Positionable/randomly accessible (Pos, Seek).
    20  //	- Repeatable (deterministic).
    21  //	- Can run forward or backward (Next, Prev).
    22  //	- For a billion numbers cycle the Next/Prev PRN can be produced in cca 100-150ns.
    23  //	  That's like 5-10 times slower compared to PRNs generated using the (non FC) rand package.
    24  type FC32 struct {
    25  	cycle   int64     // On average: 3 * delta / 2, (HQ: 2 * delta)
    26  	delta   int64     // hi - lo
    27  	factors [][]int64 // This trades some space for hopefully a bit of speed (multiple adding vs multiplying).
    28  	lo      int
    29  	mods    []int   // pos % set
    30  	pos     int64   // Within cycle.
    31  	primes  []int64 // Ordered. ∏ primes == cycle.
    32  	set     []int64 // Reordered primes (magnitude order bases) according to seed.
    33  }
    34  
    35  // NewFC32 returns a newly created FC32 adjusted for the closed interval [lo, hi] or an Error if any.
    36  // If hq == true then trade some generation time for improved (pseudo)randomness.
    37  func NewFC32(lo, hi int, hq bool) (r *FC32, err error) {
    38  	if lo > hi {
    39  		return nil, fmt.Errorf("invalid range %d > %d", lo, hi)
    40  	}
    41  
    42  	if uint64(hi)-uint64(lo) > math.MaxUint32 {
    43  		return nil, fmt.Errorf("range out of int32 limits %d, %d", lo, hi)
    44  	}
    45  
    46  	delta := int64(hi) - int64(lo)
    47  	// Find the primorial covering whole delta
    48  	n, set, p := int64(1), []int64{}, uint32(2)
    49  	if hq {
    50  		p++
    51  	}
    52  	for {
    53  		set = append(set, int64(p))
    54  		n *= int64(p)
    55  		if n > delta {
    56  			break
    57  		}
    58  		p, _ = NextPrime(p)
    59  	}
    60  
    61  	// Adjust the set so n ∊ [delta, 2 * delta] (HQ: [delta, 3 * delta])
    62  	// while keeping the cardinality of the set (correlates with the statistic "randomness quality")
    63  	// at max, i.e. discard atmost one member.
    64  	i := -1 // no candidate prime
    65  	if n > 2*(delta+1) {
    66  		for j, p := range set {
    67  			q := n / p
    68  			if q < delta+1 {
    69  				break
    70  			}
    71  
    72  			i = j // mark the highest candidate prime set index
    73  		}
    74  	}
    75  	if i >= 0 { // shrink the inner cycle
    76  		n = n / set[i]
    77  		set = delete(set, i)
    78  	}
    79  	r = &FC32{
    80  		cycle:   n,
    81  		delta:   delta,
    82  		factors: make([][]int64, len(set)),
    83  		lo:      lo,
    84  		mods:    make([]int, len(set)),
    85  		primes:  set,
    86  	}
    87  	r.Seed(1) // the default seed should be always non zero
    88  	return
    89  }
    90  
    91  // Cycle reports the length of the inner FCPRNG cycle.
    92  // Cycle is atmost the double (HQ: triple) of the generator period (hi - lo + 1).
    93  func (r *FC32) Cycle() int64 {
    94  	return r.cycle
    95  }
    96  
    97  // Next returns the first PRN after Pos.
    98  func (r *FC32) Next() int {
    99  	return r.step(1)
   100  }
   101  
   102  // Pos reports the current position within the inner cycle.
   103  func (r *FC32) Pos() int64 {
   104  	return r.pos
   105  }
   106  
   107  // Prev return the first PRN before Pos.
   108  func (r *FC32) Prev() int {
   109  	return r.step(-1)
   110  }
   111  
   112  // Seed uses the provided seed value to initialize the generator to a deterministic state.
   113  // A zero seed produces a "canonical" generator with worse randomness than for most non zero seeds.
   114  // Still, the FC property holds for any seed value.
   115  func (r *FC32) Seed(seed int64) {
   116  	u := uint64(seed)
   117  	r.set = mix(r.primes, &u)
   118  	n := int64(1)
   119  	for i, p := range r.set {
   120  		k := make([]int64, p)
   121  		v := int64(0)
   122  		for j := range k {
   123  			k[j] = v
   124  			v += n
   125  		}
   126  		n *= p
   127  		r.factors[i] = mix(k, &u)
   128  	}
   129  }
   130  
   131  // Seek sets Pos to |pos| % Cycle.
   132  func (r *FC32) Seek(pos int64) { //vet:ignore
   133  	if pos < 0 {
   134  		pos = -pos
   135  	}
   136  	pos %= r.cycle
   137  	r.pos = pos
   138  	for i, p := range r.set {
   139  		r.mods[i] = int(pos % p)
   140  	}
   141  }
   142  
   143  func (r *FC32) step(dir int) int {
   144  	for { // avg loops per step: 3/2 (HQ: 2)
   145  		y := int64(0)
   146  		pos := r.pos
   147  		pos += int64(dir)
   148  		switch {
   149  		case pos < 0:
   150  			pos = r.cycle - 1
   151  		case pos >= r.cycle:
   152  			pos = 0
   153  		}
   154  		r.pos = pos
   155  		for i, mod := range r.mods {
   156  			mod += dir
   157  			p := int(r.set[i])
   158  			switch {
   159  			case mod < 0:
   160  				mod = p - 1
   161  			case mod >= p:
   162  				mod = 0
   163  			}
   164  			r.mods[i] = mod
   165  			y += r.factors[i][mod]
   166  		}
   167  		if y <= r.delta {
   168  			return int(y) + r.lo
   169  		}
   170  	}
   171  }
   172  
   173  func delete(set []int64, i int) (y []int64) {
   174  	for j, v := range set {
   175  		if j != i {
   176  			y = append(y, v)
   177  		}
   178  	}
   179  	return
   180  }
   181  
   182  func mix(set []int64, seed *uint64) (y []int64) {
   183  	for len(set) != 0 {
   184  		*seed = rol(*seed)
   185  		i := int(*seed % uint64(len(set)))
   186  		y = append(y, set[i])
   187  		set = delete(set, i)
   188  	}
   189  	return
   190  }
   191  
   192  func rol(u uint64) (y uint64) {
   193  	y = u << 1
   194  	if int64(u) < 0 {
   195  		y |= 1
   196  	}
   197  	return
   198  }
   199  
   200  // FCBig is a full cycle PRNG covering ranges outside of the int32 limits.
   201  // For more info see the FC32 docs.
   202  // Next/Prev PRN on a 1e15 cycle can be produced in about 2 µsec.
   203  type FCBig struct {
   204  	cycle   *big.Int     // On average: 3 * delta / 2, (HQ: 2 * delta)
   205  	delta   *big.Int     // hi - lo
   206  	factors [][]*big.Int // This trades some space for hopefully a bit of speed (multiple adding vs multiplying).
   207  	lo      *big.Int
   208  	mods    []int    // pos % set
   209  	pos     *big.Int // Within cycle.
   210  	primes  []int64  // Ordered. ∏ primes == cycle.
   211  	set     []int64  // Reordered primes (magnitude order bases) according to seed.
   212  }
   213  
   214  // NewFCBig returns a newly created FCBig adjusted for the closed interval [lo, hi] or an Error if any.
   215  // If hq == true then trade some generation time for improved (pseudo)randomness.
   216  func NewFCBig(lo, hi *big.Int, hq bool) (r *FCBig, err error) {
   217  	if lo.Cmp(hi) > 0 {
   218  		return nil, fmt.Errorf("invalid range %d > %d", lo, hi)
   219  	}
   220  
   221  	delta := big.NewInt(0)
   222  	delta.Add(delta, hi).Sub(delta, lo)
   223  
   224  	// Find the primorial covering whole delta
   225  	n, set, pp, p := big.NewInt(1), []int64{}, big.NewInt(0), uint32(2)
   226  	if hq {
   227  		p++
   228  	}
   229  	for {
   230  		set = append(set, int64(p))
   231  		pp.SetInt64(int64(p))
   232  		n.Mul(n, pp)
   233  		if n.Cmp(delta) > 0 {
   234  			break
   235  		}
   236  		p, _ = NextPrime(p)
   237  	}
   238  
   239  	// Adjust the set so n ∊ [delta, 2 * delta] (HQ: [delta, 3 * delta])
   240  	// while keeping the cardinality of the set (correlates with the statistic "randomness quality")
   241  	// at max, i.e. discard atmost one member.
   242  	dd1 := big.NewInt(1)
   243  	dd1.Add(dd1, delta)
   244  	dd2 := big.NewInt(0)
   245  	dd2.Lsh(dd1, 1)
   246  	i := -1 // no candidate prime
   247  	if n.Cmp(dd2) > 0 {
   248  		q := big.NewInt(0)
   249  		for j, p := range set {
   250  			pp.SetInt64(p)
   251  			q.Set(n)
   252  			q.Div(q, pp)
   253  			if q.Cmp(dd1) < 0 {
   254  				break
   255  			}
   256  
   257  			i = j // mark the highest candidate prime set index
   258  		}
   259  	}
   260  	if i >= 0 { // shrink the inner cycle
   261  		pp.SetInt64(set[i])
   262  		n.Div(n, pp)
   263  		set = delete(set, i)
   264  	}
   265  	r = &FCBig{
   266  		cycle:   n,
   267  		delta:   delta,
   268  		factors: make([][]*big.Int, len(set)),
   269  		lo:      lo,
   270  		mods:    make([]int, len(set)),
   271  		pos:     big.NewInt(0),
   272  		primes:  set,
   273  	}
   274  	r.Seed(1) // the default seed should be always non zero
   275  	return
   276  }
   277  
   278  // Cycle reports the length of the inner FCPRNG cycle.
   279  // Cycle is atmost the double (HQ: triple) of the generator period (hi - lo + 1).
   280  func (r *FCBig) Cycle() *big.Int {
   281  	return r.cycle
   282  }
   283  
   284  // Next returns the first PRN after Pos.
   285  func (r *FCBig) Next() *big.Int {
   286  	return r.step(1)
   287  }
   288  
   289  // Pos reports the current position within the inner cycle.
   290  func (r *FCBig) Pos() *big.Int {
   291  	return r.pos
   292  }
   293  
   294  // Prev return the first PRN before Pos.
   295  func (r *FCBig) Prev() *big.Int {
   296  	return r.step(-1)
   297  }
   298  
   299  // Seed uses the provided seed value to initialize the generator to a deterministic state.
   300  // A zero seed produces a "canonical" generator with worse randomness than for most non zero seeds.
   301  // Still, the FC property holds for any seed value.
   302  func (r *FCBig) Seed(seed int64) {
   303  	u := uint64(seed)
   304  	r.set = mix(r.primes, &u)
   305  	n := big.NewInt(1)
   306  	v := big.NewInt(0)
   307  	pp := big.NewInt(0)
   308  	for i, p := range r.set {
   309  		k := make([]*big.Int, p)
   310  		v.SetInt64(0)
   311  		for j := range k {
   312  			k[j] = big.NewInt(0)
   313  			k[j].Set(v)
   314  			v.Add(v, n)
   315  		}
   316  		pp.SetInt64(p)
   317  		n.Mul(n, pp)
   318  		r.factors[i] = mixBig(k, &u)
   319  	}
   320  }
   321  
   322  // Seek sets Pos to |pos| % Cycle.
   323  func (r *FCBig) Seek(pos *big.Int) {
   324  	r.pos.Set(pos)
   325  	r.pos.Abs(r.pos)
   326  	r.pos.Mod(r.pos, r.cycle)
   327  	mod := big.NewInt(0)
   328  	pp := big.NewInt(0)
   329  	for i, p := range r.set {
   330  		pp.SetInt64(p)
   331  		r.mods[i] = int(mod.Mod(r.pos, pp).Int64())
   332  	}
   333  }
   334  
   335  func (r *FCBig) step(dir int) (y *big.Int) {
   336  	y = big.NewInt(0)
   337  	d := big.NewInt(int64(dir))
   338  	for { // avg loops per step: 3/2 (HQ: 2)
   339  		r.pos.Add(r.pos, d)
   340  		switch {
   341  		case r.pos.Sign() < 0:
   342  			r.pos.Add(r.pos, r.cycle)
   343  		case r.pos.Cmp(r.cycle) >= 0:
   344  			r.pos.SetInt64(0)
   345  		}
   346  		for i, mod := range r.mods {
   347  			mod += dir
   348  			p := int(r.set[i])
   349  			switch {
   350  			case mod < 0:
   351  				mod = p - 1
   352  			case mod >= p:
   353  				mod = 0
   354  			}
   355  			r.mods[i] = mod
   356  			y.Add(y, r.factors[i][mod])
   357  		}
   358  		if y.Cmp(r.delta) <= 0 {
   359  			y.Add(y, r.lo)
   360  			return
   361  		}
   362  		y.SetInt64(0)
   363  	}
   364  }
   365  
   366  func deleteBig(set []*big.Int, i int) (y []*big.Int) {
   367  	for j, v := range set {
   368  		if j != i {
   369  			y = append(y, v)
   370  		}
   371  	}
   372  	return
   373  }
   374  
   375  func mixBig(set []*big.Int, seed *uint64) (y []*big.Int) {
   376  	for len(set) != 0 {
   377  		*seed = rol(*seed)
   378  		i := int(*seed % uint64(len(set)))
   379  		y = append(y, set[i])
   380  		set = deleteBig(set, i)
   381  	}
   382  	return
   383  }