Skip to main content

feagi_evolutionary/
random.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Cross-platform random number generation for FEAGI evolution.
6
7Uses platform-specific implementations:
8- Desktop/Server: `rand` crate (fast, native)
9- WASM/Browser: JavaScript's `crypto.getRandomValues()` (Web Crypto API)
10
11Copyright 2025 Neuraville Inc.
12Licensed under the Apache License, Version 2.0
13*/
14
15/// Generate random f64 in range [0.0, 1.0)
16#[cfg(not(target_family = "wasm"))]
17pub fn random_f64() -> f64 {
18    use rand::Rng;
19    rand::thread_rng().gen()
20}
21
22#[cfg(target_family = "wasm")]
23pub fn random_f64() -> f64 {
24    js_sys::Math::random()
25}
26
27/// Generate random f32 in range [0.0, 1.0)
28pub fn random_f32() -> f32 {
29    random_f64() as f32
30}
31
32/// Fill buffer with random bytes
33#[cfg(not(target_family = "wasm"))]
34pub fn random_bytes(buffer: &mut [u8]) {
35    use rand::RngCore;
36    rand::thread_rng().fill_bytes(buffer);
37}
38
39#[cfg(target_family = "wasm")]
40pub fn random_bytes(buffer: &mut [u8]) {
41    // Get browser's crypto API
42    let window = web_sys::window().expect("no global window");
43    let crypto = window.crypto().expect("crypto not available");
44
45    // Fill buffer directly - web_sys supports passing &mut [u8]
46    crypto
47        .get_random_values_with_u8_array(buffer)
48        .expect("get_random_values failed");
49}
50
51/// Generate random integer in range [min, max)
52pub fn random_range(min: i32, max: i32) -> i32 {
53    assert!(min < max, "min must be less than max");
54    let range = (max - min) as f64;
55    min + (random_f64() * range) as i32
56}
57
58/// Generate random u64 (useful for IDs, seeds, etc.)
59pub fn random_u64() -> u64 {
60    let mut bytes = [0u8; 8];
61    random_bytes(&mut bytes);
62    u64::from_le_bytes(bytes)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_random_f64() {
71        for _ in 0..100 {
72            let val = random_f64();
73            assert!(
74                (0.0..1.0).contains(&val),
75                "random_f64 out of range: {}",
76                val
77            );
78        }
79    }
80
81    #[test]
82    fn test_random_f32() {
83        for _ in 0..100 {
84            let val = random_f32();
85            assert!(
86                (0.0..1.0).contains(&val),
87                "random_f32 out of range: {}",
88                val
89            );
90        }
91    }
92
93    #[test]
94    fn test_random_bytes() {
95        let mut buffer = [0u8; 32];
96        random_bytes(&mut buffer);
97
98        // Extremely unlikely to get all zeros
99        let all_zeros = buffer.iter().all(|&b| b == 0);
100        assert!(!all_zeros, "random_bytes returned all zeros");
101    }
102
103    #[test]
104    fn test_random_range() {
105        for _ in 0..100 {
106            let val = random_range(10, 20);
107            assert!(
108                (10..20).contains(&val),
109                "random_range out of range: {}",
110                val
111            );
112        }
113    }
114
115    #[test]
116    fn test_random_u64() {
117        let mut values = std::collections::HashSet::new();
118        for _ in 0..100 {
119            let val = random_u64();
120            values.insert(val);
121        }
122
123        // Should have good entropy - at least 95 unique values out of 100
124        assert!(
125            values.len() >= 95,
126            "random_u64 has poor entropy: {} unique out of 100",
127            values.len()
128        );
129    }
130}