use crate::error::DecimalError;
use num_traits::FromPrimitive;
use rand::rngs::StdRng;
use rand::{Rng, RngExt, SeedableRng, rng};
use rust_decimal::Decimal;
use std::collections::BTreeSet;
pub const DETERMINISTIC_RNG_DEFAULT_SEED: u64 = 0x0B_AD_C0_FF_EE_15_DE_AD;
#[must_use]
#[inline]
pub fn deterministic_rng(seed: u64) -> StdRng {
StdRng::seed_from_u64(seed)
}
#[must_use]
pub fn get_random_element<T>(set: &BTreeSet<T>) -> Option<&T> {
get_random_element_with(set, &mut rng())
}
#[must_use]
pub fn get_random_element_with<'a, T, R: Rng + ?Sized>(
set: &'a BTreeSet<T>,
rng: &mut R,
) -> Option<&'a T> {
if set.is_empty() {
return None;
}
let random_index = rng.random_range(0..set.len());
set.iter().nth(random_index)
}
pub fn random_decimal<R: Rng + ?Sized>(rng: &mut R) -> Result<Decimal, DecimalError> {
Decimal::from_f64(rng.random::<f64>()).ok_or_else(|| DecimalError::ConversionError {
from_type: "f64".to_string(),
to_type: "Decimal".to_string(),
reason: "Failed to convert f64 to Decimal".to_string(),
})
}
#[cfg(test)]
mod tests_get_random_element {
use super::*;
use std::collections::BTreeSet;
#[test]
fn test_get_random_element_empty_set() {
let set: BTreeSet<i32> = BTreeSet::new();
assert!(get_random_element(&set).is_none());
}
#[test]
fn test_get_random_element_single_element() {
let mut set = BTreeSet::new();
set.insert(42);
assert_eq!(get_random_element(&set), Some(&42));
}
#[test]
fn test_get_random_element_multiple_elements() {
let mut set = BTreeSet::new();
for i in 0..5 {
set.insert(i);
}
let random_element = get_random_element_with(&set, &mut deterministic_rng(3));
assert!(random_element.is_some());
assert!((0..5).contains(random_element.unwrap()));
}
#[test]
fn test_get_random_element_distribution() {
let mut set = BTreeSet::new();
for i in 0..3 {
set.insert(i);
}
let mut counts = vec![0; 3];
let mut rng = deterministic_rng(DETERMINISTIC_RNG_DEFAULT_SEED);
for _ in 0..1000 {
if let Some(&value) = get_random_element_with(&set, &mut rng) {
counts[value as usize] += 1;
}
}
for count in counts {
assert!(count > 200); }
}
}
#[cfg(test)]
mod tests_get_random_element_with {
use super::*;
#[test]
fn test_get_random_element_with_empty_set_returns_none() {
let set: BTreeSet<i32> = BTreeSet::new();
assert!(get_random_element_with(&set, &mut deterministic_rng(1)).is_none());
}
#[test]
fn test_get_random_element_with_same_seed_same_sequence() {
let set: BTreeSet<u32> = (0..100).collect();
let mut a = deterministic_rng(DETERMINISTIC_RNG_DEFAULT_SEED);
let mut b = deterministic_rng(DETERMINISTIC_RNG_DEFAULT_SEED);
for _ in 0..50 {
assert_eq!(
get_random_element_with(&set, &mut a),
get_random_element_with(&set, &mut b)
);
}
}
#[test]
fn test_get_random_element_with_seeded_distribution_is_uniform() {
let set: BTreeSet<usize> = (0..3).collect();
let mut rng = deterministic_rng(42);
let mut counts = [0_u32; 3];
let n = 30_000_u32;
for _ in 0..n {
let value = get_random_element_with(&set, &mut rng).copied();
assert!(value.is_some());
if let Some(v) = value {
counts[v] += 1;
}
}
for count in counts {
assert!((9_592..=10_408).contains(&count), "count {count}");
}
}
}
#[cfg(test)]
mod tests_random_decimal {
use super::*;
use rand::SeedableRng;
use rand::rngs::SmallRng;
use tracing::info;
#[test]
fn test_random_decimal_generates_valid_value() {
let mut t_rng = SmallRng::seed_from_u64(42);
let result = random_decimal(&mut t_rng);
assert!(result.is_ok());
let decimal = result.unwrap();
assert!(decimal >= Decimal::ZERO);
assert!(decimal < Decimal::ONE);
}
#[test]
fn test_random_decimal_different_calls_different_values() {
let mut t_rng = SmallRng::seed_from_u64(42);
let decimal1 = random_decimal(&mut t_rng).unwrap();
let decimal2 = random_decimal(&mut t_rng).unwrap();
assert_ne!(decimal1, decimal2);
}
#[test]
fn test_random_decimal_reproduces_expected_values() {
let mut t_rng = SmallRng::seed_from_u64(12345);
let decimal = random_decimal(&mut t_rng).unwrap();
info!("Generated decimal: {}", decimal);
let mut rng2 = SmallRng::seed_from_u64(12345);
let decimal2 = random_decimal(&mut rng2).unwrap();
assert_eq!(decimal, decimal2);
}
#[test]
fn test_random_decimal_with_multiple_rng_types() {
{
let mut t_rng = SmallRng::seed_from_u64(1);
assert!(random_decimal(&mut t_rng).is_ok());
}
{
let mut t_rng = rng();
assert!(random_decimal(&mut t_rng).is_ok());
}
{
let mut t_rng = SmallRng::seed_from_u64(42);
assert!(random_decimal(&mut t_rng).is_ok());
}
}
#[test]
fn test_multiple_random_decimals() {
let mut t_rng = SmallRng::seed_from_u64(42);
let decimals: Vec<Decimal> = (0..10)
.map(|_| random_decimal(&mut t_rng).unwrap())
.collect();
assert_eq!(decimals.len(), 10);
for i in 0..9 {
assert_ne!(decimals[i], decimals[i + 1]);
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests_deterministic_rng {
use super::*;
#[test]
fn same_seed_yields_identical_stream() {
let mut a = deterministic_rng(12345);
let mut b = deterministic_rng(12345);
for _ in 0..16 {
assert_eq!(a.random::<u64>(), b.random::<u64>());
}
}
#[test]
fn different_seeds_diverge() {
let mut a = deterministic_rng(1);
let mut b = deterministic_rng(2);
let mut collisions = 0;
for _ in 0..32 {
if a.random::<u64>() == b.random::<u64>() {
collisions += 1;
}
}
assert!(
collisions <= 1,
"expected diverging streams, got {collisions} collisions"
);
}
#[test]
fn default_seed_is_stable_across_calls() {
let mut a = deterministic_rng(DETERMINISTIC_RNG_DEFAULT_SEED);
let mut b = deterministic_rng(DETERMINISTIC_RNG_DEFAULT_SEED);
assert_eq!(a.random::<u64>(), b.random::<u64>());
}
}