iyi

Standard library, 0.18.0

std/random

A PCG-32 generator, and a handful of random values from it - the same stream, draw for draw, as the other library's Random.new(seed), so a program seeded there and ported here prints the same numbers.

import std/random to use it. Source: src/std/random.iyi at v0.18.0.

Random.new(seed, sequence = 0) is deterministic: the seed is PCG's initial state and the sequence picks one of 2^63 streams, as Random::PCG32.new(initstate, initseq) does. Random.new seeds from sixteen bytes of the platform's own entropy, the source Random::Secure reads. wasm32 seeds from the clocks. PCG is not a lock either way: its state is 128 bits a few outputs give away, so this is shuffle-the-deck randomness, not a secret.

Random::Secure is the secret kind. Every byte it answers comes from the operating system's entropy source at the moment it is asked for, with no state of its own in the process: getrandom on Linux, arc4random_buf in libSystem on darwin, RtlGenRandom on Windows and WASI's random_get on wasm32. Nothing is buffered, because a buffer a fork copied would hand the child the parent's next secrets. It is a Random, so every draw below works on it, and Random::Secure.hex and the rest answer from it without an instance.

next_u is the raw 32 bits. rand/next_float is in [0, 1], 53 bits from two draws. rand(n) is in 0...n for an Int32, UInt32, Int64 or UInt64 n, a draw past the largest multiple of n thrown away so that no value is likelier than another; a bound past 2^32 is made of two draws, high first. rand(range) is the same over an Int32 or Int64 range, and rand(x) and rand(range) take a Float64 too. A bound that holds no value is empty range. random_bytes, hex, base64 and urlsafe_base64 are the other library's, byte for byte from a seeded generator. Random.rand(...) and the rest at class level draw from one generator seeded the first time it is asked. Every one of these makes the other library's draws in the other library's order.

Array#shuffle, Array#sample and sample(n) take a generator, and use a fresh one when handed none; they land here so a program that never asks for a random number does not carry one. Indexable#sample and sample(n) in std/indexable make the same choices for a Deque or a Slice.

Randomclass

