a single Python step, and is, therefore, threadsafe.
"""
-
+from types import BuiltinMethodType as _BuiltinMethodType
from math import log as _log, exp as _exp, pi as _pi, e as _e
from math import sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin
from math import floor as _floor
"randrange","shuffle","normalvariate","lognormvariate",
"cunifvariate","expovariate","vonmisesvariate","gammavariate",
"stdgamma","gauss","betavariate","paretovariate","weibullvariate",
- "getstate","setstate","jumpahead"]
+ "getstate","setstate","jumpahead", "WichmannHill"]
NV_MAGICCONST = 4 * _exp(-0.5)/_sqrt(2.0)
TWOPI = 2.0*_pi
LOG4 = _log(4.0)
SG_MAGICCONST = 1.0 + _log(4.5)
+BPF = 53 # Number of bits in a float
# Translated by Guido van Rossum from C source provided by
# Adrian Baddeley. Adapted by Raymond Hettinger for use with
## -------------------- integer methods -------------------
- def randrange(self, start, stop=None, step=1, int=int, default=None):
+ def randrange(self, start, stop=None, step=1, int=int, default=None,
+ maxwidth=1L<<BPF, _BuiltinMethod=_BuiltinMethodType):
"""Choose a random item from range(start, stop[, step]).
This fixes the problem with randint() which includes the
endpoint; in Python this is usually not what you want.
- Do not supply the 'int' and 'default' arguments.
+ Do not supply the 'int', 'default', and 'maxwidth' arguments.
"""
# This code is a bit messy to make it fast for the
raise ValueError, "non-integer arg 1 for randrange()"
if stop is default:
if istart > 0:
+ if istart >= maxwidth and type(self.random) is _BuiltinMethod:
+ return self._randbelow(istart)
return int(self.random() * istart)
raise ValueError, "empty range for randrange()"
istop = int(stop)
if istop != stop:
raise ValueError, "non-integer stop for randrange()"
- if step == 1 and istart < istop:
+ width = istop - istart
+ if step == 1 and width > 0:
# Note that
# int(istart + self.random()*(istop - istart))
# instead would be incorrect. For example, consider istart
# can return a long, and then randrange() would also return
# a long, but we're supposed to return an int (for backward
# compatibility).
- return int(istart + int(self.random()*(istop - istart)))
+ if width >= maxwidth and type(self.random) is _BuiltinMethod:
+ return int(istart + self._randbelow(width))
+ return int(istart + int(self.random()*width))
if step == 1:
raise ValueError, "empty range for randrange()"
if istep != step:
raise ValueError, "non-integer step for randrange()"
if istep > 0:
- n = (istop - istart + istep - 1) / istep
+ n = (width + istep - 1) / istep
elif istep < 0:
- n = (istop - istart + istep + 1) / istep
+ n = (width + istep + 1) / istep
else:
raise ValueError, "zero step for randrange()"
if n <= 0:
raise ValueError, "empty range for randrange()"
+
+ if n >= maxwidth and type(self.random) is _BuiltinMethod:
+ return istart + self._randbelow(n)
return istart + istep*int(self.random() * n)
def randint(self, a, b):
return self.randrange(a, b+1)
+ def _randbelow(self, n, bpf=BPF, maxwidth=1L<<BPF,
+ long=long, _log=_log, int=int):
+ """Return a random int in the range [0,n)
+
+ Handles the case where n has more bits than returned
+ by a single call to the underlying generator.
+ """
+
+ # k is a sometimes over but never under estimate of the bits in n
+ k = int(1.00001 + _log(n-1, 2)) # 2**k > n-1 >= 2**(k-2)
+
+ random = self.random
+ r = n
+ while r >= n:
+ # In Py2.4, this section becomes: r = self.getrandbits(k)
+ r = long(random() * maxwidth)
+ bits = bpf
+ while bits < k:
+ r = (r << bpf) | (long(random() * maxwidth))
+ bits += bpf
+ r >>= (bits - k)
+ return r
+
## -------------------- sequence methods -------------------
def choice(self, seq):
seed = (1L << (10000 * 8)) - 1 # about 10K bytes
self.gen.seed(seed)
+ def test_53_bits_per_float(self):
+ # This should pass whenever a C double has 53 bit precision.
+ span = 2 ** 53
+ cum = 0
+ for i in xrange(100):
+ cum |= int(self.gen.random() * span)
+ self.assertEqual(cum, span-1)
+
+ def test_bigrand(self):
+ # The randrange routine should build-up the required number of bits
+ # in stages so that all bit positions are active.
+ span = 2 ** 500
+ cum = 0
+ for i in xrange(100):
+ r = self.gen.randrange(span)
+ self.assert_(0 <= r < span)
+ cum |= r
+ self.assertEqual(cum, span-1)
+
+ def test_bigrand_ranges(self):
+ for i in [40,80, 160, 200, 211, 250, 375, 512, 550]:
+ start = self.gen.randrange(2 ** i)
+ stop = self.gen.randrange(2 ** (i-2))
+ if stop <= start:
+ return
+ self.assert_(start <= self.gen.randrange(start, stop) < stop)
+
+ def test_rangelimits(self):
+ for start, stop in [(-2,0), (-(2**60)-2,-(2**60)), (2**60,2**60+2)]:
+ self.assertEqual(Set(range(start,stop)),
+ Set([self.gen.randrange(start,stop) for i in xrange(100)]))
+
_gammacoeff = (0.9999999999995183, 676.5203681218835, -1259.139216722289,
771.3234287757674, -176.6150291498386, 12.50734324009056,
-0.1385710331296526, 0.9934937113930748e-05, 0.1659470187408462e-06)