Diogo Kollross

Posted on

# Random integers

Elixir doesn't have a module to generate random integers, but it can call underlying Erlang modules such as rand (nowadays, preferred over random) or crypto and can select random items from enumerables. This gives us some options to generate integers inside a range.

To generate integers between 0 and 255 (inclusive):

``````n = :rand.uniform(256) - 1
n = :crypto.rand_uniform(0, 256)
n = Enum.random(0..255)
``````

Note that `:rand.uniform(n)` returns integers so that `1 <= x <= n` - that's why we decrement the result. Also, the documentation for Enum.random states that it will efficiently "pick a random value between the range limits, without traversing the whole range".

You can also use `Enum.random` with ranges or charlists to pick random code points from specific alphabets.

``````n = Enum.random(?a..?z)
n = Enum.random('0123456789abcdef')
``````

# Random strings

The next step is to use these building blocks to create a binary from a number of random code points using comprehensions:

``````s = for _ <- 1..10, into: "", do: <<Enum.random('0123456789abcdef')>>
``````

# Cryptographically secure strings

Sometimes there's a need for a cryptographically secure generator to create, for example, random passwords or authentication tokens.

If you simply need a sequence of random bytes, you can use strong_random_bytes from Erlang's `crypto` module. It's also much faster than looping in Elixir using comprehensions.

``````s = :crypto.strong_rand_bytes(10)
``````

If you need to use a specific alphabet, you can combine `:crypto.rand_uniform` with `Enum.at`:

``````symbols = '0123456789abcdef'
symbol_count = Enum.count(symbols)
s = for _ <- 1..10, into: "", do: <<Enum.at(symbols, :crypto.rand_uniform(0, symbol_count))>>
``````

# Benchmark

I've benchmarked these options using Benchee.

``````random_length = 10_000
Benchee.run(%{
"Enum.random comprehension" => fn ->
for _ <- 1..random_length, into: "", do: <<Enum.random(0..255)>>
end,
":rand.uniform comprehension" => fn ->
for _ <- 1..random_length, into: "", do: <<:rand.uniform(256) - 1>>
end,
":crypto.rand_uniform comprehension" => fn ->
for _ <- 1..random_length, into: "", do: <<:crypto.rand_uniform(0, 256)>>
end,
":crypto.strong_rand_bytes" => fn ->
:crypto.strong_rand_bytes(random_length)
end,
})
``````

Which yielded these results:

``````Name                                         ips        average  deviation         median         99th %
:crypto.strong_rand_bytes               73695.16      0.0136 ms    ±34.33%      0.0130 ms      0.0239 ms
:rand.uniform comprehension               488.88        2.05 ms    ±10.39%        1.99 ms        2.62 ms
Enum.random comprehension                 284.56        3.51 ms     ±8.22%        3.53 ms        4.33 ms
:crypto.rand_uniform comprehension         44.04       22.71 ms     ±2.16%       22.64 ms       24.54 ms

Comparison:
:crypto.strong_rand_bytes               73695.16
:rand.uniform comprehension               488.88 - 150.74x slower +2.03 ms
Enum.random comprehension                 284.56 - 258.98x slower +3.50 ms
:crypto.rand_uniform comprehension         44.04 - 1673.55x slower +22.70 ms
``````

# Summary

Based on the benchmarks and thinking about readability, this is what I would use depending on what I need to generate:

• Binary strings: `:crypto.strong_rand_bytes`
• Any other symbol alphabet: `Enum.random`
• Passwords/tokens: `:crypto.rand_uniform`
• Fast: `:rand.uniform`