## DEV Community is a community of 867,901 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Tim McNamara

Posted on

# How computers can turn randomness into meaningful numbers

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:
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!

## Image Credit

Dylan Nolte / Unsplash