use core::ops::{Range, RangeInclusive};
const SPLITMIX_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
const JUMP: [u64; 4] = [
0x180E_C6D3_3CFD_0ABA,
0xD5A6_1266_F0C9_392C,
0xA958_2618_E03F_C9AA,
0x39AB_DC45_29B1_661C,
];
const LONG_JUMP: [u64; 4] = [
0x76E1_5D3E_FEFD_CBBF,
0xC500_4E44_1C52_2FB3,
0x7771_0069_854E_E241,
0x3910_9BB0_2ACB_E635,
];
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Xoshiro256 {
state: [u64; 4],
}
impl Xoshiro256 {
#[inline]
pub fn seed_from_u64(seed: u64) -> Self {
let mut x = seed;
Self {
state: [
splitmix64(&mut x),
splitmix64(&mut x),
splitmix64(&mut x),
splitmix64(&mut x),
],
}
}
#[inline]
pub fn from_state(state: [u64; 4]) -> Option<Self> {
if state == [0; 4] {
None
} else {
Some(Self { state })
}
}
#[inline]
pub fn state(&self) -> [u64; 4] {
self.state
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
let result = self.state[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
let t = self.state[1] << 17;
self.state[2] ^= self.state[0];
self.state[3] ^= self.state[1];
self.state[1] ^= self.state[2];
self.state[0] ^= self.state[3];
self.state[2] ^= t;
self.state[3] = self.state[3].rotate_left(45);
result
}
#[inline]
pub fn next_u32(&mut self) -> u32 {
(self.next_u64() >> 32) as u32
}
#[inline]
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / ((1u64 << 53) as f64))
}
pub fn fill_bytes(&mut self, buf: &mut [u8]) {
let mut chunks = buf.chunks_exact_mut(8);
for chunk in &mut chunks {
let bytes = self.next_u64().to_le_bytes();
chunk.copy_from_slice(&bytes);
}
let tail = chunks.into_remainder();
if !tail.is_empty() {
let bytes = self.next_u64().to_le_bytes();
tail.copy_from_slice(&bytes[..tail.len()]);
}
}
#[inline]
pub fn gen_range_u64(&mut self, range: Range<u64>) -> u64 {
let Range { start, end } = range;
assert!(start < end, "gen_range_u64: empty range {start}..{end}");
let span = end - start;
start + self.bounded_u64(span)
}
#[inline]
pub fn gen_range_inclusive_u64(&mut self, range: RangeInclusive<u64>) -> u64 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_u64: empty range {start}..={end}"
);
if start == 0 && end == u64::MAX {
return self.next_u64();
}
let span = end - start + 1;
start + self.bounded_u64(span)
}
#[inline]
pub fn gen_range_u32(&mut self, range: Range<u32>) -> u32 {
let Range { start, end } = range;
assert!(start < end, "gen_range_u32: empty range {start}..{end}");
let span = (end - start) as u64;
(start as u64 + self.bounded_u64(span)) as u32
}
#[inline]
pub fn gen_range_inclusive_u32(&mut self, range: RangeInclusive<u32>) -> u32 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_u32: empty range {start}..={end}"
);
let span = (end as u64) - (start as u64) + 1;
(start as u64 + self.bounded_u64(span)) as u32
}
#[inline]
pub fn gen_range_i64(&mut self, range: Range<i64>) -> i64 {
let Range { start, end } = range;
assert!(start < end, "gen_range_i64: empty range {start}..{end}");
let span = (end as i128 - start as i128) as u64;
let offset = self.bounded_u64(span);
((start as i128) + (offset as i128)) as i64
}
#[inline]
pub fn gen_range_inclusive_i64(&mut self, range: RangeInclusive<i64>) -> i64 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_i64: empty range {start}..={end}"
);
if start == i64::MIN && end == i64::MAX {
return self.next_u64() as i64;
}
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = self.bounded_u64(span);
((start as i128) + (offset as i128)) as i64
}
#[inline]
pub fn gen_range_i32(&mut self, range: Range<i32>) -> i32 {
let Range { start, end } = range;
assert!(start < end, "gen_range_i32: empty range {start}..{end}");
let span = (end as i64 - start as i64) as u64;
let offset = self.bounded_u64(span);
((start as i64) + (offset as i64)) as i32
}
#[inline]
pub fn gen_range_inclusive_i32(&mut self, range: RangeInclusive<i32>) -> i32 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_i32: empty range {start}..={end}"
);
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = self.bounded_u64(span);
((start as i64) + (offset as i64)) as i32
}
#[inline]
pub fn gen_range_f64(&mut self, range: Range<f64>) -> f64 {
let Range { start, end } = range;
assert!(
start.is_finite() && end.is_finite(),
"gen_range_f64: non-finite bounds {start}..{end}"
);
assert!(start < end, "gen_range_f64: empty range {start}..{end}");
let span = end - start;
assert!(
span.is_finite(),
"gen_range_f64: span {start}..{end} overflows to infinity; \
split the range or use a smaller interval"
);
start + self.next_f64() * span
}
#[inline]
pub fn gen_range_u8(&mut self, range: Range<u8>) -> u8 {
let Range { start, end } = range;
assert!(start < end, "gen_range_u8: empty range {start}..{end}");
let span = (end - start) as u64;
(start as u64 + self.bounded_u64(span)) as u8
}
#[inline]
pub fn gen_range_inclusive_u8(&mut self, range: RangeInclusive<u8>) -> u8 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_u8: empty range {start}..={end}"
);
let span = (end as u64) - (start as u64) + 1;
(start as u64 + self.bounded_u64(span)) as u8
}
#[inline]
pub fn gen_range_u16(&mut self, range: Range<u16>) -> u16 {
let Range { start, end } = range;
assert!(start < end, "gen_range_u16: empty range {start}..{end}");
let span = (end - start) as u64;
(start as u64 + self.bounded_u64(span)) as u16
}
#[inline]
pub fn gen_range_inclusive_u16(&mut self, range: RangeInclusive<u16>) -> u16 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_u16: empty range {start}..={end}"
);
let span = (end as u64) - (start as u64) + 1;
(start as u64 + self.bounded_u64(span)) as u16
}
#[inline]
pub fn gen_range_u128(&mut self, range: Range<u128>) -> u128 {
let Range { start, end } = range;
assert!(start < end, "gen_range_u128: empty range {start}..{end}");
let span = end - start;
start + self.bounded_u128(span)
}
#[inline]
pub fn gen_range_inclusive_u128(&mut self, range: RangeInclusive<u128>) -> u128 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_u128: empty range {start}..={end}"
);
if start == 0 && end == u128::MAX {
return ((self.next_u64() as u128) << 64) | (self.next_u64() as u128);
}
let span = end - start + 1;
start + self.bounded_u128(span)
}
#[inline]
pub fn gen_range_usize(&mut self, range: Range<usize>) -> usize {
let Range { start, end } = range;
assert!(start < end, "gen_range_usize: empty range {start}..{end}");
let span = (end - start) as u64;
(start as u64 + self.bounded_u64(span)) as usize
}
#[inline]
pub fn gen_range_inclusive_usize(&mut self, range: RangeInclusive<usize>) -> usize {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_usize: empty range {start}..={end}"
);
if start == 0 && end == usize::MAX {
#[cfg(target_pointer_width = "64")]
{
return self.next_u64() as usize;
}
#[cfg(not(target_pointer_width = "64"))]
{
let span = (end as u64) - (start as u64) + 1;
return (start as u64 + self.bounded_u64(span)) as usize;
}
}
let span = (end - start) as u64 + 1;
(start as u64 + self.bounded_u64(span)) as usize
}
#[inline]
pub fn gen_range_i8(&mut self, range: Range<i8>) -> i8 {
let Range { start, end } = range;
assert!(start < end, "gen_range_i8: empty range {start}..{end}");
let span = (end as i64 - start as i64) as u64;
let offset = self.bounded_u64(span);
((start as i64) + (offset as i64)) as i8
}
#[inline]
pub fn gen_range_inclusive_i8(&mut self, range: RangeInclusive<i8>) -> i8 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_i8: empty range {start}..={end}"
);
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = self.bounded_u64(span);
((start as i64) + (offset as i64)) as i8
}
#[inline]
pub fn gen_range_i16(&mut self, range: Range<i16>) -> i16 {
let Range { start, end } = range;
assert!(start < end, "gen_range_i16: empty range {start}..{end}");
let span = (end as i64 - start as i64) as u64;
let offset = self.bounded_u64(span);
((start as i64) + (offset as i64)) as i16
}
#[inline]
pub fn gen_range_inclusive_i16(&mut self, range: RangeInclusive<i16>) -> i16 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_i16: empty range {start}..={end}"
);
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = self.bounded_u64(span);
((start as i64) + (offset as i64)) as i16
}
#[inline]
pub fn gen_range_i128(&mut self, range: Range<i128>) -> i128 {
let Range { start, end } = range;
assert!(start < end, "gen_range_i128: empty range {start}..{end}");
let span = (end as u128).wrapping_sub(start as u128);
let offset = self.bounded_u128(span);
(start as u128).wrapping_add(offset) as i128
}
#[inline]
pub fn gen_range_inclusive_i128(&mut self, range: RangeInclusive<i128>) -> i128 {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_i128: empty range {start}..={end}"
);
if start == i128::MIN && end == i128::MAX {
let hi = self.next_u64() as u128;
let lo = self.next_u64() as u128;
return ((hi << 64) | lo) as i128;
}
let span = (end as u128).wrapping_sub(start as u128) + 1;
let offset = self.bounded_u128(span);
(start as u128).wrapping_add(offset) as i128
}
#[inline]
pub fn gen_range_isize(&mut self, range: Range<isize>) -> isize {
let Range { start, end } = range;
assert!(start < end, "gen_range_isize: empty range {start}..{end}");
let span = (end as i128 - start as i128) as u64;
let offset = self.bounded_u64(span);
((start as i128) + (offset as i128)) as isize
}
#[inline]
pub fn gen_range_inclusive_isize(&mut self, range: RangeInclusive<isize>) -> isize {
let (start, end) = range.into_inner();
assert!(
start <= end,
"gen_range_inclusive_isize: empty range {start}..={end}"
);
if start == isize::MIN && end == isize::MAX {
#[cfg(target_pointer_width = "64")]
{
return self.next_u64() as isize;
}
#[cfg(not(target_pointer_width = "64"))]
{
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = self.bounded_u64(span);
return ((start as i128) + (offset as i128)) as isize;
}
}
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = self.bounded_u64(span);
((start as i128) + (offset as i128)) as isize
}
#[inline]
pub fn gen_f64(&mut self) -> f64 {
self.next_f64()
}
#[inline]
pub fn gen_f32(&mut self) -> f32 {
(self.next_u32() >> 8) as f32 * (1.0 / ((1u32 << 24) as f32))
}
#[inline]
pub fn gen_bool(&mut self, p: f64) -> bool {
assert!(
p.is_finite() && (0.0..=1.0).contains(&p),
"gen_bool: p={p} must be finite and in [0.0, 1.0]"
);
if p == 0.0 {
return false;
}
if p == 1.0 {
return true;
}
self.next_f64() < p
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn gen_string(&mut self, len: usize, charset: &[u8]) -> String {
assert!(!charset.is_empty(), "gen_string: charset must be non-empty");
assert!(
charset.iter().all(|&b| b < 128),
"gen_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 = self.bounded_u64(n) as usize;
out.push(charset[idx] as char);
}
out
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[inline]
pub fn gen_alphanumeric(&mut self, len: usize) -> String {
self.gen_string(len, crate::charsets::ALPHANUMERIC)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[inline]
pub fn gen_alpha(&mut self, len: usize) -> String {
self.gen_string(len, crate::charsets::ALPHA)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[inline]
pub fn gen_numeric(&mut self, len: usize) -> String {
self.gen_string(len, crate::charsets::NUMERIC)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[inline]
pub fn gen_hex(&mut self, len: usize) -> String {
self.gen_string(len, crate::charsets::HEX_LOWER)
}
pub fn shuffle<T>(&mut self, slice: &mut [T]) {
let n = slice.len();
if n < 2 {
return;
}
for i in (1..n).rev() {
let j = self.gen_range_inclusive_usize(0..=i);
slice.swap(i, j);
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn sample<'a, T>(&mut self, slice: &'a [T], k: usize) -> Vec<&'a T> {
let n = slice.len();
assert!(k <= n, "sample: k={k} exceeds slice length n={n}");
let mut out: Vec<&'a T> = Vec::with_capacity(k);
if k == 0 {
return out;
}
let mut remaining_needed = k as u64;
let mut remaining_pool = n as u64;
for item in slice {
let draw = self.bounded_u64(remaining_pool);
if draw < remaining_needed {
out.push(item);
remaining_needed -= 1;
if remaining_needed == 0 {
break;
}
}
remaining_pool -= 1;
}
out
}
pub fn weighted_index(&mut self, weights: &[f64]) -> Option<usize> {
if weights.is_empty() {
return None;
}
let mut total = 0.0_f64;
for (i, &w) in weights.iter().enumerate() {
assert!(
w.is_finite() && w >= 0.0,
"weighted_index: weights[{i}]={w} must be finite and non-negative"
);
total += w;
}
if total <= 0.0 {
return None;
}
let pick = self.next_f64() * total;
let mut cumulative = 0.0_f64;
for (i, &w) in weights.iter().enumerate() {
cumulative += w;
if pick < cumulative {
return Some(i);
}
}
weights.iter().rposition(|&w| w > 0.0)
}
pub fn weighted_choice<'a, T>(&mut self, items: &'a [T], weights: &[f64]) -> Option<&'a T> {
assert_eq!(
items.len(),
weights.len(),
"weighted_choice: items.len()={} != weights.len()={}",
items.len(),
weights.len()
);
self.weighted_index(weights).map(|i| &items[i])
}
#[inline]
fn bounded_u128(&mut self, n: u128) -> u128 {
debug_assert!(n != 0, "bounded_u128 requires n > 0");
let mut x = self.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 = self.draw_u128();
let (h, l) = mul_128_full(x, n);
hi = h;
lo = l;
}
}
let _ = x;
hi
}
#[inline]
fn draw_u128(&mut self) -> u128 {
((self.next_u64() as u128) << 64) | (self.next_u64() as u128)
}
#[inline]
fn bounded_u64(&mut self, n: u64) -> u64 {
debug_assert!(n != 0, "bounded_u64 requires n > 0");
let mut x = self.next_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 = self.next_u64();
m = (x as u128).wrapping_mul(n as u128);
l = m as u64;
}
}
(m >> 64) as u64
}
pub fn jump(&mut self) {
self.apply_jump(&JUMP);
}
pub fn long_jump(&mut self) {
self.apply_jump(&LONG_JUMP);
}
fn apply_jump(&mut self, constants: &[u64; 4]) {
let mut s0 = 0u64;
let mut s1 = 0u64;
let mut s2 = 0u64;
let mut s3 = 0u64;
for &word in constants {
for b in 0..64 {
if (word & (1u64 << b)) != 0 {
s0 ^= self.state[0];
s1 ^= self.state[1];
s2 ^= self.state[2];
s3 ^= self.state[3];
}
let _ = self.next_u64();
}
}
self.state[0] = s0;
self.state[1] = s1;
self.state[2] = s2;
self.state[3] = s3;
}
}
#[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)
}
#[inline]
fn splitmix64(x: &mut u64) -> u64 {
*x = x.wrapping_add(SPLITMIX_GAMMA);
let mut z = *x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seed_produces_a_value() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.next_u64();
}
#[test]
fn different_seeds_produce_different_streams() {
let mut a = Xoshiro256::seed_from_u64(1);
let mut b = Xoshiro256::seed_from_u64(2);
assert_ne!(a.next_u64(), b.next_u64());
}
#[test]
fn same_seed_produces_same_stream() {
let mut a = Xoshiro256::seed_from_u64(42);
let mut b = Xoshiro256::seed_from_u64(42);
for _ in 0..256 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn zero_seed_does_not_yield_zero_state() {
let mut rng = Xoshiro256::seed_from_u64(0);
assert_ne!(rng.state(), [0; 4]);
assert_ne!(rng.next_u64(), 0);
}
#[test]
fn from_state_rejects_all_zero() {
assert!(Xoshiro256::from_state([0, 0, 0, 0]).is_none());
assert!(Xoshiro256::from_state([0, 0, 0, 1]).is_some());
}
#[test]
fn state_roundtrip_resumes_stream() {
let mut rng = Xoshiro256::seed_from_u64(99);
for _ in 0..17 {
let _ = rng.next_u64();
}
let snap = rng.state();
let mut resumed = Xoshiro256::from_state(snap).unwrap();
for _ in 0..32 {
assert_eq!(rng.next_u64(), resumed.next_u64());
}
}
#[test]
fn splitmix64_known_value_zero_counter() {
let mut x = 0u64;
assert_eq!(splitmix64(&mut x), 0xE220_A839_7B1D_CDAF);
}
#[test]
fn xoshiro_known_first_outputs_for_seed_zero() {
let mut rng = Xoshiro256::seed_from_u64(0);
let v0 = rng.next_u64();
let v1 = rng.next_u64();
let v2 = rng.next_u64();
let v3 = rng.next_u64();
assert_ne!(v0, 0);
assert_ne!(v0, v1);
assert_ne!(v1, v2);
assert_ne!(v2, v3);
let mut rng2 = Xoshiro256::seed_from_u64(0);
assert_eq!(rng2.next_u64(), v0);
assert_eq!(rng2.next_u64(), v1);
assert_eq!(rng2.next_u64(), v2);
assert_eq!(rng2.next_u64(), v3);
}
#[test]
fn next_u32_takes_high_bits() {
let mut a = Xoshiro256::seed_from_u64(7);
let mut b = Xoshiro256::seed_from_u64(7);
for _ in 0..64 {
let hi = (a.next_u64() >> 32) as u32;
assert_eq!(hi, b.next_u32());
}
}
#[test]
fn next_f64_bounds() {
let mut rng = Xoshiro256::seed_from_u64(1);
for _ in 0..10_000 {
let f = rng.next_f64();
assert!((0.0..1.0).contains(&f));
}
}
#[test]
fn fill_bytes_exact_chunks() {
let mut a = Xoshiro256::seed_from_u64(123);
let mut b = Xoshiro256::seed_from_u64(123);
let mut buf = [0u8; 64];
a.fill_bytes(&mut buf);
for chunk in buf.chunks_exact(8) {
let expected = b.next_u64().to_le_bytes();
assert_eq!(chunk, expected);
}
}
#[test]
fn fill_bytes_partial_tail() {
let mut rng = Xoshiro256::seed_from_u64(2026);
let mut buf = [0u8; 13];
rng.fill_bytes(&mut buf);
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn jump_produces_different_state() {
let mut a = Xoshiro256::seed_from_u64(1);
let mut b = a.clone();
b.jump();
assert_ne!(a.state(), b.state());
assert_ne!(a.next_u64(), b.next_u64());
}
#[test]
fn long_jump_produces_different_state() {
let mut a = Xoshiro256::seed_from_u64(1);
let mut b = a.clone();
b.long_jump();
assert_ne!(a.state(), b.state());
assert_ne!(a.next_u64(), b.next_u64());
}
#[test]
fn jump_is_deterministic() {
let mut a = Xoshiro256::seed_from_u64(5);
let mut b = Xoshiro256::seed_from_u64(5);
a.jump();
b.jump();
assert_eq!(a.state(), b.state());
}
#[test]
fn gen_range_u64_bounds() {
let mut rng = Xoshiro256::seed_from_u64(1);
for _ in 0..10_000 {
let n = rng.gen_range_u64(100..200);
assert!((100..200).contains(&n));
}
}
#[test]
fn gen_range_u64_single_value_at_top() {
let mut rng = Xoshiro256::seed_from_u64(2);
for _ in 0..1000 {
assert_eq!(rng.gen_range_u64(7..8), 7);
}
}
#[test]
fn gen_range_inclusive_u64_die_roll() {
let mut rng = Xoshiro256::seed_from_u64(3);
let mut faces = [0u32; 6];
for _ in 0..10_000 {
let d = rng.gen_range_inclusive_u64(1..=6);
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 10000 rolls", i + 1);
}
}
#[test]
fn gen_range_inclusive_u64_single_value() {
let mut rng = Xoshiro256::seed_from_u64(4);
for _ in 0..1000 {
assert_eq!(rng.gen_range_inclusive_u64(42..=42), 42);
}
}
#[test]
fn gen_range_inclusive_u64_full_width_uses_raw_draw() {
let mut a = Xoshiro256::seed_from_u64(5);
let mut b = Xoshiro256::seed_from_u64(5);
for _ in 0..256 {
assert_eq!(a.gen_range_inclusive_u64(0..=u64::MAX), b.next_u64());
}
}
#[test]
fn gen_range_u32_bounds() {
let mut rng = Xoshiro256::seed_from_u64(6);
for _ in 0..10_000 {
let n = rng.gen_range_u32(0..256);
assert!(n < 256);
}
}
#[test]
fn gen_range_inclusive_u32_full_width() {
let mut rng = Xoshiro256::seed_from_u64(7);
for _ in 0..1000 {
let _ = rng.gen_range_inclusive_u32(0..=u32::MAX);
}
}
#[test]
fn gen_range_i64_negative_range() {
let mut rng = Xoshiro256::seed_from_u64(8);
for _ in 0..10_000 {
let n = rng.gen_range_i64(-100..-50);
assert!((-100..-50).contains(&n));
}
}
#[test]
fn gen_range_i64_mixed_sign_range() {
let mut rng = Xoshiro256::seed_from_u64(9);
let mut saw_neg = false;
let mut saw_pos = false;
for _ in 0..10_000 {
let n = rng.gen_range_i64(-100..100);
assert!((-100..100).contains(&n));
if n < 0 {
saw_neg = true;
}
if n >= 0 {
saw_pos = true;
}
}
assert!(saw_neg && saw_pos);
}
#[test]
fn gen_range_inclusive_i64_full_width_is_raw_draw() {
let mut a = Xoshiro256::seed_from_u64(10);
let mut b = Xoshiro256::seed_from_u64(10);
for _ in 0..256 {
let from_range = a.gen_range_inclusive_i64(i64::MIN..=i64::MAX);
let from_raw = b.next_u64() as i64;
assert_eq!(from_range, from_raw);
}
}
#[test]
fn gen_range_i32_bounds() {
let mut rng = Xoshiro256::seed_from_u64(11);
for _ in 0..10_000 {
let n = rng.gen_range_i32(-1000..1000);
assert!((-1000..1000).contains(&n));
}
}
#[test]
fn gen_range_inclusive_i32_full_width() {
let mut rng = Xoshiro256::seed_from_u64(12);
for _ in 0..1000 {
let _ = rng.gen_range_inclusive_i32(i32::MIN..=i32::MAX);
}
}
#[test]
fn gen_range_f64_bounds() {
let mut rng = Xoshiro256::seed_from_u64(13);
for _ in 0..10_000 {
let x = rng.gen_range_f64(-1.0..1.0);
assert!((-1.0..1.0).contains(&x));
}
}
#[test]
fn gen_range_f64_positive_range() {
let mut rng = Xoshiro256::seed_from_u64(14);
for _ in 0..10_000 {
let x = rng.gen_range_f64(10.0..20.0);
assert!((10.0..20.0).contains(&x));
}
}
#[test]
#[should_panic(expected = "empty range")]
fn gen_range_u64_panics_on_empty() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_u64(10..10);
}
#[test]
#[should_panic(expected = "empty range")]
#[allow(clippy::reversed_empty_ranges)]
fn gen_range_u64_panics_on_reverse() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_u64(10..5);
}
#[test]
#[should_panic(expected = "empty range")]
#[allow(clippy::reversed_empty_ranges)]
fn gen_range_inclusive_u64_panics_on_reverse() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_inclusive_u64(10..=5);
}
#[test]
#[should_panic(expected = "empty range")]
#[allow(clippy::reversed_empty_ranges)]
fn gen_range_i64_panics_on_reverse() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_i64(5..-5);
}
#[test]
#[should_panic(expected = "non-finite")]
fn gen_range_f64_panics_on_nan() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_f64(f64::NAN..1.0);
}
#[test]
#[should_panic(expected = "non-finite")]
fn gen_range_f64_panics_on_infinity() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_f64(0.0..f64::INFINITY);
}
#[test]
#[should_panic(expected = "empty range")]
fn gen_range_f64_panics_on_reverse() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_f64(1.0..0.0);
}
#[test]
#[should_panic(expected = "overflows to infinity")]
fn gen_range_f64_panics_when_span_overflows() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.gen_range_f64(-f64::MAX..f64::MAX);
}
#[test]
fn gen_range_f64_output_is_finite_for_finite_span() {
let mut rng = Xoshiro256::seed_from_u64(0xF1_F1_F1);
for _ in 0..10_000 {
let x = rng.gen_range_f64(-1e100..1e100);
assert!(x.is_finite(), "got non-finite {x}");
assert!((-1e100..1e100).contains(&x));
}
}
#[test]
fn gen_range_is_deterministic_under_same_seed() {
let mut a = Xoshiro256::seed_from_u64(99);
let mut b = Xoshiro256::seed_from_u64(99);
for _ in 0..1000 {
assert_eq!(a.gen_range_u64(0..1000), b.gen_range_u64(0..1000));
}
}
#[test]
fn gen_range_uniformity_chi_squared() {
let mut rng = Xoshiro256::seed_from_u64(0xC0DE_F00D);
let mut counts = [0u32; 100];
for _ in 0..100_000 {
let v = rng.gen_range_u32(0..100);
counts[v as usize] += 1;
}
let expected = 100_000.0 / 100.0;
let chi: f64 = counts
.iter()
.map(|&c| {
let diff = c as f64 - expected;
diff * diff / expected
})
.sum();
assert!(
chi < 250.0,
"chi-squared {chi} too high — bounded-range output is biased"
);
}
#[test]
fn gen_range_die_roll_uniformity() {
let mut rng = Xoshiro256::seed_from_u64(0xD1CE_D011);
let mut faces = [0u32; 6];
for _ in 0..600_000 {
let d = rng.gen_range_inclusive_u32(1..=6);
faces[(d - 1) as usize] += 1;
}
let expected = 100_000.0;
let chi: f64 = faces
.iter()
.map(|&c| {
let diff = c as f64 - expected;
diff * diff / expected
})
.sum();
assert!(chi < 50.0, "d6 uniformity chi-squared {chi} too high");
}
}