module Random::Formatter - Documentation for Ruby 3.3

# module Random::Formatter

## Random number formatter.¶ ↑

Formats generated random numbers in many manners. When `'random/formatter'` is required, several methods are added to empty core module `Random::Formatter`, making them available as Random’s instance and module methods.

Standard library `SecureRandom` is also extended with the module, and the methods described below are available as a module methods in it.

### Examples¶ ↑

Generate random hexadecimal strings:

    require 'random/formatter'

    prng = Random.new
    prng.hex(10) #=> "52750b30ffbc7de3b362"
    prng.hex(10) #=> "92b15d6c8dc4beb5f559"
    prng.hex(13) #=> "39b290146bea6ce975c37cfc23"
    # or just
    Random.hex #=> "1aed0c631e41be7f77365415541052ee"

Generate random base64 strings:

    prng.base64(10) #=> "EcmTPZwWRAozdA=="
    prng.base64(10) #=> "KO1nIU+p9DKxGg=="
    prng.base64(12) #=> "7kJSM/MzBJI+75j8"
    Random.base64(4) #=> "bsQ3fQ=="

Generate random binary strings:

    prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
    prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
    Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43"

Generate alphanumeric strings:

    prng.alphanumeric(10) #=> "S8baxMJnPl"
    prng.alphanumeric(10) #=> "aOxAg8BAJe"
    Random.alphanumeric #=> "TmP9OsJHJLtaZYhP"

Generate UUIDs:

    prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
    prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
    Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd"

All methods are available in the standard library `SecureRandom`, too:

    SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf"

Generate a random number in the given range as `Random` does

    prng.random_number       #=> 0.5816771641321361
    prng.random_number(1000) #=> 485
    prng.random_number(1..6) #=> 3
    prng.rand                #=> 0.5816771641321361
    prng.rand(1000)          #=> 485
    prng.rand(1..6)          #=> 3

### Constants

ALPHANUMERIC

  The default character list for `alphanumeric`.

### Public Instance Methods

alphanumeric(n = nil, chars: ALPHANUMERIC) click to toggle source

Generate a random alphanumeric string.

The argument n specifies the length, in characters, of the alphanumeric string to be generated. The argument chars specifies the character list which the result is consist of.

If n is not specified or is nil, 16 is assumed. It may be larger in the future.

The result may contain A-Z, a-z and 0-9, unless chars is specified.

    require 'random/formatter'

    Random.alphanumeric     #=> "2BuBuLf3WfSKyQbR"
    # or
    prng = Random.new
    prng.alphanumeric(10) #=> "i6K93NdqiH"

    Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952"
    # or
    prng = Random.new
    prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''."

    # File lib/random/formatter.rb, line 368
    def alphanumeric(n = nil, chars: ALPHANUMERIC)
      n = 16 if n.nil?
      choose(chars, n)
    end

base64(n=nil) click to toggle source

Generate a random base64 string.

The argument n specifies the length, in bytes, of the random number to be generated. The length of the result string is about 4/3 of n.

If n is not specified or is nil, 16 is assumed. It may be larger in the future.

The result may contain A-Z, a-z, 0-9, “+”, “/” and “=”.

    require 'random/formatter'

    Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
    # or
    prng = Random.new
    prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="

See RFC 3548 for the definition of base64.

    # File lib/random/formatter.rb, line 114
    def base64(n=nil)
      [random_bytes(n)].pack("m0")
    end

hex(n=nil) click to toggle source

Generate a random hexadecimal string.

The argument n specifies the length, in bytes, of the random number to be generated. The length of the resulting hexadecimal string is twice of n.

If n is not specified or is nil, 16 is assumed. It may be larger in the future.

The result may contain 0-9 and a-f.

    require 'random/formatter'

    Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
    # or
    prng = Random.new
    prng.hex #=> "91dc3bfb4de5b11d029d376634589b61"

    # File lib/random/formatter.rb, line 92
    def hex(n=nil)
      random_bytes(n).unpack1("H*")
    end

rand → float
rand(max) → number
rand(range) → number

Generates formatted random number from raw random bytes. See `Random#rand`.

