academy

Generating Random Numbers in Ruby

Joyce Echessa

Joyce Echessa on

Generating Random Numbers in Ruby

Random numbers are useful for a variety of purposes such as in gaming, encryption and building simulations. Technically, computers cannot generate random numbers purely by computation. It is fundamentally impossible to produce truly random numbers on any deterministic device. The best you can hope for is pseudorandom numbers, a stream of numbers that appear as if they were generated randomly.

In this article, we'll look at the various ways you can generate random numbers in Ruby.

Generating Random Numbers with Kernel#rand

To start off, let's generate random numbers with the rand method. When the method is called with no arguments, it returns a float that is greater than or equal to 0.0 and less than 1.0.

1rand()
2> 0.7308136972953823

To get an integer, you pass an integer to the function. The function will return a random integer value that is greater than or equal to 0 and less than the integer passed to the function. Each time the following is run, you will get a number that is between 0 and 7.

1rand(8)
2> 5

For a random number within a particular range, pass the Range to rand.

The following uses an inclusive Range to generate random numbers from a lower limit (1), up to (and including) the upper limit (10).

1rand(1..10)
2> 6

The next example uses a non-inclusive Range to generate random numbers from a lower limit, up to (but not including) the upper limit.

1rand(1...10)
2> 9

The range can also be between floating point values.

1rand(1.5..3.0)
2> 1.7494305393711571

You can also use negative range limits with rand.

1rand(-5..-1)
2> -5

Passing in single negative numbers may give surprising results, as shown below.

1rand(-100)
2> 94
3
4rand(-0.5)
5> 0.7692627344737486

This is because for an argument n passed into rand, rand returns random numbers from 0 up to (but not including) n.to_i.abs. For the above example (-100).to_i.abs is 100 and (-0.5).to_i.abs is 0, thus the resulting random numbers.

Calling rand(0) is similar to calling rand(). You will get random numbers between 0.0 and 1.0 (not inclusive).

Generating Reproducible Sequences with Kernel#srand

Before moving on to the next method of generating random numbers, let's first look at the srand function.

Kernel#srand sets the seed for Kernel#rand. We can use it to generate repeatable sequences of random numbers between different runs of the program.

To understand what this means, we first need to understand how random numbers are generated.

Generating "Random" Numbers from a Seed

As stated earlier, computers don't generate truly random numbers purely from computation. What they do is generate a sequence of numbers that seem random. To do this, the computer starts with a seed number, which it runs through some algorithm and then spits out a seemingly random output.

The seed number is generated by the computer using a combination of different elements, e.g. timestamp, the process ID of the program, e.t.c. Because these elements vary for each request to generate a random number, the seed number will always be different, which would produce a different sequence of numbers, thus the resulting random number output. If you ran the algorithm with the same seed, then you would get the same sequence of numbers each time. This is what Kernel#srand allows us to do.

srand is usually used in testing. It could be handy for testing code in your app that deals with randomness, with values that are random but still predictable enough to test. It could also help in isolating or reproducing bugs.

Below we use srand to set the seed and then call rand first to produce a couple of individual random numbers and then to produce a couple sequences of random numbers.

1srand(777)
2
3rand()
4> 0.152663734901322
5
6rand()
7> 0.3023566097075212
8
910.times.map { rand(10) }
10> [7, 1, 7, 4, 7, 9, 8, 7, 2, 0]
11
1210.times.map { rand(10) }
13> [1, 2, 4, 5, 7, 1, 7, 2, 2, 7]

If you run srand again with the same seed and make the same calls we made previously, you will see that we get the same random numbers.

1srand(777)
2
3rand()
4> 0.152663734901322
5
6rand()
7> 0.3023566097075212
8
910.times.map { rand(10) }
10> [7, 1, 7, 4, 7, 9, 8, 7, 2, 0]
11
1210.times.map { rand(10) }
13> [1, 2, 4, 5, 7, 1, 7, 2, 2, 7]

Generating Random Numbers with Random

You can also generate random numbers with the Random class.

The class method rand provides the base functionality of Kernel#rand along with better handling of floating point values.

1Random.rand(1...10)
2> 5

Unlike Kernel#rand, if Random.rand is given a negative or 0 argument, it raises an ArgumentError.

Generating Random Numbers Based on Normal Distribution

In the real world, many things tend to follow a Normal Distribution. If you have a range of values that something falls under, rarely do you get an equal distribution of all the values. Mostly, a majority of the data tends to fall within a smaller range, with a smaller percentage falling within the larger range. Let's take an adult man's height as an example. The shortest height recorded is 54.6 cm (21.5 inches) while the tallest is 267 cm (8'9"). If you want to generate data to simulate the height of men in a population, you might not want to use rand with these limits. You don't want the probability of getting an 8'9" man to be the same as getting a 6' man, because the latter is more common.

Other examples of things that follow a Normal Distribution are:

  • Errors in measurements
  • Blood pressure
  • Test scores
  • Weight of an adult man/woman

To generate better random numbers for such use cases, you can use the rubystats gem.

1$ gem install rubystats
1require 'rubystats'
2
3adult_male_height = Rubystats::NormalDistribution.new(178, 10)
4sample = 50.times.map { adult_male_height.rng.round(1) }
5
6> [183.2, 169.5, 189.7, 171.9, 176.0, 179.3, 189.3, 175.3, 188.3, 190.0, 185.5, 182.8, 187.2, 191.6, 185.4, 178.4, 187.1, 183.3, 189.6, 179.7, 172.7, 174.4, 153.8, 197.4, 176.0, 174.6, 181.1, 182.0, 204.7, 185.2, 175.9, 167.7, 160.6, 170.5, 169.3, 160.6, 165.6, 166.4, 182.6, 179.7, 183.1, 171.9, 185.4, 175.4, 179.7, 176.9, 160.6, 173.8, 181.9, 190.2]

In the above, we pass the average height for men (178cm) and a standard deviation of 10cm to NormalDistribution.new, before generating 50 values that fall in this normal distribution. If you are curious about the math, this article may interest you.

Random Roundup

That brings us to the end of this discussion. We covered a few different ways of creating 'random' numbers in Ruby, with rand, srand, Random and Rubystats. We also briefly touched on how 'random' numbers are created and looked at the reason why deterministic devices cannot create real random numbers.

You should note that the methods covered are not ideal for all use cases that call for randomness. The integer or floating point numbers generated by the methods might be ideal for producing chance in gaming or for creating simulations, but in situations that call for some security, for instance when generating a password reset token, you should consider using SecureRandom. With SecureRandom, you can generate hexadecimal, base64, binary and UUID strings that are much harder to crack as compared to plain numbers.

We hope you found some of this interesting. If you have any comments or questions on what we covered, please reach out to us @AppSignal. You can also send us your requests for topics you want covered.

We've updated this article on August 1st, 2018 to include a note on SecureRandom

Share this article

RSS

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps