diff -r bc322469a7a8 Lib/random.py --- a/Lib/random.py Mon Dec 10 20:22:55 2012 -0800 +++ b/Lib/random.py Tue Dec 11 20:54:58 2012 +0200 @@ -43,6 +43,7 @@ from os import urandom as _urandom from collections.abc import Set as _Set, Sequence as _Sequence from hashlib import sha512 as _sha512 +from struct import pack as _pack, unpack as _unpack __all__ = ["Random","seed","random","uniform","randint","choice","sample", "randrange","shuffle","normalvariate","lognormvariate", @@ -59,13 +60,147 @@ RECIP_BPF = 2**-BPF +# Period parameters -- These are all magic. Don't change. +_N = 624 +_M = 397 + +class _Random: + 'Random() -> create a random number generator with its own internal state.' + def __new__(cls, *args, **kwargs): + if cls == _Random and kwargs: + raise TypeError('Random() does not take keyword arguments') + self = super(_Random, cls).__new__(cls) + self.seed(*args) + return self + + def random(self): + """random() -> x in the interval [0, 1).""" + a = self._genrand_int32() >> 5 + b = self._genrand_int32() >> 6 + return (a * 67108864 + b) * (1 / 9007199254740992) + + def seed(self, n=None): + """seed([n]) -> None. Defaults to current time.""" + if n is None: + self._init_genrand(int(time.time())) + return None + if isinstance(n, int): + n = abs(n) + else: + n = hash(n) + if n < 0: + n, = _unpack('N', _pack('n', n)) + # Now split n into 32-bit chunks, from the right. + bits = n.bit_length() + keysize = (bits - 1) // 32 + 1 if bits else 1 + key = _unpack('<%dI' % keysize, n.to_bytes(keysize * 4, 'little')) + self._init_by_array(key) + + def getstate(self): + """getstate() -> tuple containing the current state.""" + return tuple(self.state) + (self.index,) + + def setstate(self, state): + """setstate(state) -> None. Restores generator state.""" + if not isinstance(state, tuple): + raise TypeError('state vector must be a tuple') + if len(state) != _N + 1: + raise ValueError('state vector is the wrong size') + self.state = [x & 0xffffffff for x in state[:-1]] + self.index = int(state[-1]) + + def getrandbits(self, k): + """getrandbits(k) -> x. Generates a long int with k random bits.""" + if k <= 0: + raise ValueError('number of bits must be greater than zero') + + # Fill-out whole words, byte-by-byte to avoid endianness issues + words = [self._genrand_int32() for i in range((k - 1) // 32 + 1)] + k %= 32 + if k: + words[-1] = words[-1] >> (32 - k) + # little endian order to match bytearray assignment order + return int.from_bytes(_pack('<%dI' % len(words), *words), 'little') + + # generates a random number on [0,0xffffffff]-interval + def _genrand_int32(self): + try: + y = self.state[self.index] + self.index += 1 + except IndexError: + mt = self.state + N = len(mt) + M = _M + # generate N words at one time + for kk in range(N): + y = (mt[kk] & 0x80000000) | (mt[kk + 1 - N] & 0x7fffffff) + x = mt[kk + M - N] ^ (y >> 1) + if y & 1: + x ^= 0x9908b0df + mt[kk] = x + y = mt[0] + self.index = 1 + + y ^= (y >> 11) + y ^= (y << 7) & 0x9d2c5680 + y ^= (y << 15) & 0xefc60000 + y ^= (y >> 18) + return y + + # initializes mt[N] with a seed + def _init_genrand(self, s): + x = s & 0xffffffff + mt = [x] + append = mt.append + for i in range(1, _N): + x ^= x >> 30 + x = (1812433253 * x + i) & 0xffffffff + append(x) + # See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. + # In the previous versions, MSBs of the seed affect + # only MSBs of the array mt[]. + # 2002/01/09 modified by Makoto Matsumoto + self.state = mt + self.index = len(mt) + + # initialize by an array with array-length + # init_key is the array for initializing keys + # key_length is its length + def _init_by_array(self, init_key): + self._init_genrand(19650218) + mt = self.state + N = len(self.state) + i = 1 + j = 0 + x = mt[0] + for k in range(max(N, len(init_key))): + x ^= x >> 30 + mt[i] = x = ((mt[i] ^ (x * 1664525)) + init_key[j] + j) & 0xffffffff + i += 1 + if i >= N: + i = 1 + j += 1 + if j >= len(init_key): + j = 0 + for k in range(N - 1): + x ^= x >> 30 + mt[i] = x = ((mt[i] ^ (x * 1566083941)) - i) & 0xffffffff + i += 1 + if i >= N: + i = 1 + mt[0] = 0x80000000 # MSB is 1; assuring non-zero initial array + +try: + from _random import Random as _Random +except ImportError: + pass + + # Translated by Guido van Rossum from C source provided by # Adrian Baddeley. Adapted by Raymond Hettinger for use with # the Mersenne Twister and os.urandom() core generators. -import _random - -class Random(_random.Random): +class Random(_Random): """Random number generator base class used by bound module functions. Used to instantiate instances of Random to get generators that don't diff -r bc322469a7a8 Lib/test/test_random.py --- a/Lib/test/test_random.py Mon Dec 10 20:22:55 2012 -0800 +++ b/Lib/test/test_random.py Tue Dec 11 20:54:58 2012 +0200 @@ -1,13 +1,16 @@ #!/usr/bin/env python3 import unittest -import random import time import pickle +import sys import warnings from math import log, exp, pi, fsum, sin from test import support +py_random = support.import_fresh_module('random', blocked=['_random']) +c_random = support.import_fresh_module('random', fresh=['_random']) + class TestBasicOps(unittest.TestCase): # Superclass with tests common to all generators. # Subclasses must arrange for self.gen to retrieve the Random instance @@ -148,11 +151,20 @@ self.assertEqual(y1, y2) def test_pickling(self): - state = pickle.dumps(self.gen) - origseq = [self.gen.random() for i in range(10)] - newgen = pickle.loads(state) - restoredseq = [newgen.random() for i in range(10)] - self.assertEqual(origseq, restoredseq) + with support.CleanImport('random'): + sys.modules['random'] = self.module + state = pickle.dumps(self.gen) + origseq = [self.gen.random() for i in range(10)] + newgen = pickle.loads(state) + restoredseq = [newgen.random() for i in range(10)] + self.assertEqual(origseq, restoredseq) + + if c_random: + # Test interchangeability + sys.modules['random'] = c_random if self.module != c_random else py_random + newgen = pickle.loads(state) + restoredseq = [newgen.random() for i in range(10)] + self.assertEqual(origseq, restoredseq) def test_bug_1727780(self): # verify that version-2-pickles can be loaded @@ -175,9 +187,8 @@ k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n)) self.assertTrue(0.30 < k/n < .37, (k/n)) + class SystemRandom_TestBasicOps(TestBasicOps): - gen = random.SystemRandom() - def test_autoseed(self): # Doesn't need to do anything except not fail self.gen.seed() @@ -271,10 +282,17 @@ self.assertEqual(k, numbits) # note the stronger assertion self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion +class SystemRandom_TestBasicOpsPython(SystemRandom_TestBasicOps): + module = py_random + gen = module.SystemRandom() + +@unittest.skipUnless(c_random, 'requires _random') +class SystemRandom_TestBasicOpsC(SystemRandom_TestBasicOps): + module = c_random + gen = module.SystemRandom() + class MersenneTwister_TestBasicOps(TestBasicOps): - gen = random.Random() - def test_guaranteed_stable(self): # These sequences are guaranteed to stay the same across versions of python self.gen.seed(3456147, version=1) @@ -446,6 +464,16 @@ self.assertTrue(stop < x <= start) self.assertEqual((x+stop)%step, 0) +class MersenneTwister_TestBasicOpsPython(MersenneTwister_TestBasicOps): + module = py_random + gen = module.Random() + +@unittest.skipUnless(c_random, 'requires _random') +class MersenneTwister_TestBasicOpsC(MersenneTwister_TestBasicOps): + module = c_random + gen = module.Random() + + def gamma(z, sqrt2pi=(2.0*pi)**0.5): # Reflection to right half of complex plane if z < 0.5: @@ -467,7 +495,7 @@ class TestDistributions(unittest.TestCase): def test_zeroinputs(self): # Verify that distributions can handle a series of zero inputs' - g = random.Random() + g = self.module.Random() x = [g.random() for i in range(50)] + [0.0]*5 g.random = x[:].pop; g.uniform(1,10) g.random = x[:].pop; g.paretovariate(1.0) @@ -486,7 +514,7 @@ def test_avg_std(self): # Use integration to test distribution average and standard deviation. # Only works for distributions which do not consume variates in pairs - g = random.Random() + g = self.module.Random() N = 5000 x = [i/float(N) for i in range(1,N)] for variate, args, mu, sigmasqrd in [ @@ -512,36 +540,53 @@ self.assertAlmostEqual(s1/N, mu, places=2) self.assertAlmostEqual(s2/(N-1), sigmasqrd, places=2) +class TestDistributionsPython(TestDistributions): + module = py_random + +@unittest.skipUnless(c_random, 'requires _random') +class TestDistributionsC(TestDistributions): + module = c_random + + class TestModule(unittest.TestCase): def testMagicConstants(self): - self.assertAlmostEqual(random.NV_MAGICCONST, 1.71552776992141) - self.assertAlmostEqual(random.TWOPI, 6.28318530718) - self.assertAlmostEqual(random.LOG4, 1.38629436111989) - self.assertAlmostEqual(random.SG_MAGICCONST, 2.50407739677627) + self.assertAlmostEqual(self.module.NV_MAGICCONST, 1.71552776992141) + self.assertAlmostEqual(self.module.TWOPI, 6.28318530718) + self.assertAlmostEqual(self.module.LOG4, 1.38629436111989) + self.assertAlmostEqual(self.module.SG_MAGICCONST, 2.50407739677627) def test__all__(self): # tests validity but not completeness of the __all__ list - self.assertTrue(set(random.__all__) <= set(dir(random))) + self.assertTrue(set(self.module.__all__) <= set(dir(self.module))) def test_random_subclass_with_kwargs(self): # SF bug #1486663 -- this used to erroneously raise a TypeError - class Subclass(random.Random): + module = self.module + class Subclass(module.Random): def __init__(self, newarg=None): - random.Random.__init__(self) + module.Random.__init__(self) Subclass(newarg=1) +class TestModulePython(TestModule): + module = py_random + +@unittest.skipUnless(c_random, 'requires _random') +class TestModuleC(TestModule): + module = c_random + def test_main(verbose=None): - testclasses = [MersenneTwister_TestBasicOps, - TestDistributions, - TestModule] + testclasses = [ + MersenneTwister_TestBasicOpsPython, MersenneTwister_TestBasicOpsC, + TestDistributionsPython, TestDistributionsC, + TestModulePython, TestModuleC] try: - random.SystemRandom().random() + py_random.SystemRandom().random() except NotImplementedError: pass else: - testclasses.append(SystemRandom_TestBasicOps) + testclasses.extend([SystemRandom_TestBasicOpsPython, SystemRandom_TestBasicOpsC]) support.run_unittest(*testclasses)