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 andmax
(but not includingmax
).
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 userand
orRandom.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 useRandom.new
, you should thus call it as rarely as possible, for example once asMyApp::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 thanrand
because each separate instance ofRandom
will be using a different seed (and thus a different psuedo-random pattern), whereas every call torand
is using the same seed.
If your code is usingrand
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 ownRandom.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?
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.