Alias for: random_number
random_bytes(n=nil) click to toggle source

Generate a random binary string.

The argument n specifies the length of the result string.

If n is not specified or is nil, 16 is assumed. It may be larger in future.

The result may contain any byte: “x00” - “xff”.

    require 'random/formatter'

    Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
    # or
    prng = Random.new
    prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"

    # File lib/random/formatter.rb, line 71
    def random_bytes(n=nil)
      n = n ? n.to_int : 16
      gen_random(n)
    end

random_number → float click to toggle source
random_number(max) → number
random_number(range) → number

Generates formatted random number from raw random bytes. See `Random#rand`.

    static VALUE
    rand_random_number(int argc, VALUE *argv, VALUE obj)
    {
        rb_random_t *rnd = try_get_rnd(obj);
        VALUE v = rand_random(argc, argv, obj, rnd);
        if (NIL_P(v)) v = rand_random(0, 0, obj, rnd);
        else if (!v) invalid_argument(argv[0]);
        return v;
    }

Also aliased as: rand
urlsafe_base64(n=nil, padding=false) click to toggle source

Generate a random URL-safe base64 string.

The argument n specifies the length, in bytes, of the random number to be generated. The length of the result string is about 4/3 of n.

If n is not specified or is nil, 16 is assumed. It may be larger in the future.

The boolean argument padding specifies the padding. If it is false or nil, padding is not generated. Otherwise padding is generated. By default, padding is not generated because “=” may be used as a URL delimiter.

The result may contain A-Z, a-z, 0-9, “-” and “_”. “=” is also used if padding is true.

    require 'random/formatter'

    Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
    # or
    prng = Random.new
    prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"

    prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
    prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="

See RFC 3548 for the definition of URL-safe base64.

    # File lib/random/formatter.rb, line 145
    def urlsafe_base64(n=nil, padding=false)
      s = [random_bytes(n)].pack("m0")
      s.tr!("+/", "-_")
      s.delete!("=") unless padding
      s
    end

uuid() click to toggle source

Generate a random v4 UUID (Universally Unique IDentifier).

    require 'random/formatter'

    Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
    Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
    # or
    prng = Random.new
    prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"

The version 4 UUID is purely random (except the version). It doesn’t contain meaningful information such as MAC addresses, timestamps, etc.

The result contains 122 random bits (15.25 random bytes).

See RFC4122 for details of UUID.

    # File lib/random/formatter.rb, line 169
    def uuid
      ary = random_bytes(16).unpack("NnnnnN")
      ary[2] = (ary[2] & 0x0fff) | 0x4000
      ary[3] = (ary[3] & 0x3fff) | 0x8000
      "%08x-%04x-%04x-%04x-%04x%08x" % ary
    end

Also aliased as: uuid_v4
uuid_v4()
Alias for: uuid
uuid_v7(extra_timestamp_bits: 0) click to toggle source

Generate a random v7 UUID (Universally Unique IDentifier).

    require 'random/formatter'

    Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e"
    Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5"
    Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23"
    Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31"
    #                    |<--sorted-->| |<----- random ---->|

    # or
    prng = Random.new
    prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98"

The version 7 UUID starts with the least significant 48 bits of a 64 bit Unix timestamp (milliseconds since the epoch) and fills the remaining bits with random data, excluding the version and variant bits.

This allows version 7 UUIDs to be sorted by creation time. `Time` ordered UUIDs can be used for better database index locality of newly inserted records, which may have a significant performance benefit compared to random data inserts.

The result contains 74 random bits (9.25 random bytes).

Note that this method cannot be made reproducable because its output includes not only random bits but also timestamp.

See draft-ietf-uuidrev-rfc4122bis for details of UUIDv7.

#### Monotonicity¶ ↑

