I am trying to implement my own version of the Random class from the python standard library. I can generate random bits and I implemented the getrandbits(n) function. But the superclass does not use this function to calculate the returned float from random(). So I have to implement that by myself:
def random(self):
exp = 0x3FF0000000000000
mant = self.getrandbits(52)
return struct.unpack("d", struct.pack("q", exp^mant))[0]-1.0
I am using a sign of 0 (positive), an exponent of 1023 (2^0 = 1) and a random mantisse. So I get a number from [1.0, 2.0). The random() function must return a number in [0.0, 1.0) so I subtract 1.0 before returning.
As I’m not an expert when it comes to floating point numbers I’m not sure this is done the right way. Don’t I lose precision by subtracting? Can I build the number from random bits so that it’s in [0.0, 1.0) without subtraction?
Your implementation is fine: assuming that
getrandbitsitself is random enough, your implementation will produce each number of the formn / 2^52for0 <= n < 2^52with equal probability, so it’s a good approximation to the uniform distribution on[0, 1). The subtraction you use is not a problem: the result from that subtraction is always exactly representable, so there’s no rounding or precision loss involved in the subtraction.Python’s
random()implementation does something more along the lines ofreturn self.getrandbits(53) / 2**53.The effects are similar, except that the distribution of outputs is now twice as fine: you get each number of the formn / 2^53for0 <= n < 2^53with equal probability. It’s unlikely that most applications would notice the difference between these two implementations in practice. If you care about speed, it’s likely that this is faster, too, though as always you should profile to find out whether that’s actually the case.Neither of these is perfect: there are around
2^62distinct IEEE 754 binary64 floats in the range[0.0, 1.0), and your implementation can only generate2^52distinct outputs, so most of those floats can never be generated by either of the above implementations. A betterrandom()implementation could generate each floating-point numberxin the range[0.0, 1.0]with a probability equal to the length of the subinterval of[0.0, 1.0)that rounds toxunder some form of round-to-nearest. However, such an implementation would be significantly more complicated (though not particularly hard to implement), and few applications would benefit from the larger output set. As the Zen of Python says: “Practicality beats purity.”EDIT: To illustrate the last paragraph above, here’s some code. The
uniformfunction usesgetrandbitsto generate uniformly distributed floats on[0, 1]according to the above description.