Capítulo 23 10 min de leitura

Random

Números pseudo-aleatórios baseados em xorshift64\* — rápidos, com período completo de 64 bits. Não é criptograficamente seguro, mas é exatamente o que você quer para embaralhar, amostrar, testes aleatorizados e simulações reproduzíveis.

Use seed determinística com `Rng::with_seed` para execuções que possam ser repetidas, ou a partir do relógio monotônico com `Rng::from_clock` para "aleatório o suficiente".

Import

import stdlib::random::*;

Visão geral da interface pública

O módulo expõe exatamente um tipo e oito métodos — três construtores/destrutor e cinco auxiliares de sorteio. Tudo abaixo é construído sobre next_i64.

Item Assinatura Finalidade
Rng pub struct Rng { state: i64 } O gerador (um único i64 de estado).
with_seed pub fn with_seed(seed: i64) -> *Rng Constrói a partir de uma seed conhecida (reproduzível).
from_clock pub fn from_clock() -> *Rng Constrói a partir de now_ns() ("aleatório o suficiente").
free pub fn free(self: *Rng) Libera o gerador.
next_i64 pub fn next_i64(self: *Rng) -> i64 Valor bruto de 64 bits (pode transbordar; pode ser negativo).
next_int pub fn next_int(self: *Rng) -> i32 i32 não-negativo em [0, 2^31 - 1].
range pub fn range(self: *Rng, lo: i32, hi: i32) -> i32 i32 uniforme em [lo, hi).
next_f64 pub fn next_f64(self: *Rng) -> f64 f64 uniforme em [0.0, 1.0).
next_bool pub fn next_bool(self: *Rng) -> bool true com probabilidade ~0,5.

O gerador Rng

pub struct Rng { state: i64 }

Rng guarda um único i64 de estado interno. É alocado na heap pelos seus construtores e deve ser liberado com `free` — use defer para garantir uso sem vazamentos.

Construção

Função Assinatura Descrição
with_seed pub fn with_seed(seed: i64) -> *Rng Cria um Rng a partir de uma seed conhecida; a mesma seed sempre produz o mesmo fluxo.
from_clock pub fn from_clock() -> *Rng Cria um Rng com seed derivada do relógio monotônico (now_ns()).
free pub fn free(self: *Rng) Libera o gerador.

Rng::with_seed

pub fn with_seed(seed: i64) -> *Rng

Constrói um Rng com um estado inicial conhecido. A mesma seed sempre produz o mesmo fluxo, tornando esta a escolha certa para testes e simulações reproduzíveis.

import stdlib::random::*;

fn main() -> i32 {
    let r: *Rng = Rng::with_seed(42);
    defer r.free();
    println!("first draw", r.next_int());   // mesmo valor em toda execução
    return 0;
}

Rng::from_clock

pub fn from_clock() -> *Rng

Constrói um Rng com seed derivada do relógio monotônico. Internamente é apenas Rng::with_seed(now_ns()).

import stdlib::random::*;

fn main() -> i32 {
    let r: *Rng = Rng::from_clock();
    defer r.free();
    for i in 0..5 { println!("d6", r.range(1, 7)); }   // cinco lançamentos de dado
    return 0;
}

free

pub fn free(self: *Rng)

Libera a alocação na heap do gerador. Como os construtores retornam um *Rng bruto, você é responsável pelo tempo de vida — defer r.free(); logo após a construção é o padrão idiomático.

Sorteando valores

Cada sorteio avança o estado interno no lugar. O catálogo completo:

Método Assinatura Intervalo / resultado
next_i64 pub fn next_i64(self: *Rng) -> i64 Valor bruto de 64 bits (pode transbordar); entropia total.
next_int pub fn next_int(self: *Rng) -> i32 i32 não-negativo em [0, 2^31 - 1].
range pub fn range(self: *Rng, lo: i32, hi: i32) -> i32 i32 uniforme em [lo, hi).
next_f64 pub fn next_f64(self: *Rng) -> f64 f64 uniforme em [0.0, 1.0).
next_bool pub fn next_bool(self: *Rng) -> bool true com probabilidade ~0,5.

Os cinco juntos, a partir de um gerador com seed fixa:

import stdlib::random::*;

fn main() -> i32 {
    let r: *Rng = Rng::with_seed(42);
    defer r.free();

    let raw: i64 = r.next_i64();      // 64 bits completos, pode ser negativo
    let n: i32 = r.next_int();        // 0 .. 2^31-1
    let d6: i32 = r.range(1, 7);      // 1..=6
    let p: f64 = r.next_f64();        // [0.0, 1.0)
    let flip: bool = r.next_bool();   // ~50/50

    println!("raw", raw);
    println!("int", n);
    println!("d6", d6);
    if flip { println!("heads"); } else { println!("tails"); }
    if p < 0.5 { println!("low half"); }
    return 0;
}

next_i64

pub fn next_i64(self: *Rng) -> i64

Próximo valor bruto de 64 bits: um passo de xorshift seguido de uma multiplicação por uma constante ímpar. O resultado pode transbordar e pode ser negativo. Use este método quando quiser todos os 64 bits de entropia (hashing, jitter); para inteiros delimitados, prefira `range`. Todos os outros auxiliares de sorteio são invólucros finos sobre este.

let raw: i64 = r.next_i64();

next_int

