Index: Misc/NEWS =================================================================== RCS file: /cvsroot/python/python/dist/src/Misc/NEWS,v retrieving revision 1.861 diff -c -r1.861 NEWS *** Misc/NEWS 23 Sep 2003 20:21:00 -0000 1.861 --- Misc/NEWS 3 Oct 2003 02:38:13 -0000 *************** *** 74,79 **** --- 74,91 ---- seed. Modified to match Py2.2 behavior and use fractional seconds so that successive runs are more likely to produce different sequences. + - random.WichmannHill and random.Random are now subclasses of a new + class, BaseRandom. Formerly WichmannHill inherited from Random. + User defined random numbers generators should inherit from BaseRandom. + + - random.Random has a new method, getrandbits(k), which returns an int + with k random bits. This method is now an optional part of the API + for user defined generators. Any generator that defines genrandbits() + can now use randrange() for ranges with a length >= 2**53. Formerly, + randrange would return only even numbers for ranges that large (see + SF bug #812202). Generators that do not define genrandbits() now + issue a warning when randrange() is called with ranges that large. + - itertools.izip() with no arguments now returns an empty iterator instead of raising a TypeError exception. Index: Lib/random.py =================================================================== RCS file: /cvsroot/python/python/dist/src/Lib/random.py,v retrieving revision 1.55 diff -c -r1.55 random.py *** Lib/random.py 6 Sep 2003 04:25:54 -0000 1.55 --- Lib/random.py 3 Oct 2003 02:38:13 -0000 *************** *** 47,66 **** "randrange","shuffle","normalvariate","lognormvariate", "expovariate","vonmisesvariate","gammavariate", "gauss","betavariate","paretovariate","weibullvariate", ! "getstate","setstate","jumpahead"] NV_MAGICCONST = 4 * _exp(-0.5)/_sqrt(2.0) TWOPI = 2.0*_pi LOG4 = _log(4.0) SG_MAGICCONST = 1.0 + _log(4.5) # Translated by Guido van Rossum from C source provided by # Adrian Baddeley. Adapted by Raymond Hettinger for use with # the Mersenne Twister core generator. ! import _random ! ! class Random(_random.Random): """Random number generator base class used by bound module functions. Used to instantiate instances of Random to get generators that don't --- 47,66 ---- "randrange","shuffle","normalvariate","lognormvariate", "expovariate","vonmisesvariate","gammavariate", "gauss","betavariate","paretovariate","weibullvariate", ! "getstate","setstate","jumpahead", "WichmannHill", "getrandbits", ! "BaseRandom"] 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 # the Mersenne Twister core generator. ! class BaseRandom(object): """Random number generator base class used by bound module functions. Used to instantiate instances of Random to get generators that don't *************** *** 97,115 **** if a is None: import time a = long(time.time() * 256) # use fractional seconds ! super(Random, self).seed(a) self.gauss_next = None def getstate(self): """Return internal state; can be passed to setstate() later.""" ! return self.VERSION, super(Random, self).getstate(), self.gauss_next def setstate(self, state): """Restore internal state from object returned by getstate().""" version = state[0] if version == 2: version, internalstate, self.gauss_next = state ! super(Random, self).setstate(internalstate) else: raise ValueError("state with version %s passed to " "Random.setstate() of version %s" % --- 97,115 ---- if a is None: import time a = long(time.time() * 256) # use fractional seconds ! super(BaseRandom, self).seed(a) self.gauss_next = None def getstate(self): """Return internal state; can be passed to setstate() later.""" ! return self.VERSION, super(BaseRandom, self).getstate(), self.gauss_next def setstate(self, state): """Restore internal state from object returned by getstate().""" version = state[0] if version == 2: version, internalstate, self.gauss_next = state ! super(BaseRandom, self).setstate(internalstate) else: raise ValueError("state with version %s passed to " "Random.setstate() of version %s" % *************** *** 131,142 **** ## -------------------- integer methods ------------------- ! def randrange(self, start, stop=None, step=1, int=int, default=None): """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. """ # This code is a bit messy to make it fast for the --- 131,143 ---- ## -------------------- integer methods ------------------- ! def randrange(self, start, stop=None, step=1, int=int, default=None, ! maxwidth=1L< 0: + if istart >= maxwidth: + return self._randbelow(istart) return int(self.random() * istart) raise ValueError, "empty range for randrange()" *************** *** 153,188 **** istop = int(stop) if istop != stop: raise ValueError, "non-integer stop for randrange()" ! if step == 1 and istart < istop: # Note that ! # int(istart + self.random()*(istop - istart)) # instead would be incorrect. For example, consider istart # = -2 and istop = 0. Then the guts would be in # -2.0 to 0.0 exclusive on both ends (ignoring that random() # might return 0.0), and because int() truncates toward 0, the # final result would be -1 or 0 (instead of -2 or -1). ! # istart + int(self.random()*(istop - istart)) # would also be incorrect, for a subtler reason: the RHS # 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 step == 1: ! raise ValueError, "empty range for randrange()" # Non-unit step argument supplied. istep = int(step) if istep != step: raise ValueError, "non-integer step for randrange()" if istep > 0: ! n = (istop - istart + istep - 1) / istep elif istep < 0: ! n = (istop - istart + istep + 1) / istep else: raise ValueError, "zero step for randrange()" if n <= 0: raise ValueError, "empty range for randrange()" return istart + istep*int(self.random() * n) def randint(self, a, b): --- 156,198 ---- istop = int(stop) if istop != stop: raise ValueError, "non-integer stop for randrange()" ! width = istop - istart ! if step == 1 and width > 0: # Note that ! # int(istart + self.random()*width) # instead would be incorrect. For example, consider istart # = -2 and istop = 0. Then the guts would be in # -2.0 to 0.0 exclusive on both ends (ignoring that random() # might return 0.0), and because int() truncates toward 0, the # final result would be -1 or 0 (instead of -2 or -1). ! # istart + int(self.random()*width) # would also be incorrect, for a subtler reason: the RHS # can return a long, and then randrange() would also return # a long, but we're supposed to return an int (for backward # compatibility). ! ! if width >= maxwidth: ! return int(istart + self._randbelow(width)) ! return int(istart + int(self.random()*width)) if step == 1: ! raise ValueError, "empty range for randrange() (%d,%d, %d)" % (istart, istop, width) # Non-unit step argument supplied. istep = int(step) if istep != step: raise ValueError, "non-integer step for randrange()" if istep > 0: ! n = (width + istep - 1) / istep elif istep < 0: ! n = (width + istep + 1) / istep else: raise ValueError, "zero step for randrange()" if n <= 0: raise ValueError, "empty range for randrange()" + + if n >= maxwidth: + return istart + self._randbelow(n) return istart + istep*int(self.random() * n) def randint(self, a, b): *************** *** 191,196 **** --- 201,228 ---- return self.randrange(a, b+1) + def _randbelow(self, n, _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.001 + _log(n, 2)) # 2**k > n >= 2**(k-2) + r = n + try: + while r >= n: + r = self.getrandbits(k) + except AttributeError: + if n >= 1L<> 8); + bytearray[i+2] = (unsigned char)(r >> 16); + bytearray[i+3] = (unsigned char)(r >> 24); + } + + /* Fill-out remaining bits in the final, partial word */ + r = genrand_int32(self) >> (bytes * 8 - k); + bytearray[i+0] = (unsigned char)r; + bytearray[i+1] = (unsigned char)(r >> 8); + bytearray[i+2] = (unsigned char)(r >> 16); + bytearray[i+3] = (unsigned char)(r >> 24); + + /* little endian order to match bytearray assignment order */ + return _PyLong_FromByteArray(bytearray, bytes, 1, 0); + } + + static PyObject * random_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { RandomObject *self; *************** *** 464,469 **** --- 505,513 ---- {"jumpahead", (PyCFunction)random_jumpahead, METH_O, PyDoc_STR("jumpahead(int) -> None. Create new state from " "existing state and integer.")}, + {"getrandbits", (PyCFunction)random_getrandbits, METH_VARARGS, + PyDoc_STR("getrandbits(k) -> x. Generates an long int with " + "k random bits.")}, {NULL, NULL} /* sentinel */ }; Index: Doc/lib/librandom.tex =================================================================== RCS file: /cvsroot/python/python/dist/src/Doc/lib/librandom.tex,v retrieving revision 1.34 diff -c -r1.34 librandom.tex *** Doc/lib/librandom.tex 5 Aug 2003 12:23:19 -0000 1.34 --- Doc/lib/librandom.tex 3 Oct 2003 02:38:14 -0000 *************** *** 37,46 **** thread, and using the \method{jumpahead()} method to ensure that the generated sequences seen by each thread don't overlap. ! Class \class{Random} can also be subclassed if you want to use a ! different basic generator of your own devising: in that case, override the \method{random()}, \method{seed()}, \method{getstate()}, \method{setstate()} and \method{jumpahead()} methods. As an example of subclassing, the \module{random} module provides the \class{WichmannHill} class which implements an alternative generator --- 37,49 ---- thread, and using the \method{jumpahead()} method to ensure that the generated sequences seen by each thread don't overlap. ! Class \class{BaseRandom} is available for subclassing if you want to use ! a different basic generator of your own devising: in that case, override the \method{random()}, \method{seed()}, \method{getstate()}, \method{setstate()} and \method{jumpahead()} methods. + Optionally, a new generator can supply a \method{getrandombits()} + method --- this allows \method{randrange()} to produce selections + over an arbitrarily large range. As an example of subclassing, the \module{random} module provides the \class{WichmannHill} class which implements an alternative generator *************** *** 92,97 **** --- 95,110 ---- separated by many steps.]{2.3} \end{funcdesc} + + \begin{funcdesc}{getrandbits}{k} + Returns a python \class{long} int with \var{k} random bits. + + This method is supplied with the MersenneTwister generator. + Other generators may provide it as an optional part of the API. + When available, \method{getrandbits()} enables \method{randrange()} + to handle arbitrarily large ranges. + \versionadded{2.4} + \end{funcdesc} Functions for integers: Index: Lib/test/test_random.py =================================================================== RCS file: /cvsroot/python/python/dist/src/Lib/test/test_random.py,v retrieving revision 1.14 diff -c -r1.14 test_random.py *** Lib/test/test_random.py 6 Sep 2003 04:25:54 -0000 1.14 --- Lib/test/test_random.py 3 Oct 2003 02:38:14 -0000 *************** *** 219,224 **** --- 219,256 ---- 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)