UUIDv7 has millisecond precision by default, so multiple UUIDs created within the same millisecond are not issued in monotonically increasing order. To create UUIDs that are time-ordered with sub-millisecond precision, up to 12 bits of additional timestamp may added with `extra_timestamp_bits`. The extra timestamp precision comes at the expense of random bits. Setting `extra_timestamp_bits: 12` provides ~244ns of precision, but only 62 random bits (7.75 random bytes).

    prng = Random.new
    Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) }
    # =>
    ["0188d4c7-13da-74f9-8b53-22a786ffdd5a",
     "0188d4c7-13da-753b-83a5-7fb9b2afaeea",
     "0188d4c7-13da-754a-88ea-ac0baeedd8db",
     "0188d4c7-13da-7557-83e1-7cad9cda0d8d"]
    # |<--- sorted --->| |<-- random --->|

    Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) }
    # =>
    ["0188d4c7-3333-7a95-850a-de6edb858f7e",
     "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9",  # <- out of order
     "0188d4c7-3333-7ae2-995a-9f135dc44ead",  # <- out of order
     "0188d4c7-3333-7af9-87c3-8f612edac82e"]
    # |<--- sorted -->||<---- random --->|

Any rollbacks of the system clock will break monotonicity. UUIDv7 is based on UTC, which excludes leap seconds and can rollback the clock. To avoid this, the system clock can synchronize with an NTP server configured to use a “leap smear” approach. NTP or PTP will also be needed to synchronize across distributed nodes.

Counters and other mechanisms for stronger guarantees of monotonicity are not implemented. Applications with stricter requirements should follow Section 6.2 of the specification.

    # File lib/random/formatter.rb, line 247
    def uuid_v7(extra_timestamp_bits: 0)
      case (extra_timestamp_bits = Integer(extra_timestamp_bits))
      when 0 # min timestamp precision
        ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
        rand = random_bytes(10)
        rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version
        rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant
        "%08x-%04x-%s" % [
          (ms & 0x0000_ffff_ffff_0000) >> 16,
          (ms & 0x0000_0000_0000_ffff),
          rand.unpack("H4H4H12").join("-")
        ]

      when 12 # max timestamp precision
        ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
          .divmod(1_000_000)
        extra_bits = ns * 4096 / 1_000_000
        rand = random_bytes(8)
        rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant
        "%08x-%04x-7%03x-%s" % [
          (ms & 0x0000_ffff_ffff_0000) >> 16,
          (ms & 0x0000_0000_0000_ffff),
          extra_bits,
          rand.unpack("H4H12").join("-")
        ]

      when (0..12) # the generic version is slower than the special cases above
        rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN")
        rand_mask_bits = 12 - extra_timestamp_bits
        ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
          .divmod(1_000_000)
        "%08x-%04x-%04x-%04x-%04x%08x" % [
          (ms & 0x0000_ffff_ffff_0000) >> 16,
          (ms & 0x0000_0000_0000_ffff),
          0x7000 |
            ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) |
            rand_a & ((1 << rand_mask_bits) - 1),
          0x8000 | (rand_b1 & 0x3fff),
          rand_b2,
          rand_b3
        ]

      else
        raise ArgumentError, "extra_timestamp_bits must be in 0..12"
      end
    end

### Private Instance Methods

choose(source, n) click to toggle source

Generate a string that randomly draws from a source array of characters.

The argument source specifies the array of characters from which to generate the string. The argument n specifies the length, in characters, of the string to be generated.

The result may contain whatever characters are in the source array.

    require 'random/formatter'

    prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
    prng.choose([*'0'..'9'], 5)  #=> "27309"

    # File lib/random/formatter.rb, line 313
            def choose(source, n)
      size = source.size
      m = 1
      limit = size
      while limit * size <= 0x100000000
        limit *= size
        m += 1
      end
      result = ''.dup
      while m <= n
        rs = random_number(limit)
        is = rs.digits(size)
        (m-is.length).times { is << 0 }
        result << source.values_at(*is).join('')
        n -= m
      end
      if 0 < n
        rs = random_number(limit)
        is = rs.digits(size)
        if is.length < n
          (n-is.length).times { is << 0 }
        else
          is.pop while n < is.length
        end
        result.concat source.values_at(*is).join('')
      end
      result
    end

gen_random(n) click to toggle source

Internal interface to `Random`; Generate random data n bytes.

    # File lib/random/formatter.rb, line 295
            def gen_random(n)
      self.bytes(n)
    end