Random Number Generation in Ruby

How to Generate Random Numbers in Ruby

This post covers the basics of random number generation in Ruby. As with everything in Ruby, there're multiple ways you could do it, with some approaches better than the other. We'll also explore generating random numbers in various formats as well as generating secure random numbers.

5 min read

Ruby has a Random class that can generate pseudo-random numbers. The reason for pseudo is that it produces a deterministic sequence of bits which approximate true randomness. You can generate integers, floats, and even binary strings.

I've always found myself referring to the docs to understand various ways Ruby lets you generate random numbers and thought it'd be nice to write a cookbook-style post listing the important recipes. So here it goes.

Random Number Generation in Ruby

The simplest way to generate random numbers in Ruby is to use the Kernel#rand method.

rand           # 0.47578359467975184
rand 10        # 2
rand 4.5       # 3
rand 1.2..3.5  # 3.3037237197517415
rand 5...8     # 6

What do all these arguments mean? Let's study the Random class for a deeper investigation, since the Random::rand class method works similarly to the Kernel#rand method, returning a random number based on the argument.

There are three different versions of this method.

  • Calling Random.rand without providing any arguments will return a random float number between 0 and 1 (not including 1).
Random.rand # 0.8722060064922107
  • Calling Random.rand(max) by providing the max value will generate a random number between 0 and max (but not including max).
Random.rand(10)  # 7
Random.rand(10)  # 3
Random.rand(14)  # 2
Random.rand(1.5) # 0.008421002905374453
  • Calling Random.rand(Range) with a Range will generate a random number within that range. If you use .., the range will include the end value, and if you use ..., it will exclude the end value.
# can generate 2, 3, or 4
Random.rand(2..4)  # 3

# can generate 2 or 3
Random.rand(2...4)  # 2

# A float range
Random.rand(2.1...4.4)  # 2.35435574571588

The same method is also available on an instance of Random, so you could do Random.new.rand(5) and so on.

Should You Use Random or Random.new?

To be honest, I don't know. While researching for this post, I came across this excellent answer on Stack Overflow from Marc-André Lafortune:

In most cases, the simplest is to use rand or Random.rand.

Creating a new random generator each time you want a random number is a really bad idea. If you do this, you will get the random properties of the initial seeding algorithm which are atrocious compared to the properties of the random generator itself.

- Stack Overflow

He goes on to suggest:

If you use Random.new, you should thus call it as rarely as possible, for example once as MyApp::Random = Random.new and use it everywhere else.

Sound advice.

The next day, right before I was going to publish this post, I stumbled across this post on Stack Overflow, where Schwern suggests the exact opposite approach:

Random.new.rand is better than rand because each separate instance of Random will be using a different seed (and thus a different psuedo-random pattern), whereas every call to rand is using the same seed.

If your code is using rand to generate random numbers, the attacker can assume that any random number is using the same seed and the same sequence. Then they can more quickly gather observations to guess at the seed.

If each part of your code is using its own Random.new, now the attacker has to figure out which random number goes with which seed. This makes it harder to build a sequence of random numbers, and gives the attacker less numbers to work with.

- Stack Overflow

Which also sounds like a good advice. From the profiles of both Marc and Schwern, it seems like they both know what they're talking about. So I'm confused. Is there a consensus or best practice to choose one or the other?

To get some clarity, I posted a question on the Ruby Subreddit, but the answers were... unsatisfactory?

If you're reading this post and know the definitive best practice, please let me know in the comments or by emailing me, and I'll update the post.

Anyway, let's continue!

How to Generate Secure Random Numbers?

Often you may need to generate secure random numbers which are useful for generating session keys in HTTP cookies.

While the Random class is suitable for many scenarios, it is not recommended for situations where cryptographic security is a concern, as the predictability of pseudo-random number generators makes them vulnerable to certain types of attacks.

To generate secure random numbers, use the `securerandom` gem (make sure to install the gem first with gem install securerandom).

require "securerandom"

SecureRandom.alphanumeric    # "tpEnoWgScSJRU3YB"

SecureRandom.base64          # "0jHnJ7Yx5oTW0OY+YKgUog=="

SecureRandom.hex             # "b51372ee8b93eb3e1f0035d9300c3e97"

SecureRandom.rand            # 0.6053942880507039

SecureRandom.urlsafe_base64  # "OTHNscnomrNjjT0g_dzpdw"

SecureRandom.uuid            # "f6f54bd8-fc5a-483f-8909-05428dea2290"

The numbers generated by SecureRandom are not easily predictable, even if an attacker knows previous values generated.

Check out the documentation to learn more.

How to Generate a Random Binary String in Ruby?

The bytes(size) class method returns a random binary string. To control the length of the generated string, use the size argument.

Random.bytes(5)  # "\xFFT\f\xE0\x0F"

Random.bytes(10) # ":\x84q>\xD1\x15G\xBA\xAA\xF4"

If you don't provide the size, Ruby will throw an error.

bytes is also available as an instance method.

Random.new.bytes(8)  # "\xF0j\xFBa\xCC\x1C\xCF\x12"

How to Get the seed value used to generate an instance of random generator?

Use the seed class method to get the seed value used to initialize the random generator. This is useful if you want to initialize another random number generator with the same state as the first, at a later time.

Random.seed      #=> 1234
prng1 = Random.new(Random.seed)
prng1.seed       #=> 1234

prng1.rand(100)  #=> 47
Random.rand(100) #=> 47

The second generator will provide the same sequence of numbers.

How to Generate Random Numbers in Pre-Defined Formats?

The Random::Formatter module formates generated random numbers in many manners. To use it, simply require it. It makes several methods available as Random's instance as well as module methods.

require "random/formatter"

Random.hex          # "1aed0c631e41be7f77365415541052ee"
Random.base64(4)    # "bsQ3fQ=="
Random.alphanumeric # "TmP9OsJHJLtaZYhP"
Random.uuid         # "f14e0271-de96-45cc-8911-8910292a42cd"

For a complete reference, check out the documentation of the Random::Formatter.

How to Initialize Random Class with Seed Values?

To initialize the Random class, you can provide a seed value. You can use the Kernel#srand method to generate this seed, or can manually supply one.

srand may be used to ensure repeatable sequences of pseudo-random numbers between different runs of the program. This is helpful in testing. You can make your tests deterministic by setting the seed to a known value.

srand 1234       # seed with 1234
[ rand, rand ]   # => [0.1915194503788923, 0.6221087710398319]

srand 1234       # seed with 1234
[ rand, rand ]   # => [0.1915194503788923, 0.6221087710398319]

srand 5678       # seed with 5678
[ rand, rand ]   # => [0.4893269800108977, 0.05933244265166393]

An important thing to keep in mind is that the random number generator becomes deterministic if you use the same seed value. That is, different instances will return the same values.

r1 = Random.new(1234)

r1.rand  # 0.1915194503788923
r1.rand  # 0.6221087710398319

r2 = Random.new(1234)
r2.rand  # 0.1915194503788923
r2.rand  # 0.6221087710398319

When you don't provide a seed value, Ruby uses an arbitrary seed value generated by the new_seed class method.

Random.new_seed   # 281953773930427386731290692710425966835
Random.new_seed   # 105614143647114073473001171940625552466

Since Ruby takes care of using a new seed value each time, I suggest you initialize the Random instances without providing an explicit seed value.

That's a wrap. I hope you found this article helpful and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.