"""truly_random.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 Special thanks to Gregor Lingl for the Fourmilab code, Tim Peters for math.ldexp(), and Sean Perry for /dev/random. To see how this module was born, we can browse through the thread here: http://mail.python.org/pipermail/tutor/2002-July/015581.html truly_random 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* This module can use different sources for random bits. I've included three of them: 1. Fourmilab: http://www.fourmilab.ch/cgi-bin/uncgi/Hotbits This was the motivating example for writing this module. By the way, we probably shouldn't use Fourmilab too much, since Fourmilab does have a quota on the number of bits one is allows to pull from it. 2. /dev/random Another is the '/dev/random' device found on Unix systems. This is the default source we use for random bits. 3. pseudorandom: Finally, we have a test BitSource that uses pseudorandom numbers. This isn't truly random. To switch bit sources, we provide the following functions: set_default_as_dev_random() set_default_as_fourmi() set_default_as_pseudorandom() """ ## For these following import statements, we put underscores in front ## of the names to prevent name confusion between the module '_random' ## and the function 'random'. import random as _random import urllib as _urllib import math as _math import string as _string __VERSION__ = "1.0" class RandomBitSource: """An abstract class for any "source" of random bits.""" ## All subclasses of RandomBitSource should implement the __call__ ## method. def __call__(self, n): """Returns a list of n random bits.""" pass ###################################################################### ## The following are implementations of a RandomBitSource. class PseudoRandomBitSource(RandomBitSource): """A sample RandomBitSource for offline testing. As the name implies, this uses the standard 'Random' module to create random bits.""" def __call__(self, n): bits = [] for i in range(n): bits.append(_random.randrange(2)) return bits class FourmiBitSource(RandomBitSource): """A BitSource that uses the Hotbits web site service provide by Fourmilab. (http://www.fourmilab.ch/hotbits/).""" """Fourmilab has a limit to how many bits we can pull from it from a single request.""" _MAX_BITS_GIVEN_BY_FOURMI = 2048 * 8 def __init__(self): self._source = [] def __call__(self, n): """Returns a list of n random bits.""" while len(self._source) < n: self._source.extend(self._refill()) bits_to_return = self._source[:n] self._source = self._source[n:] return bits_to_return def _refill(self): """Requests MAX_BITS_GIVEN_BY_FOURMI bits from FourmiLab.""" n = self._MAX_BITS_GIVEN_BY_FOURMI 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] class DevRandomBitSource(RandomBitSource): _DEV_RANDOM_DEVICE = '/dev/random' def __init__(self, file=_DEV_RANDOM_DEVICE): self._devrandom = open(file) def __call__(self, n): """Returns a list of n random bits.""" self.bits = [] while len(self.bits) < n: byte = self._devrandom.read(1) self.bits.extend(byte_to_binary(ord(byte))) return self.bits[:n] ###################################################################### class TrulyRandom(_random.Random): """A subclass of _random.Random that supplies truly random numbers, using a RandomBitSource as a supply of random bits.""" """Tim Peters suggests using 53 bits for each random number we generate, since the floating point mantissa should be able to represent it.""" _BITS_USED = 53 def __init__(self, source): self._source = source def random(self): """Returns a random float within the half-open interval [0, 1).""" bits = self._source(self._BITS_USED) return _math.ldexp(binary_list_to_long(bits), -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 ###################################################################### ## Here are some utility functions I use to deal with lists of bits. def byte_to_binary(byte): """Converts a byte into a binary list of bits.""" assert 0 <= byte < 256, "byte not within 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 ###################################################################### ## Finally, for the remainder of the code, we want to emulate the ## interface of the Standard Library's random module. """The following functions are defined in the random module:""" #cunifvariate #stdgamma whseed _module_functions = _string.split(""" seed random uniform randint choice randrange shuffle normalvariate lognormvariate expovariate vonmisesvariate gammavariate gauss betavariate paretovariate weibullvariate getstate setstate jumpahead""") _inst = None def set_default_randomizer(randomizer): """This modifies the module so that the randomizer's methods, listed in _module_functions, become accessible as if they were functions.""" global _inst _inst = randomizer module = globals() for function_name in _module_functions: module[function_name] = getattr(randomizer, function_name) def set_default_as_dev_random(): """Sets the default random bit source as /dev/random.""" set_default_randomizer(TrulyRandom(DevRandomBitSource())) def set_default_as_fourmi(): """Sets the default random bit source as Fourmilab.""" set_default_randomizer(TrulyRandom(FourmiBitSource())) def set_default_as_pseudorandom(): """Sets the default random bit source as the standard pseudorandom generator.""" set_default_randomizer(TrulyRandom(PseudoRandomBitSource())) ## Finally, we set our module's default to use dev_random as the ## BitSource. set_default_as_dev_random()