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}