std/randomiyi doc src/std/random.iyi
pub class Random
  # The operating system's randomness, for anything a guess must not reach:
  # tokens, keys, nonces, query IDs. It is a `Random`, so `rand(n)`,
  # `rand(range)`, `hex` and every other draw take their values from it, and
  # `Random::Secure.rand(n)` answers without an instance. There is no state to
  # seed: each draw is a fresh request to the kernel (or libSystem, or the
  # Windows or WASI host), and nothing is held between draws, so a `fork`
  # cannot hand the child the parent's next value.
  class Secure < ::Std::Random::Random
    # *count* bytes at *buffer*, every one from the platform's entropy source,
    # or a panic that says why there are none: a byte that only looks random
    # is never the answer.
    def self.fill(buffer : Pointer(UInt8), count : Int32) : Nil
    # The generator fields a `Random` carries, which nothing here reads.
    def initialize
    def next_u : UInt32
    # The whole buffer in one request rather than four bytes at a time.
    def random_bytes(buffer : Array(UInt8)) : Nil
  end
  # *n* random bytes in base64 with `+` `/` and `=` padding, no line breaks.
  def base64(n : Int32 = 16) : String
  def self.base64(n : Int32 = 16) : String
  # *n* random bytes as two lowercase hex digits each.
  def hex(n : Int32 = 16) : String
  def self.hex(n : Int32 = 16) : String
  # The stream came from the seed, and one seed was one stream: the other
  # library takes the stream apart from the seed, zero unless asked, and
  # the same seed gave two different sequences in the two libraries.
  def initialize(seed : UInt64, sequence : UInt64 = 0_u64)
  # Any integer seed and sequence, as the other library takes them. There
  # were two overloads, an `Int32` and an `Int64` seed with a `UInt64`
  # sequence, beside this class's own `UInt64` pair: `Random.new(7, 3)`,
  # the form the comment above writes, cast its literals to all three and
  # was an ambiguous call, and a `UInt32` seed matched none of them. A
  # negative seed or sequence panics in the conversion, as it raises there.
  def self.new(seed : ::Int, sequence : ::Int = 0_u64) : Random
  def self.new : Random
  def next_bool : Bool
  def self.next_bool : Bool
  # 2^53 values, the high draw first: two 32-bit draws make the integer,
  # and it is divided by 2^53 - 1, so both ends can come out.
  def next_float : Float64
  def next_u : UInt32
  # The same draws without a generator in hand, from one seeded the first
  # time it is asked, as the other library's `Random.rand` draws from its
  # default. `Random::Secure.rand(...)` and the rest are these, answered by
  # the operating system instead.
  def self.next_u : UInt32
  def rand(n : Int32) : Int32
  def rand(n : UInt32) : UInt32
  def rand(n : Int64) : Int64
  def rand(n : UInt64) : UInt64
  # In `[0, x)`: 2^53 evenly spaced values, scaled. Not `rand * x`, which
  # divides by 2^53 - 1 and can answer *x* itself.
  def rand(x : Float64) : Float64
  # The span is taken as the unsigned difference, so a range across zero
  # does not overflow on the way. The whole of `Int32` is one draw read as
  # an `Int32`, as the other library reads it, rather than added to the
  # bottom of the range, which would answer the same value with its top
  # bit turned over.
  def rand(range : Range(Int32, Int32)) : Int32
  # As the `Int32` range: the whole of `Int64` is 2^64 values, one more than
  # a `UInt64` span can count, and is two draws, high first, read as one.
  def rand(range : Range(Int64, Int64)) : Int64
  # An end left out is the other library's choice too: `rand(x)` cannot
  # reach *x*, and an inclusive range scales `rand`, which can.
  def rand(range : Range(Float64, Float64)) : Float64
  def rand : Float64
  def self.rand(n : Int32) : Int32
  def self.rand(n : UInt32) : UInt32
  def self.rand(n : Int64) : Int64
  def self.rand(n : UInt64) : UInt64
  def self.rand(x : Float64) : Float64
  def self.rand(range : Range(Int32, Int32)) : Int32
  def self.rand(range : Range(Int64, Int64)) : Int64
  def self.rand(range : Range(Float64, Float64)) : Float64
  def self.rand : Float64
  # Four bytes a draw, low byte first, the last draw cut short: a seeded
  # generator fills a buffer with the other library's bytes.
  #
  # The bytes are an `Array(UInt8)`, as `UUID#bytes` and std/digest's
  # `hash_bytes` are, rather than the other library's `Bytes`: std/slice
  # reaches this module through std/indexable's `sample`, and an import
  # back would be a cycle.
  def random_bytes(buffer : Array(UInt8)) : Nil
  def random_bytes(n : Int32 = 16) : Array(UInt8)
  def self.random_bytes(buffer : Array(UInt8)) : Nil
  def self.random_bytes(n : Int32 = 16) : Array(UInt8)
  # The bytes as a String, which is how std/base64 takes them.
  protected def self.text_of(bytes : Array(UInt8)) : String
  # *n* random bytes in the URL-safe alphabet, `-` and `_`; unpadded unless
  # asked, as the other library answers it, since a token in a URL or a
  # file name has no use for the `=`.
  def urlsafe_base64(n : Int32 = 16, padding : Bool = false) : String
  def self.urlsafe_base64(n : Int32 = 16, padding : Bool = false) : String
end

Array(T)class, reopened

std/randomiyi doc src/std/random.iyi
class ::Array(T)
  def sample(random : Std::Random::Random | ::Nil = nil) : T
  # *n* distinct elements. One is `sample` in a list; more are a reservoir
  # (each element past the first *n* replaces a slot with probability
  # n/(i+1)) shuffled at the end - both as the other library does it.
  def sample(n : Int32, random : Std::Random::Random | ::Nil = nil) : Array(T)
  def shuffle(random : Std::Random::Random | ::Nil = nil) : Array(T)
  # Fisher-Yates from the back, `rand(i + 1)` a step: the other library's
  # `shuffle!`, so a seeded generator puts the elements where it would.
  def shuffle_in_place(random : Std::Random::Random | ::Nil = nil) : Array(T)
end

StaticArray(T, N)struct, reopened

std/randomiyi doc src/std/random.iyi
struct ::StaticArray(T, N)
  def initialize
  # `Array#shuffle_in_place` for the array that holds its elements inline:
  # the other library's `StaticArray#shuffle!`, the same draws in the same
  # order, so a seeded generator puts the elements where it would. Its
  # sample of noise does exactly this to a 256-entry permutation table.
  def shuffle_in_place(random : Std::Random::Random | ::Nil = nil) : self
end