"""fourmirandom.py --- A random number generator that returns truly random numbers. Danny Yoo (dyoo@hkn.eecs.berkeley.edu), with help from the kind folks at Python-tutor: http://mail.python.org/mailman/listinfo/tutor See the thread started here: http://mail.python.org/pipermail/tutor/2002-July/015581.html for more details on how this got started. This module should have the same interface as the 'random' module in the standard library: http://www.python.org/doc/lib/module-random.html Just replace the word "pseudo" with "real". *grin* Our source of random bits comes from: http://www.fourmilab.ch/cgi-bin/uncgi/Hotbits We shouldn't abuse this module too much, as Fourmilab does have a quota on the number of bits one is allows to pull from it.""" ## We import these, but put underscores in front of the names, just to ## prevent name confusion between the module '_random' and the ## function 'random'. import random as _random import urllib as _urllib import math as _math MAX_BITS_GIVEN_BY_FOURMI = 2048 * 8 def random_bits(n=MAX_BITS_GIVEN_BY_FOURMI): """Returns a list of n random bits.""" assert n <= MAX_BITS_GIVEN_BY_FOURMI, "Maximum of 2048 bytes exceeded" urlstr = "http://www.fourmilab.ch/cgi-bin/uncgi/Hotbits?"+\ "nbytes=%d&fmt=bin" % _math.ceil(n / 8.0) bytes = _urllib.urlopen(urlstr).read() bits = [] for b in bytes: bits.extend(byte_to_binary(ord(b))) return bits[:n] def _pseudorandom_bits(n): """Similar to random_bits, but for offline testing. We don't want to hammer Fourmilab while testing this module.""" bits = [] for i in range(n): bits.append(_random.randrange(2)) return bits def clump_by_n(sequence, n): """Given a sequence, returns a list of grouped chunks of that sequence, each of length n (except, possibly, the last chunk).""" collection = [] for i in range(0, len(sequence), n): collection.append(sequence[i : i+n]) return collection def byte_to_binary(byte): """Converts a byte into a binary list of bits.""" assert 0 <= byte < 256, "byte not within range 0 <= byte < 256" bits = [] for i in range(8): bits.append(byte % 2) byte = int(byte / 2) bits.reverse() return bits def binary_list_to_long(bits): """Converts a list of bits to a long.""" value = 0L for b in bits: value = (value * 2) + b return value ###################################################################### class FourmiRandom(_random.Random): """A subclass of _random.Random that supplies truly random numbers.""" BITS_USED = 53 SOURCE_CAPACITY = int(MAX_BITS_GIVEN_BY_FOURMI / BITS_USED) def __init__(self): self._source = [] def random(self): if not self._source: self._refill_source() return self._source.pop() def _refill_source(self): ## bits = _pseudorandom_bits(self.SOURCE_CAPACITY * self.BITS_USED) bits = random_bits(self.SOURCE_CAPACITY * self.BITS_USED) clumps = clump_by_n(bits, self.BITS_USED) for c in clumps: self._source.append(_math.ldexp(binary_list_to_long(c), -self.BITS_USED)) ## The rest of these functions won't be useful, since our source ## is truly random and can't be seeded. def seed(self, a=None): pass def getstate(self): return None def setstate(self, state): pass def jumpahead(self, n): pass ###################################################################### ## A little trick to make fourmirandom look much like the random module. _inst = FourmiRandom() seed = _inst.seed random = _inst.random uniform = _inst.uniform randint = _inst.randint choice = _inst.choice randrange = _inst.randrange shuffle = _inst.shuffle normalvariate = _inst.normalvariate lognormvariate = _inst.lognormvariate cunifvariate = _inst.cunifvariate expovariate = _inst.expovariate vonmisesvariate = _inst.vonmisesvariate gammavariate = _inst.gammavariate stdgamma = _inst.stdgamma gauss = _inst.gauss betavariate = _inst.betavariate paretovariate = _inst.paretovariate weibullvariate = _inst.weibullvariate getstate = _inst.getstate setstate = _inst.setstate jumpahead = _inst.jumpahead whseed = _inst.whseed