DEV Community

@kon_yu
@kon_yu

Posted on

Why Ruby Random object not to need a seed number

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

JavaScript as an example of not needing to seed a random number

const number = Math.random()
console.log(number)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

*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;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)