pub fn next_int(self: *Rng) -> i32

Próximo i32 não-negativo. O valor é mascarado para 31 bits, de forma que o resultado seja sempre positivo após o cast i64 -> i32 — manter todos os 32 bits faria o bit mais significativo torná-lo negativo. Intervalo: [0, 2^31 - 1].

let n: i32 = r.next_int();        // 0 .. 2^31-1

range

pub fn range(self: *Rng, lo: i32, hi: i32) -> i32

Inteiro uniforme no intervalo semiaberto [lo, hi). Retorna lo quando o intervalo é vazio (hi <= lo), portanto nunca falha com um intervalo degenerado.

let dice: i32 = r.range(1, 7);    // d6  -> 1..=6
let pct:  i32 = r.range(0, 100);  // 0..=99
let same: i32 = r.range(5, 5);    // intervalo vazio -> retorna 5

next_f64

pub fn next_f64(self: *Rng) -> f64

Float uniforme em [0.0, 1.0). Construído a partir dos 53 bits superiores de um sorteio bruto, de modo que o resultado caiba exatamente na mantissa de um f64 sem erro de arredondamento nas bordas. Ideal para limiares de probabilidade e ensaios de Bernoulli.

let p: f64 = r.next_f64();
if p < 0.1 { /* executa ~10% das vezes */ }

next_bool

pub fn next_bool(self: *Rng) -> bool

Cara ou coroa — retorna true com probabilidade ~0,5 (testa o bit menos significativo de um sorteio bruto).

if r.next_bool() { println!("heads"); } else { println!("tails"); }

Reprodutibilidade

Dois geradores construídos a partir da mesma seed produzem sequências idênticas, o que torna as execuções com seed fixa reproduzíveis — registre a seed e você poderá repetir a execução exata mais tarde.

import stdlib::random::*;

fn main() -> i32 {
    let a: *Rng = Rng::with_seed(12345);
    defer a.free();
    let b: *Rng = Rng::with_seed(12345);
    defer b.free();

    let mut matched: bool = true;
    for i in 0..10 {
        if a.next_i64() != b.next_i64() { matched = false; }
    }
    if matched { println!("identical streams"); }
    return 0;
}

Verificação de sanidade da distribuição — um ensaio de Bernoulli construído sobre next_f64:

import stdlib::random::*;

fn main() -> i32 {
    // Conta ~250 acertos em 1000 tentativas com p = 0.25.
    let r: *Rng = Rng::with_seed(0);   // 0 é reescrito para um estado não-nulo
    defer r.free();
    let mut hits: i32 = 0;
    for i in 0..1000 {
        if r.next_f64() < 0.25 { hits = hits + 1; }
    }
    println!("approx 250", hits);
    return 0;
}

Receitas

Este módulo fornece apenas a seed e os cinco auxiliares de sorteio; as operações comuns sobre coleções são fáceis de implementar você mesmo.

Selecionando um elemento aleatório (choice)

Indexe um vetor com range(0, len):

import stdlib::random::*;

fn pick(r: *Rng, v: *Vector<i32>) -> i32 {
    let i: i32 = r.range(0, v.len() as i32);
    return v.get(i as usize);
}

fn main() -> i32 {
    let nums: *Vector<i32> = Vector::new();
    defer nums.free();
    push_all!(nums, 10, 20, 30, 40, 50);

    let r: *Rng = Rng::with_seed(99);
    defer r.free();
    for i in 0..3 { println!("pick", pick(r, nums)); }
    return 0;
}

Embaralhando (Fisher–Yates)

Percorra do último índice para baixo, trocando cada elemento com um anterior (ou igual) escolhido aleatoriamente. Note o limite superior inclusivo r.range(0, i + 1).

import stdlib::random::*;

fn shuffle(r: *Rng, v: *Vector<i32>) {
    let n: i32 = v.len() as i32;
    let mut i: i32 = n - 1;
    while i > 0 {
        let j: i32 = r.range(0, i + 1);   // 0..=i
        let tmp: i32 = v.get(i as usize);
        v.set(i as usize, v.get(j as usize));
        v.set(j as usize, tmp);
        i = i - 1;
    }
}

fn main() -> i32 {
    let deck: *Vector<i32> = Vector::new();
    defer deck.free();
    for k in 0..8 { deck.push(k); }

    let r: *Rng = Rng::with_seed(2024);
    defer r.free();
    shuffle(r, deck);

    for k in 0..deck.len() { println!("card", deck.get(k)); }
    return 0;
}

Bytes aleatórios

Não há auxiliar bytes; sorteie cada byte com range(0, 256):

import stdlib::random::*;

fn fill_bytes(r: *Rng, out: *Vector<i32>, count: i32) {
    let mut i: i32 = 0;
    while i < count {
        out.push(r.range(0, 256));   // um byte 0..=255
        i = i + 1;
    }
}

fn main() -> i32 {
    let buf: *Vector<i32> = Vector::new();
    defer buf.free();

    let r: *Rng = Rng::with_seed(7);
    defer r.free();
    fill_bytes(r, buf, 16);

    println!("len", buf.len());
    println!("first", buf.get(0));
    return 0;
}

Veja também

  • `time`now_ns() é a função da qual from_clock extrai a seed.
  • A referência de vectorVector::new, push, get, set, len usados nas receitas acima.