One of the things that really intrigued me when I started to program was that I could ask the computer: "Please generate a random number between 50 and 200".

The thing that bugged me was that random number generators seemed to produce numbers between 0 and 1. What's involved in generating that value between 0 and 1? And how does that become a number between 50 and 200?

Today I decided to implement from scratch in case anyone else has a simple mind like me.

## The problem

Let's say that we're presented with a random number generator that only produces random bytes.. but what we really want is a random number between `[n,m]`

.

## The solution

- Treat your incoming random bytes as an unsigned integer, then divide by the max that integer could generate. We now have a proportion between 0 and "max integer".
- Multiply the difference between the two numbers in the range by the proportion
- Add start to the result of the previous operation

## The easy implementation

First, let's assume that can get the proportion for free by calling `random.random()`

.

```
import random
def rand_range(start, end):
diff = end - start
proportion = random.random()
return start + (proportion * diff)
```

## The hard implementation

When we need to generate the proportion ourselves, there is more work to do. An explanation follows below!

```
def random_bytes(n_bytes, source='/dev/urandom'):
with open(source, 'rb') as f:
entropy = f.read(n_bytes)
return entropy
def rand_from_bytes(n_bytes):
assert n_bytes in {1, 2, 4, 8}
entropy = random_bytes(n_bytes)
typecode = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[n_bytes]
r, = struct.unpack(typecode, entropy)
return r / (1 << 8*n_bytes)
def rand_range(start, end):
diff = end - start
proportion = rand_from_bytes(4)
return start + (proportion * diff)
```

In `random_bytes()`

we read a fixed number of random bytes. By "random bytes", I mean an unpredictable sequence of 0s and 1s. On UNIXy operating systems, we can read from the pseudo-file `/dev/urandom`

to get a sequence. (The operating kernel makes sure that the sequence really is unpredictable.) Those bytes are passed through to `rand_from_bytes()`

.

`rand_from_bytes()`

uses lots of quirky syntax and some trickery. It uses Python's `struct`

module to interpret the bytes as unsigned integers using Python's rules for doing so. That module requires a coded string to describe how the bytes provided in the second argument should be interpreted. The code `{1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[n_bytes]`

creates a Python dictionary that is immediately accessed to generate that coded string.

`r / (1 << 8*n_bytes)`

might look like black magic, but all it's doing is generating a proportion for us. `(1 << 8*n_bytes)`

is the maximum integer value for whatever type that we care about. We use that as a denominator. `r`

lies somewhere between 0 and `(1 << 8*n_bytes)`

.

Python will return a floating point number because of the division.

And now we're back where we started with when calling `random.random()`

. Yay. And then we repeat the easy implementation.

## Questions?

If you've not read much Python before—or even if you have and you haven't used the `struct`

module before—code like this can seem kind of crazy. Hopefully not too crazy. If you have any questions, please ask!

## Top comments (0)