"""RandomNumGen module: contains the RandomNumGen class""" __all__ = ['randHash', 'RandomNumGen'] from direct.directnotify import DirectNotifyGlobal from pandac.PandaModules import Mersenne def randHash(num): """ this returns a random 16-bit integer, given a seed integer. It will always return the same output given the same input. This is useful for repeatably mapping numbers with predictable bit patterns (i.e. doIds or zoneIds) to numbers with random bit patterns """ rng = RandomNumGen(num) return rng.randint(0, (1<<16) - 1) class RandomNumGen: notify = \ DirectNotifyGlobal.directNotify.newCategory("RandomNumGen") def __init__(self, seed): """seed must be an integer or another RandomNumGen""" if isinstance(seed, RandomNumGen): # seed this rng with the other rng rng = seed seed = rng.randint(0, 1L << 16) self.notify.debug("seed: " + str(seed)) seed = int(seed) rng = Mersenne(seed) self.__rng = rng def __rand(self, N): """returns integer in [0..N)""" """ # using modulus biases the numbers a little bit # the bias is worse for larger values of N return self.__rng.getUint31() % N """ # this technique produces an even distribution. # random.py would solve this problem like so: # where: # M=randomly generated number # O=1 greater than the maximum value of M # return int(float(M)*(float(N)/float(O))) # M*(N/O) # # that generally works fine, except that it relies # on floating-point numbers, which are not guaranteed # to produce identical results on different machines. # # for our purposes, we need an entirely-integer approach, # since integer operations *are* guaranteed to produce # identical results on different machines. # # SO, we take the equation M*(N/O) and change the order of # operations to (M*N)/O. # # this requires that we have the ability to hold the result of # M*N. Luckily, Python has support for large integers. One # alternative would be to limit the RNG to a 16-bit range, # in which case we could do this math down in C++; but 16 bits # really doesn't provide a large enough range (0..65535). # Finally, since our O happens to be a power of two (0x80000000), # we can replace the divide with a shift. # boo-ya # the maximum for N ought to be 0x80000000, but Python treats # that as a negative number. assert N >= 0 assert N <= 0x7fffffff # the cast to 'long' prevents python from importing warnings.py, # presumably to warn that the multiplication result is too # large for an int and is implicitly being returned as a long. # import of warnings.py was taking a few seconds return int((self.__rng.getUint31() * long(N)) >> 31) def choice(self, seq): """returns a random element from seq""" return seq[self.__rand(len(seq))] def shuffle(self, x): """randomly shuffles x in-place""" for i in xrange(len(x)-1, 0, -1): # pick an element in x[:i+1] with which to exchange x[i] j = int(self.__rand(i+1)) x[i], x[j] = x[j], x[i] def randrange(self, start, stop=None, step=1): """randrange([start,] stop[, step]) same as choice(range(start, stop[, step])) without construction of a list""" ## this was lifted from Python2.2's random.py # This code is a bit messy to make it fast for the # common case while still doing adequate error checking istart = int(start) if istart != start: raise ValueError, "non-integer arg 1 for randrange()" if stop is None: if istart > 0: return self.__rand(istart) raise ValueError, "empty range for randrange()" istop = int(stop) if istop != stop: raise ValueError, "non-integer stop for randrange()" if step == 1: if istart < istop: return istart + self.__rand(istop - istart) raise ValueError, "empty range for randrange()" 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.__rand(n)) def randint(self, a, b): """returns integer in [a, b]""" assert a <= b range = b-a+1 r = self.__rand(range) return a+r # since floats are involved, I would recommend not trusting # this function for important decision points where remote # synchronicity is critical def random(self): """returns random float in [0.0, 1.0)""" return float(self.__rng.getUint31()) / float(1L << 31)