Overview
Why Ruby can generate random numbers without seeded random numbers was investigated by a light reading of the source code of the Ruby language itself.
If the class that generates the random number does not put the seed of the random number in the argument, it calls the method that generates the seed of the random number, and in the logic that generates the random number, the seed of the random number is generated by calling the CPU clock number or the random number generation of the OS.
Since the seed of the random number does not depend on UNIX time alone, it seems that the seed value is the same even if the random number is generated at the same time in a high-access service or parallelized batch processing.
First things first
If you want to generate a random number in Ruby, you should write it like this.
r=Random.new
r.rand(10)
=> 5
# Outputs a random number (Integer) from 0 to 9 when an integer is inserted into the argument
r.rand(5.5)
=> 1.81617029321715
# Outputs a random result with Float type if a few arguments are input
rand method specification
Method: Random.rand — Documentation for core (2.7.0)
Random number generation in Ruby uses Mersenne Twister as a pseudo-random number sequence generator. The pseudo-random number generator needs an initial value called the seed of the random number. (There may be a pseudo-random number generator that doesn't need a seed, but that's not the subject of this article.)
I have the impression that many programming languages that use compilers prepare seeds of random numbers when generating random numbers, and many interpreters, like Ruby, can generate random numbers without using seeds of random numbers.
Random number generation logic in other languages than Ruby
An example of the need to seed a random number is the Go language
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().Unix())
number:= rand.Int()
fmt.Println(number)
}
JavaScript as an example of not needing to seed a random number
const number = Math.random()
console.log(number)
If you look at the definition of Math.random() in this JS, you'll see that you can't seed a random number in JavaScript.
The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.
For languages that don't need to seed random numbers, let's use Ruby as an example to find out how to seed random numbers
If you look at the specs of the Random constructor
Method: Random#initialize — Documentation for core (2.7.0)
Random.new_seed generates a random seed value and instantiates it.
Running Random.new_seed returns the following results
Random.new_seed
=> 115032730400174366788466674494640623225
*You're generating random numbers to seed random numbers? * So how do you generate the seed that generates the random number? They probably generate random numbers in a way that doesn't make use of pseudo-random number generators.
Since this is the target Ruby code and the main body of Ruby, it is the code of C. The code of Random.new_seed is made as follows. I'll read this one line at a time.
Method: Random.new_seed — Documentation for core (2.7.0)
static VALUE
random_seed(VALUE _)
{
VALUE v;
uint32_t buf[DEFAULT_SEED_CNT+1];
fill_random_seed(buf, DEFAULT_SEED_CNT);
v = make_seed_value(buf, DEFAULT_SEED_CNT);
explicit_bzero(buf, DEFAULT_SEED_LEN);
return v;
}
The variable VALUE v;
at the first line is the return value of the function at the sixth line.
The second line, uint32_t buf[DEFAULT_SEED_CNT+1];
declares an array with a size 1 larger than DEFAULT_SEED_CNT, and the size of the fixed value DEFAULT_SEED_CNT is 4.
Line 3 fill_random_seed(buf, DEFAULT_SEED_CNT);
fill_random_seed to make a random number seed to make a random number seed.
If you read the logic of this method loosely, it looks like this method puts a random byte number with fill_random_bytes
in each element of the size 4 seed of the array, and then takes a nanosecond or microsecond from the CPU time and overwrites it with XOR.
fill_random_bytes looks like it's reading a random number in a Linux system call at a glance, but I'm not following it too closely this time
static void
fill_random_seed(uint32_t *seed, size_t cnt)
{
static int n = 0;
#if defined HAVE_CLOCK_GETTIME
struct timespec tv;
#elif defined HAVE_GETTIMEOFDAY
struct timeval tv;
#endif
size_t len = cnt * sizeof(*seed);
memset(seed, 0, len);
fill_random_bytes(seed, len, FALSE);
#if defined HAVE_CLOCK_GETTIME
clock_gettime(CLOCK_REALTIME, &tv);
seed[0] ^= tv.tv_nsec;
#elif defined HAVE_GETTIMEOFDAY
gettimeofday(&tv, 0);
seed[0] ^= tv.tv_usec;
#endif
seed[1] ^= (uint32_t)tv.tv_sec;
#if SIZEOF_TIME_T > SIZEOF_INT
seed[0] ^= (uint32_t)((time_t)tv.tv_sec >> SIZEOF_INT * CHAR_BIT);
#endif
seed[2] ^= getpid() ^ (n++ << 16);
seed[3] ^= (uint32_t)(VALUE)&seed;
#if SIZEOF_VOIDP > SIZEOF_INT
seed[2] ^= (uint32_t)((VALUE)&seed >> SIZEOF_INT * CHAR_BIT);
#endif
}
The fourth line, v = make_seed_value(buf, DEFAULT_SEED_CNT);
generates a random number from the seed of the random number stored in buf.
In make_seed_value, I think that rb_integer_unpack joins the number of the array in buf to generate a random number.
static VALUE
make_seed_value(uint32_t *ptr, size_t len)
{
VALUE seed;
if (ptr[len-1] <= 1) {
/* set leading-zero-guard */
ptr[len++] = 1;
}
seed = rb_integer_unpack(ptr, len, sizeof(uint32_t), 0,
INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER);
return seed;
}
To summarize the above logic, it can be said that the logic of random_seed is to prepare an array, store random numbers in the array using CPU time, etc., and then combine the arrays to create a random number seed.
Conclusion
- If you consider whether or not seed data is needed to generate random numbers in Ruby, I can say that it is not needed from reading Ruby's code. I have a feeling there's a pattern we need, but I can't think of one at the moment.
- I got a glimpse of how random numbers, which we use casually, are implemented.
- I feel like it's been about five years since I read C.
Top comments (0)