use core::ops::{Range, RangeInclusive};
use std::io;
mod sys {
use std::io;
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
platform::fill(buf)
}
#[cfg(target_os = "linux")]
mod platform {
use std::io;
use std::sync::atomic::{AtomicU8, Ordering};
extern "C" {
fn getrandom(buf: *mut u8, buflen: usize, flags: u32) -> isize;
fn __errno_location() -> *mut i32;
}
const EINTR: i32 = 4;
const ENOSYS: i32 = 38;
static STATE: AtomicU8 = AtomicU8::new(0);
fn errno() -> i32 {
unsafe { *__errno_location() }
}
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
if STATE.load(Ordering::Relaxed) == 2 {
return urandom_fallback(buf);
}
let mut pos = 0;
while pos < buf.len() {
let r = unsafe { getrandom(buf.as_mut_ptr().add(pos), buf.len() - pos, 0) };
if r > 0 {
pos += r as usize;
continue;
}
let e = errno();
if e == EINTR {
continue;
}
if e == ENOSYS {
STATE.store(2, Ordering::Relaxed);
return urandom_fallback(&mut buf[pos..]);
}
return Err(io::Error::from_raw_os_error(e));
}
STATE.store(1, Ordering::Relaxed);
Ok(())
}
fn urandom_fallback(buf: &mut [u8]) -> io::Result<()> {
use std::fs::File;
use std::io::Read;
let mut f = File::open("/dev/urandom")?;
f.read_exact(buf)
}
}
#[cfg(target_os = "macos")]
mod platform {
use std::io;
extern "C" {
fn getentropy(buf: *mut u8, buflen: usize) -> i32;
fn __error() -> *mut i32;
}
const EINTR: i32 = 4;
const MAX_PER_CALL: usize = 256;
fn errno() -> i32 {
unsafe { *__error() }
}
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
for chunk in buf.chunks_mut(MAX_PER_CALL) {
loop {
let r = unsafe { getentropy(chunk.as_mut_ptr(), chunk.len()) };
if r == 0 {
break;
}
let e = errno();
if e == EINTR {
continue;
}
return Err(io::Error::from_raw_os_error(e));
}
}
Ok(())
}
}
#[cfg(target_os = "windows")]
mod platform {
use std::io;
#[link(name = "bcrypt")]
extern "system" {
fn BCryptGenRandom(
hAlgorithm: *mut core::ffi::c_void,
pbBuffer: *mut u8,
cbBuffer: u32,
dwFlags: u32,
) -> i32;
}
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x0000_0002;
const STATUS_SUCCESS: i32 = 0;
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
for chunk in buf.chunks_mut(u32::MAX as usize) {
let status = unsafe {
BCryptGenRandom(
core::ptr::null_mut(),
chunk.as_mut_ptr(),
chunk.len() as u32,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
if status != STATUS_SUCCESS {
return Err(io::Error::other(format!(
"BCryptGenRandom failed: NTSTATUS 0x{:08X}",
status as u32
)));
}
}
Ok(())
}
}
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
mod platform {
use std::fs::File;
use std::io::{self, Read};
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
let mut f = File::open("/dev/urandom")?;
f.read_exact(buf)
}
}
#[cfg(not(any(unix, target_os = "windows")))]
mod platform {
use std::io;
pub fn fill(_buf: &mut [u8]) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"mod-rand tier3 has no entropy source on this platform",
))
}
}
}
pub fn fill_bytes(buf: &mut [u8]) -> io::Result<()> {
if buf.is_empty() {
return Ok(());
}
sys::fill(buf)
}
pub fn random_u64() -> io::Result<u64> {
let mut buf = [0u8; 8];
fill_bytes(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
pub fn random_u32() -> io::Result<u32> {
let mut buf = [0u8; 4];
fill_bytes(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub fn random_bytes(len: usize) -> io::Result<Vec<u8>> {
let mut v = vec![0u8; len];
fill_bytes(&mut v)?;
Ok(v)
}
pub fn random_hex(bytes: usize) -> io::Result<String> {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut buf = vec![0u8; bytes];
fill_bytes(&mut buf)?;
let mut out = String::with_capacity(bytes * 2);
for b in buf {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0xF) as usize] as char);
}
Ok(out)
}
pub fn random_base32(chars: usize) -> io::Result<String> {
const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
let byte_count = (chars * 5).div_ceil(8);
let mut buf = vec![0u8; byte_count.max(1)];
fill_bytes(&mut buf)?;
let mut out = String::with_capacity(chars);
let mut acc: u64 = 0;
let mut bits: u32 = 0;
let mut idx = 0;
while out.len() < chars {
if bits < 5 {
acc |= (buf[idx] as u64) << bits;
bits += 8;
idx += 1;
if idx == buf.len() && bits < 5 && out.len() < chars {
let mut extra = [0u8; 8];
fill_bytes(&mut extra)?;
for &b in &extra {
acc |= (b as u64) << bits;
bits += 8;
if bits >= 5 + 56 {
break;
}
}
}
}
out.push(ALPHABET[(acc & 0x1F) as usize] as char);
acc >>= 5;
bits -= 5;
}
Ok(out)
}
#[inline]
fn bounded_u64(n: u64) -> io::Result<u64> {
debug_assert!(n != 0, "bounded_u64 requires n > 0");
let mut x = random_u64()?;
let mut m: u128 = (x as u128).wrapping_mul(n as u128);
let mut l: u64 = m as u64;
if l < n {
let t: u64 = n.wrapping_neg() % n;
while l < t {
x = random_u64()?;
m = (x as u128).wrapping_mul(n as u128);
l = m as u64;
}
}
Ok((m >> 64) as u64)
}
#[inline]
fn empty_range_error(msg: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, msg)
}
pub fn random_range_u64(range: Range<u64>) -> io::Result<u64> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u64: empty range"));
}
let span = end - start;
Ok(start + bounded_u64(span)?)
}
pub fn random_range_inclusive_u64(range: RangeInclusive<u64>) -> io::Result<u64> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_u64: empty range"));
}
if start == 0 && end == u64::MAX {
return random_u64();
}
let span = end - start + 1;
Ok(start + bounded_u64(span)?)
}
pub fn random_range_u32(range: Range<u32>) -> io::Result<u32> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u32: empty range"));
}
let span = (end - start) as u64;
Ok((start as u64 + bounded_u64(span)?) as u32)
}
pub fn random_range_inclusive_u32(range: RangeInclusive<u32>) -> io::Result<u32> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_u32: empty range"));
}
let span = (end as u64) - (start as u64) + 1;
Ok((start as u64 + bounded_u64(span)?) as u32)
}
pub fn random_range_i64(range: Range<i64>) -> io::Result<i64> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i64: empty range"));
}
let span = (end as i128 - start as i128) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i128) + (offset as i128)) as i64)
}
pub fn random_range_inclusive_i64(range: RangeInclusive<i64>) -> io::Result<i64> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_i64: empty range"));
}
if start == i64::MIN && end == i64::MAX {
return random_u64().map(|u| u as i64);
}
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i128) + (offset as i128)) as i64)
}
pub fn random_range_i32(range: Range<i32>) -> io::Result<i32> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i32: empty range"));
}
let span = (end as i64 - start as i64) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i32)
}
pub fn random_range_inclusive_i32(range: RangeInclusive<i32>) -> io::Result<i32> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_i32: empty range"));
}
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i32)
}
#[inline]
fn bounded_u128(n: u128) -> io::Result<u128> {
debug_assert!(n != 0, "bounded_u128 requires n > 0");
let mut x = draw_u128()?;
let (mut hi, mut lo) = mul_128_full(x, n);
if lo < n {
let t: u128 = n.wrapping_neg() % n;
while lo < t {
x = draw_u128()?;
let (h, l) = mul_128_full(x, n);
hi = h;
lo = l;
}
}
let _ = x;
Ok(hi)
}
#[inline]
fn draw_u128() -> io::Result<u128> {
let mut buf = [0u8; 16];
fill_bytes(&mut buf)?;
Ok(u128::from_le_bytes(buf))
}
#[inline]
fn mul_128_full(a: u128, b: u128) -> (u128, u128) {
let a_lo: u64 = a as u64;
let a_hi: u64 = (a >> 64) as u64;
let b_lo: u64 = b as u64;
let b_hi: u64 = (b >> 64) as u64;
let p00: u128 = (a_lo as u128) * (b_lo as u128);
let p01: u128 = (a_lo as u128) * (b_hi as u128);
let p10: u128 = (a_hi as u128) * (b_lo as u128);
let p11: u128 = (a_hi as u128) * (b_hi as u128);
let mask_lo: u128 = 0xFFFF_FFFF_FFFF_FFFF;
let mid: u128 = (p00 >> 64) + (p01 & mask_lo) + (p10 & mask_lo);
let low: u128 = (p00 & mask_lo) | (mid << 64);
let high: u128 = p11 + (p01 >> 64) + (p10 >> 64) + (mid >> 64);
(high, low)
}
pub fn random_range_u8(range: Range<u8>) -> io::Result<u8> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u8: empty range"));
}
let span = (end - start) as u64;
Ok((start as u64 + bounded_u64(span)?) as u8)
}
pub fn random_range_inclusive_u8(range: RangeInclusive<u8>) -> io::Result<u8> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_u8: empty range"));
}
let span = (end as u64) - (start as u64) + 1;
Ok((start as u64 + bounded_u64(span)?) as u8)
}
pub fn random_range_u16(range: Range<u16>) -> io::Result<u16> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u16: empty range"));
}
let span = (end - start) as u64;
Ok((start as u64 + bounded_u64(span)?) as u16)
}
pub fn random_range_inclusive_u16(range: RangeInclusive<u16>) -> io::Result<u16> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_u16: empty range"));
}
let span = (end as u64) - (start as u64) + 1;
Ok((start as u64 + bounded_u64(span)?) as u16)
}
pub fn random_range_u128(range: Range<u128>) -> io::Result<u128> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u128: empty range"));
}
let span = end - start;
Ok(start + bounded_u128(span)?)
}
pub fn random_range_inclusive_u128(range: RangeInclusive<u128>) -> io::Result<u128> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error(
"random_range_inclusive_u128: empty range",
));
}
if start == 0 && end == u128::MAX {
return draw_u128();
}
let span = end - start + 1;
Ok(start + bounded_u128(span)?)
}
pub fn random_range_usize(range: Range<usize>) -> io::Result<usize> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_usize: empty range"));
}
let span = (end - start) as u64;
Ok((start as u64 + bounded_u64(span)?) as usize)
}
pub fn random_range_inclusive_usize(range: RangeInclusive<usize>) -> io::Result<usize> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error(
"random_range_inclusive_usize: empty range",
));
}
if start == 0 && end == usize::MAX {
#[cfg(target_pointer_width = "64")]
{
return random_u64().map(|u| u as usize);
}
#[cfg(not(target_pointer_width = "64"))]
{
let span = (end as u64) - (start as u64) + 1;
return bounded_u64(span).map(|o| (start as u64 + o) as usize);
}
}
let span = (end - start) as u64 + 1;
Ok((start as u64 + bounded_u64(span)?) as usize)
}
pub fn random_range_i8(range: Range<i8>) -> io::Result<i8> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i8: empty range"));
}
let span = (end as i64 - start as i64) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i8)
}
pub fn random_range_inclusive_i8(range: RangeInclusive<i8>) -> io::Result<i8> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_i8: empty range"));
}
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i8)
}
pub fn random_range_i16(range: Range<i16>) -> io::Result<i16> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i16: empty range"));
}
let span = (end as i64 - start as i64) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i16)
}
pub fn random_range_inclusive_i16(range: RangeInclusive<i16>) -> io::Result<i16> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_i16: empty range"));
}
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i16)
}
pub fn random_range_i128(range: Range<i128>) -> io::Result<i128> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i128: empty range"));
}
let span = (end as u128).wrapping_sub(start as u128);
let offset = bounded_u128(span)?;
Ok((start as u128).wrapping_add(offset) as i128)
}
pub fn random_range_inclusive_i128(range: RangeInclusive<i128>) -> io::Result<i128> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error(
"random_range_inclusive_i128: empty range",
));
}
if start == i128::MIN && end == i128::MAX {
return draw_u128().map(|u| u as i128);
}
let span = (end as u128).wrapping_sub(start as u128) + 1;
let offset = bounded_u128(span)?;
Ok((start as u128).wrapping_add(offset) as i128)
}
pub fn random_range_isize(range: Range<isize>) -> io::Result<isize> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_isize: empty range"));
}
let span = (end as i128 - start as i128) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i128) + (offset as i128)) as isize)
}
pub fn random_range_inclusive_isize(range: RangeInclusive<isize>) -> io::Result<isize> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error(
"random_range_inclusive_isize: empty range",
));
}
if start == isize::MIN && end == isize::MAX {
#[cfg(target_pointer_width = "64")]
{
return random_u64().map(|u| u as isize);
}
#[cfg(not(target_pointer_width = "64"))]
{
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = bounded_u64(span)?;
return Ok(((start as i128) + (offset as i128)) as isize);
}
}
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i128) + (offset as i128)) as isize)
}
#[inline]
fn invalid_charset_error(msg: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, msg)
}
pub fn random_string(len: usize, charset: &[u8]) -> io::Result<String> {
if charset.is_empty() {
return Err(invalid_charset_error(
"random_string: charset must be non-empty",
));
}
if !charset.iter().all(|&b| b < 128) {
return Err(invalid_charset_error(
"random_string: charset must be ASCII (every byte < 128)",
));
}
let n = charset.len() as u64;
let mut out = String::with_capacity(len);
for _ in 0..len {
let idx = bounded_u64(n)? as usize;
out.push(charset[idx] as char);
}
Ok(out)
}
#[inline]
pub fn random_alphanumeric(len: usize) -> io::Result<String> {
random_string(len, crate::charsets::ALPHANUMERIC)
}
#[inline]
pub fn random_alpha(len: usize) -> io::Result<String> {
random_string(len, crate::charsets::ALPHA)
}
#[inline]
pub fn random_numeric(len: usize) -> io::Result<String> {
random_string(len, crate::charsets::NUMERIC)
}
#[inline]
pub fn random_hex_string(len: usize) -> io::Result<String> {
random_string(len, crate::charsets::HEX_LOWER)
}
pub fn shuffle<T>(slice: &mut [T]) -> io::Result<()> {
let n = slice.len();
if n < 2 {
return Ok(());
}
for i in (1..n).rev() {
let span = (i as u64) + 1;
let j = bounded_u64(span)? as usize;
slice.swap(i, j);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn fill_bytes_produces_output() {
let mut buf = [0u8; 32];
fill_bytes(&mut buf).unwrap();
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn empty_buffer_succeeds_without_syscall() {
let mut buf: [u8; 0] = [];
fill_bytes(&mut buf).unwrap();
}
#[test]
fn random_u64_varies_within_small_sample() {
let mut seen = HashSet::with_capacity(16);
for _ in 0..16 {
seen.insert(random_u64().unwrap());
}
assert!(seen.len() > 1, "u64 output appears constant in sample");
}
#[test]
fn random_u32_varies_within_small_sample() {
let mut seen = HashSet::with_capacity(32);
for _ in 0..32 {
seen.insert(random_u32().unwrap());
}
assert!(seen.len() > 1, "u32 output appears constant in sample");
}
#[test]
fn random_hex_correct_length_and_alphabet() {
let h = random_hex(16).unwrap();
assert_eq!(h.len(), 32);
assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
assert!(h.chars().all(|c| !c.is_ascii_uppercase()));
}
#[test]
fn random_hex_zero_length() {
let h = random_hex(0).unwrap();
assert_eq!(h, "");
}
#[test]
fn random_base32_correct_length_and_alphabet() {
const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
for len in [1, 5, 8, 16, 24, 32, 64] {
let s = random_base32(len).unwrap();
assert_eq!(s.len(), len, "length {len}");
assert!(
s.bytes().all(|b| ALPHABET.contains(&b)),
"alphabet violation in {s}"
);
}
}
#[test]
fn random_bytes_is_correct_length() {
let b = random_bytes(48).unwrap();
assert_eq!(b.len(), 48);
}
#[test]
fn large_buffer_fill_succeeds() {
let mut buf = vec![0u8; 4096];
fill_bytes(&mut buf).unwrap();
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn stress_many_small_calls() {
for _ in 0..1000 {
let mut buf = [0u8; 32];
fill_bytes(&mut buf).unwrap();
}
}
#[test]
fn byte_frequency_chi_squared() {
let mut buf = vec![0u8; 1 << 20];
fill_bytes(&mut buf).unwrap();
let mut counts = [0u32; 256];
for &b in &buf {
counts[b as usize] += 1;
}
let n = buf.len() as f64;
let expected = n / 256.0;
let chi: f64 = counts
.iter()
.map(|&c| {
let diff = c as f64 - expected;
diff * diff / expected
})
.sum();
assert!(chi < 500.0, "byte-frequency chi-squared {chi} too high");
}
#[test]
fn random_range_u64_bounds() {
for _ in 0..1000 {
let n = random_range_u64(100..200).unwrap();
assert!((100..200).contains(&n));
}
}
#[test]
fn random_range_u64_single_value_window() {
for _ in 0..100 {
assert_eq!(random_range_u64(7..8).unwrap(), 7);
}
}
#[test]
fn random_range_inclusive_u64_die_roll_visits_all_faces() {
let mut faces = [0u32; 6];
for _ in 0..1000 {
let d = random_range_inclusive_u64(1..=6).unwrap();
assert!((1..=6).contains(&d));
faces[(d - 1) as usize] += 1;
}
for (i, &c) in faces.iter().enumerate() {
assert!(c > 0, "face {} never appeared in 1000 rolls", i + 1);
}
}
#[test]
fn random_range_inclusive_u64_single_value() {
for _ in 0..100 {
assert_eq!(random_range_inclusive_u64(42..=42).unwrap(), 42);
}
}
#[test]
fn random_range_inclusive_u64_full_width() {
let a = random_range_inclusive_u64(0..=u64::MAX).unwrap();
let b = random_range_inclusive_u64(0..=u64::MAX).unwrap();
assert_ne!(a, b);
}
#[test]
fn random_range_u32_bounds() {
for _ in 0..1000 {
let n = random_range_u32(0..256).unwrap();
assert!(n < 256);
}
}
#[test]
fn random_range_inclusive_u32_full_width() {
for _ in 0..100 {
let _ = random_range_inclusive_u32(0..=u32::MAX).unwrap();
}
}
#[test]
fn random_range_i64_negative() {
for _ in 0..1000 {
let n = random_range_i64(-100..-50).unwrap();
assert!((-100..-50).contains(&n));
}
}
#[test]
fn random_range_i64_mixed_sign() {
let mut saw_neg = false;
let mut saw_pos = false;
for _ in 0..1000 {
let n = random_range_i64(-100..100).unwrap();
assert!((-100..100).contains(&n));
if n < 0 {
saw_neg = true;
}
if n >= 0 {
saw_pos = true;
}
}
assert!(saw_neg && saw_pos);
}
#[test]
fn random_range_inclusive_i64_full_width() {
let _ = random_range_inclusive_i64(i64::MIN..=i64::MAX).unwrap();
}
#[test]
fn random_range_i32_bounds() {
for _ in 0..1000 {
let n = random_range_i32(-1000..1000).unwrap();
assert!((-1000..1000).contains(&n));
}
}
#[test]
fn random_range_inclusive_i32_full_width() {
for _ in 0..100 {
let _ = random_range_inclusive_i32(i32::MIN..=i32::MAX).unwrap();
}
}
#[test]
fn random_range_u64_empty_returns_invalid_input() {
let err = random_range_u64(10..10).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn random_range_u64_reverse_returns_invalid_input() {
let err = random_range_u64(10..5).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn random_range_inclusive_u64_reverse_returns_invalid_input() {
let err = random_range_inclusive_u64(10..=5).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn random_range_i64_reverse_returns_invalid_input() {
let err = random_range_i64(5..-5).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn random_range_uniformity_chi_squared() {
let mut counts = [0u32; 50];
for _ in 0..50_000 {
let v = random_range_u32(0..50).unwrap();
counts[v as usize] += 1;
}
let expected = 50_000.0 / 50.0;
let chi: f64 = counts
.iter()
.map(|&c| {
let diff = c as f64 - expected;
diff * diff / expected
})
.sum();
assert!(
chi < 200.0,
"tier3 chi-squared {chi} too high — bounded-range output is biased"
);
}
}