stdrandom 0.2.0

Generate random numbers using standard library
Documentation
//! # stdrandom
//!
//! Random numbers generator using only standard library.
//!

/*
SPDX-License-Identifier: CC0-1.0 OR Unlicense

This code is released under a "No Copyright" license.

You may use, modify, distribute, and contribute to this code without restriction.
To the extent possible under law, the author(s) of this work waive all copyright and related rights.

Licensed under CC0-1.0 OR Unlicense.

See:
- https://creativecommons.org/publicdomain/zero/1.0/
- https://unlicense.org/
*/

use std::collections::hash_map::RandomState;
use std::hash::BuildHasher;
use std::hash::Hasher;

/**
Fills slice with random bytes using the provided generator function.
*/
pub fn fill_bytes<F>(buffer: &mut [u8], mut rng: F)
where
    F: FnMut() -> u64,
{
    if buffer.is_empty() {
        return; // No need to process an empty slice
    }

    for chunk in buffer.chunks_mut(8) {
        let random_bytes = rng().to_le_bytes();
        chunk.copy_from_slice(&random_bytes[..chunk.len()]);
    }
}

/**
Fills a slice with generated numbers using the provided generator function.
*/
pub fn fill_numbers<T, F>(buffer: &mut [T], mut rng: F)
where
    F: FnMut() -> T,
    T: Copy, // Ensures numbers are copied safely
{
    for item in buffer.iter_mut() {
        *item = rng();
    }
}

/**
Generates u64 random number using current thread RandomState

First returned number in thread is random, following
numbers are derived from incremented seed, having
lower randomness.
*/
pub fn fast_u64() -> u64 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    hasher.finish()
}

/**
Generates u32 random number using current thread RandomState

First returned number in thread is random, following
numbers are derived from incremented seed, having
lower randomness.
*/
pub fn fast_u32() -> u32 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    (hasher.finish() & 0xFFFFFFFF) as u32
}

/// Fast but lower-entropy `f32` in `[0, 1)`
pub fn fast_f32() -> f32 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    (hasher.finish() as f32) / (u64::MAX as f32)
}

/// Fast but lower-entropy `f64` in `[0, 1)`
pub fn fast_f64() -> f64 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    (hasher.finish() as f64) / (u64::MAX as f64)
}

/// Higher entropy `f64` in `[0, 1)`
pub fn random_f64() -> f64 {
    (random_u64() as f64) / (u64::MAX as f64)
}

/// Higher entropy `f32` in `[0, 1)`
pub fn random_f32() -> f32 {
    (random_u64() as f32) / (u64::MAX as f32)
}

/**
  Generates higher entropy random u64 number in separate thread.

  Generated numbers will always have higher randomness than
  subsequent calls to fast_u64().
  Function returns same randomness for lower or upper 32-bits.
*/
pub fn random_u64() -> u64 {
    // Generate random number inside the new thread to get unique RandomState
    let handle = std::thread::spawn(|| fast_u64());

    // Handle potential thread failure
    match handle.join() {
        Ok(randomness) => randomness, // Successfully got randomness from the spawned thread
        Err(_) => {
            // eprintln!("Error joining thread, using less random number from current thread.");
            // Fallback: Generate randomness in the main thread
            fast_u64()
        }
    }
}

/**
  Generates higher entropy random u32 number in separate thread.

  Generated numbers will always have higher randomness
  compared to subsequent fast_u32() calls.
*/

pub fn random_u32() -> u32 {
    (random_u64() & 0xFFFFFFFF) as u32
}

use std::convert::TryFrom;
use std::ops::{Bound, RangeBounds};

/**
  Generates random number in specified range using supplied generator.

  Requested range must fit into target type.
*/
#[track_caller]
pub fn gen_range<R, F, T>(range: R, mut rng: F) -> T
where
    R: RangeBounds<u64>,
    F: FnMut() -> u64,
    T: TryFrom<u64> + 'static, // Enables conversion from `u64` to any smaller integer type (`u32`, `u16`, etc.)
{
    #[track_caller]
    fn convert_to_t<T>(final_result: u64) -> T
    where
        T: TryFrom<u64> + 'static,
    {
        match T::try_from(final_result) {
            Ok(value) => value,
            Err(_) => panic!(
                "Conversion failed: Cannot convert {} into target type {}",
                final_result,
                std::any::type_name::<T>()
            ),
        }
    }

    let start = match range.start_bound() {
        Bound::Included(s) => *s,
        _ => panic!("Invalid range: Start must be included."),
    };

    let (end, is_inclusive) = match range.end_bound() {
        Bound::Included(e) => (*e, true),
        Bound::Excluded(e) => (*e, false),
        _ => panic!("Invalid range: End must be bounded."),
    };

    if end < start {
        panic!("Invalid range: End must be greater than or equal to start.");
    }

    let range_size = if is_inclusive {
        if end == u64::MAX {
            if start == 0 {
                u64::MAX
            } else {
                u64::MAX - start + 1
            }
        } else {
            end - start + 1
        }
    } else {
        end - start
    };

    if range_size == 0 {
        panic!("Invalid range: Cannot generate a number from an empty range.");
    }

    let raw_random = rng();
    let mapped_to_zero_based_range = raw_random % range_size;
    let final_result = start + mapped_to_zero_based_range;

    convert_to_t(final_result)
}

#[cfg(test)]
mod basic_tests;

#[cfg(test)]
mod generator_tests;

#[cfg(test)]
mod overflow_tests;