stdrandom 0.3.0-alpha.1

Generate random numbers using only Rust standard library
Documentation
//! # stdrandom
//!
//! Random numbers generator using only Rust 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.

   This function is generic.
*/
pub fn fill_numbers<T, F>(buffer: &mut [T], mut rng: F)
where
    F: FnMut() -> T,
    T: Copy, // Ensures numbers are copied safely
{

    if buffer.is_empty() {
        return; // No need to process an empty slice
    }

    for item in buffer.iter_mut() {
        *item = rng();
    }
}

/// Generates a `u64` random number using the current thread's `RandomState`.
///
/// The first number generated in the thread is **random**. Subsequent numbers
/// are derived from an **incremented seed**, leading to progressively
/// lower randomness.
///
/// # Returns
/// - A pseudo-random `u64` number.
///
/// # Example
/// ```
/// let random_value = stdrandom::fast_u64();
/// println!("Generated: {}", random_value);
/// ```
pub fn fast_u64() -> u64 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    hasher.finish()
}

/// Generates a `u32` random number using the current thread's `RandomState`.
///
/// The first number generated in the thread is **random**. Subsequent numbers
/// are derived from an **incremented seed**, leading to progressively
/// lower randomness.
///
/// # Returns
/// - A pseudo-random `u32` number.
///
/// # Example
/// ```
/// let random_value = stdrandom::fast_u32();
/// println!("Generated: {}", random_value);
/// ```
pub fn fast_u32() -> u32 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    (hasher.finish() & 0xFFFFFFFF) as u32
}

/// Generates a **non-negative** `i32` random number using the current thread's `RandomState`.
///
/// The first number generated in the thread is **random**, while subsequent numbers
/// are derived from an **incremented seed**, leading to progressively lower randomness.
///
/// # Returns
/// - A **non-negative** pseudo-random `i32` number.
///
/// # Example
/// ```
/// let random_value = stdrandom::fast_i32();
/// println!("Generated: {}", random_value);
/// ```
pub fn fast_i32() -> i32 {
    let s = RandomState::new();
    let hasher = s.build_hasher();
    (hasher.finish() & 0x7FFFFFFF) as i32 // Mask to ensure non-negative `i32`
}

/// 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};

/// Converts a value of type `S` into type `T`.
///
/// Safe conversion is ensured using `TryFrom` trait.
///
/// # Parameters
/// - `input`: The value to be converted.
///
/// # Returns
/// - The successfully converted value of type `T`.
///
/// # Panics
/// - If TryFrom conversion fails, a panic occurs with a detailed error message.
///
#[track_caller]
fn convert_to_t<S, T>(input: S) -> T
where
    T: TryFrom<S>,
    S: std::fmt::Debug + Copy,
{
    match T::try_from(input) {
        Ok(value) => value,
        Err(_) => panic!(
            "Conversion failed: Cannot convert value {:?} of {} type into target type {}",
            input,
            std::any::type_name::<S>(),
            std::any::type_name::<T>()
        ),
    }
}

/**
  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.)
{
    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;

#[cfg(test)]
mod closure_tests;