How computers can turn randomness into meaningful numbers
Tags: computerscience, python
Published: 2019-10-31 09:35:05

---
title: How computers can turn randomness into meaningful numbers
published: true
description: What's involved in generating a random number between 0 and 1? And how does that become a number between 50 and 200? Today I decided to implement from scratch and write up what I found.
tags: computer-science, python
cover_image: https://thepracticaldev.s3.amazonaws.com/i/o7ghu5lr2i9izpxfzln8.jpg
---

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()`.

```python
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!

```python
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](https://docs.python.org/3/library/struct.html#format-characters). 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&mdash;or even if you have and you haven't used the `struct` module before&mdash;code like this can seem kind of crazy. Hopefully not too crazy. If you have any questions, please ask!

## Image Credit

[Dylan Nolte / Unsplash](https://unsplash.com/photos/RSsqjpezn6o)