simple_std/
lib.rs

1//!
2//! # Example: guessing game
3//! ```
4//! use simple_std::{prompt, random_int_range};
5//!
6//! let number = random_int_range(0..100);
7//! loop {
8//!#    // hack the input function for this to work in the doc test    
9//!#    fn prompt(_str: &str) -> String {
10//!#         random_int_range(0..100).to_string()
11//!#    }
12//!     let input = prompt("guess: ").parse::<i32>().expect("not a number");
13//!     if input < number {
14//!         println!("Higher");
15//!     } else if input > number {
16//!         println!("Lower");
17//!     } else {
18//!         println!("Correct!");
19//!         break;
20//!     }
21//! }
22//! ```
23
24pub use io::{input, prompt};
25pub use random::{random_float, random_int_range};
26
27mod io {
28    ///
29    /// Reads a single line of input, similar to Pythons `input` function
30    ///
31    /// # Example
32    /// ```
33    /// use simple_std::input;
34    ///
35    /// println!("What is your name?");
36    /// let name = input();
37    /// println!("Hello {}!", name)
38    /// ```
39    ///
40    /// # Why is this not in std?
41    ///
42    /// The implementation is fairly simple, just 2 lines, but it has a little complexity to it,
43    /// that's why there is the simplified version here.
44    pub fn input() -> String {
45        let mut buffer = String::new();
46        std::io::stdin().read_line(&mut buffer).unwrap();
47        buffer
48    }
49
50    ///
51    /// Reads a single line of input, while providing a message that comes on the same line.
52    ///
53    /// # Example
54    /// ```
55    /// use simple_std::prompt;
56    ///
57    /// let name = prompt("Your name: ");
58    /// println!("Hello {}!", name)
59    /// ```
60    ///
61    /// # Why is this not in std?
62    ///
63    /// see [`input`]
64    pub fn prompt(message: &str) -> String {
65        use std::io::Write;
66
67        print!("{}", message);
68        std::io::stdout().flush().unwrap();
69        input()
70    }
71}
72
73mod random {
74    use std::ops::Range;
75
76    ///
77    /// Returns a random number from 0 to 1, like Javascript `Math.random`
78    ///
79    /// # Example
80    /// ```
81    /// use simple_std::random_float;
82    ///
83    /// let number = random_float();
84    ///
85    /// println!("Number between 0 and 1: {}", number);
86    ///
87    /// assert!(number < 1.0);
88    /// assert!(number >= 0.0);
89    /// ```
90    ///
91    /// # Why is this not in std?
92    ///
93    /// Rust aims to be correct, that's why it's major random number library is cryptographically secure,
94    /// meaning it's randomness can't easily be guessed. And cryptographically secure random number generation
95    /// is a big task, that's why it has it's own crate.
96    pub fn random_float() -> f64 {
97        ((random_u64() >> 11) as f64) / ((1u64 << 53) as f64)
98    }
99
100    ///
101    /// Returns an integer number contained in the range
102    ///
103    /// # Example
104    /// ```
105    /// use simple_std::random_int_range;
106    ///
107    /// let number = random_int_range(0..100);
108    ///
109    /// println!("Number between 0 and 100: {}", number);
110    ///
111    /// assert!(number < 100);
112    /// assert!(number >= 0);
113    /// ```
114    ///
115    /// # Why is this not in std?
116    ///
117    /// See [`random_float`]
118    ///
119    pub fn random_int_range(range: Range<i32>) -> i32 {
120        let difference = range.end - range.start;
121        range.start + ((random_u64() as i32).abs() % difference)
122    }
123
124    /// generates a pseudo-random u32
125    fn random_u64() -> u64 {
126        use std::sync::atomic::{AtomicU64, Ordering};
127
128        static STATE0: AtomicU64 = AtomicU64::new(0);
129        static STATE1: AtomicU64 = AtomicU64::new(0);
130
131        if STATE0.load(Ordering::SeqCst) == 0 {
132            // more or less random initial state
133            STATE0.store((system_time_random()) as u64, Ordering::SeqCst);
134            STATE1.store((system_time_random()) as u64, Ordering::SeqCst);
135        }
136
137        // use xorshift128+ because it's easy https://v8.dev/blog/math-random
138
139        // not a bug
140        let mut s1 = STATE0.load(Ordering::SeqCst);
141        let s0 = STATE1.load(Ordering::SeqCst);
142
143        STATE0.store(s0, Ordering::SeqCst);
144
145        s1 ^= s1 << 23;
146        s1 ^= s1 >> 17;
147        s1 ^= s0;
148        s1 ^= s0 >> 26;
149
150        STATE1.store(s1, Ordering::SeqCst);
151
152        s0.wrapping_add(s1)
153    }
154
155    fn system_time_random() -> u128 {
156        use std::time::SystemTime;
157
158        SystemTime::now()
159            .duration_since(SystemTime::UNIX_EPOCH)
160            .unwrap()
161            .as_micros()
162            ^ SystemTime::now()
163                .duration_since(SystemTime::UNIX_EPOCH)
164                .unwrap()
165                .as_nanos()
166    }
167
168    #[cfg(test)]
169    mod test {
170        use crate::{random_float, random_int_range};
171        use std::iter::repeat_with;
172
173        #[test]
174        fn not_equal() {
175            repeat_with(random_float)
176                .take(100)
177                .collect::<Vec<_>>()
178                .windows(2)
179                .for_each(|win| assert_ne!(win[0], win[1]));
180        }
181
182        #[test]
183        fn between_0_1() {
184            assert!(repeat_with(random_float)
185                .take(100000)
186                .all(|n| n >= 0.0 && n < 1.0))
187        }
188
189        #[test]
190        fn distributed() {
191            assert!(repeat_with(random_float).take(100000).any(|n| n > 0.999));
192            assert!(repeat_with(random_float).take(100000).any(|n| n < 0.001));
193        }
194
195        #[test]
196        fn range_in_range() {
197            [0..10, 5..15, 1000..1004, (-5)..5, (-10)..(-5)]
198                .iter()
199                .for_each(|range| {
200                    assert!(repeat_with(|| random_int_range(range.clone()))
201                        .take(10000)
202                        .all(|n| n < range.end && n >= range.start));
203                })
204        }
205
206        #[test]
207        fn distributed_range() {
208            [0..10, 5..15, 1000..1004, (-5)..5, (-10)..(-5)]
209                .iter()
210                .for_each(|range| {
211                    range.clone().for_each(|expected| {
212                        assert!(repeat_with(|| random_int_range(range.clone()))
213                            .take(100000)
214                            .any(|n| n == expected));
215                    });
216                })
217        }
218    }
219}