1use super::{Generator, TestCase};
2use crate::test_case::invalid_argument;
3use std::marker::PhantomData;
4use std::sync::OnceLock;
5
6pub trait Integer: Copy + Ord {
8 const MIN: Self;
10 const MAX: Self;
12 #[doc(hidden)]
14 fn to_i64_checked(self) -> Option<i64>;
15 #[doc(hidden)]
18 fn to_le17(self) -> [u8; 17];
19 #[doc(hidden)]
23 fn from_le17(bytes: [u8; 17]) -> Self;
24 #[doc(hidden)]
27 fn from_i64(v: i64) -> Self;
28}
29
30macro_rules! impl_signed_integer_type {
31 ($($t:ty),*) => { $(
32 impl Integer for $t {
33 const MIN: Self = <$t>::MIN;
34 const MAX: Self = <$t>::MAX;
35 fn to_i64_checked(self) -> Option<i64> {
36 i64::try_from(self).ok()
37 }
38 fn to_le17(self) -> [u8; 17] {
39 let v = self as i128;
40 let fill = if v < 0 { 0xFF } else { 0x00 };
41 let mut bytes = [fill; 17];
42 bytes[..16].copy_from_slice(&v.to_le_bytes());
43 bytes
44 }
45 fn from_le17(bytes: [u8; 17]) -> Self {
46 i128::from_le_bytes(bytes[..16].try_into().unwrap()) as $t
47 }
48 fn from_i64(v: i64) -> Self {
49 v as $t
50 }
51 }
52 )* };
53}
54
55macro_rules! impl_unsigned_integer_type {
56 ($($t:ty),*) => { $(
57 impl Integer for $t {
58 const MIN: Self = <$t>::MIN;
59 const MAX: Self = <$t>::MAX;
60 fn to_i64_checked(self) -> Option<i64> {
61 i64::try_from(self).ok()
62 }
63 fn to_le17(self) -> [u8; 17] {
64 let v = self as u128;
65 let mut bytes = [0u8; 17];
66 bytes[..16].copy_from_slice(&v.to_le_bytes());
67 bytes
68 }
69 fn from_le17(bytes: [u8; 17]) -> Self {
70 u128::from_le_bytes(bytes[..16].try_into().unwrap()) as $t
71 }
72 fn from_i64(v: i64) -> Self {
73 v as $t
74 }
75 }
76 )* };
77}
78
79impl_signed_integer_type!(i8, i16, i32, i64, i128, isize);
80impl_unsigned_integer_type!(u8, u16, u32, u64, u128, usize);
81
82pub trait Float: Copy + PartialOrd {
84 const MIN: Self;
86 const MAX: Self;
88 fn to_f64(self) -> f64;
90 #[doc(hidden)]
93 fn from_f64(v: f64) -> Self;
94}
95
96impl Float for f32 {
97 const MIN: Self = f32::MIN;
98 const MAX: Self = f32::MAX;
99 fn to_f64(self) -> f64 {
100 self as f64
101 }
102 fn from_f64(v: f64) -> Self {
103 v as f32
104 }
105}
106
107impl Float for f64 {
108 const MIN: Self = f64::MIN;
109 const MAX: Self = f64::MAX;
110 fn to_f64(self) -> f64 {
111 self
112 }
113 fn from_f64(v: f64) -> Self {
114 v
115 }
116}
117
118pub(crate) fn sign_aware_lte<T: Float>(a: T, b: T) -> bool {
125 let a = a.to_f64();
126 let b = b.to_f64();
127 if a == 0.0 && b == 0.0 {
128 a.is_sign_negative() || b.is_sign_positive()
129 } else {
130 a <= b
131 }
132}
133
134pub struct IntegerGenerator<T> {
138 min: Option<T>,
139 max: Option<T>,
140 _phantom: PhantomData<T>,
141}
142
143impl<T> IntegerGenerator<T> {
144 pub fn min_value(mut self, min_value: T) -> Self {
146 self.min = Some(min_value);
147 self
148 }
149
150 pub fn max_value(mut self, max_value: T) -> Self {
152 self.max = Some(max_value);
153 self
154 }
155}
156
157impl<T: Integer> Generator<T> for IntegerGenerator<T> {
158 fn do_draw(&self, tc: &TestCase) -> T {
159 let min = self.min.unwrap_or(T::MIN);
160 let max = self.max.unwrap_or(T::MAX);
161 if min > max {
162 invalid_argument!("Cannot have max_value < min_value");
163 }
164 match (min.to_i64_checked(), max.to_i64_checked()) {
165 (Some(lo), Some(hi)) => T::from_i64(tc.generate_integer_i64(lo, hi)),
166 _ => T::from_le17(tc.generate_integer_le17(&min.to_le17(), &max.to_le17())),
167 }
168 }
169}
170
171pub fn integers<T: Integer>() -> IntegerGenerator<T> {
177 IntegerGenerator {
178 min: None,
179 max: None,
180 _phantom: PhantomData,
181 }
182}
183
184pub struct FloatGenerator<T> {
189 min: Option<T>,
190 max: Option<T>,
191 exclude_min: bool,
192 exclude_max: bool,
193 allow_nan: Option<bool>,
194 allow_infinity: Option<bool>,
195 allow_subnormal: Option<bool>,
196 params: OnceLock<FloatDrawParams>,
197}
198
199impl<T> FloatGenerator<T> {
200 pub fn min_value(mut self, min_value: T) -> Self {
202 self.min = Some(min_value);
203 self.params = OnceLock::new();
204 self
205 }
206
207 pub fn max_value(mut self, max_value: T) -> Self {
209 self.max = Some(max_value);
210 self.params = OnceLock::new();
211 self
212 }
213
214 pub fn exclude_min(mut self, exclude_min: bool) -> Self {
216 self.exclude_min = exclude_min;
217 self.params = OnceLock::new();
218 self
219 }
220
221 pub fn exclude_max(mut self, exclude_max: bool) -> Self {
223 self.exclude_max = exclude_max;
224 self.params = OnceLock::new();
225 self
226 }
227
228 pub fn allow_nan(mut self, allow: bool) -> Self {
230 self.allow_nan = Some(allow);
231 self.params = OnceLock::new();
232 self
233 }
234
235 pub fn allow_infinity(mut self, allow: bool) -> Self {
237 self.allow_infinity = Some(allow);
238 self.params = OnceLock::new();
239 self
240 }
241
242 pub fn allow_subnormal(mut self, allow: bool) -> Self {
248 self.allow_subnormal = Some(allow);
249 self.params = OnceLock::new();
250 self
251 }
252}
253
254struct FloatDrawParams {
257 width: u32,
258 min_value: f64,
259 max_value: f64,
260 allow_nan: bool,
261 allow_infinity: bool,
262 smallest_nonzero_magnitude: f64,
263}
264
265impl<T: Float> FloatGenerator<T> {
266 fn draw_params(&self) -> FloatDrawParams {
267 let width = (std::mem::size_of::<T>() * 8) as u32;
268 let has_min = self.min.is_some();
269 let has_max = self.max.is_some();
270
271 if let (Some(min), Some(max)) = (self.min, self.max) {
272 if !matches!(
273 min.partial_cmp(&max),
274 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
275 ) {
276 invalid_argument!("Cannot have max_value < min_value");
277 }
278 if !sign_aware_lte(min, max) {
279 invalid_argument!(
280 "InvalidArgument: There are no {width}-bit floating-point \
281 values between min_value=0.0 and max_value=-0.0"
282 );
283 }
284 let min_f = min.to_f64();
285 let max_f = max.to_f64();
286 let zero_pair = min_f == 0.0 && max_f == 0.0;
287 if (min_f == max_f || zero_pair) && (self.exclude_min || self.exclude_max) {
288 invalid_argument!(
289 "InvalidArgument: exclude_min/exclude_max leave no \
290 {width}-bit floating-point values in [{min_f}, {max_f}]"
291 );
292 }
293 }
294
295 if self.exclude_min && !has_min {
296 invalid_argument!("InvalidArgument: Cannot have exclude_min=true without min_value");
297 }
298 if self.exclude_max && !has_max {
299 invalid_argument!("InvalidArgument: Cannot have exclude_max=true without max_value");
300 }
301
302 if self.exclude_min && self.min.is_some_and(|v| v.to_f64() == f64::INFINITY) {
303 invalid_argument!(
304 "InvalidArgument: exclude_min=true with min_value=+inf leaves \
305 no {width}-bit floating-point values"
306 );
307 }
308 if self.exclude_max && self.max.is_some_and(|v| v.to_f64() == f64::NEG_INFINITY) {
309 invalid_argument!(
310 "InvalidArgument: exclude_max=true with max_value=-inf leaves \
311 no {width}-bit floating-point values"
312 );
313 }
314
315 let allow_nan = self.allow_nan.unwrap_or(!has_min && !has_max);
316 let allow_infinity = self.allow_infinity.unwrap_or(!has_min || !has_max);
317
318 if allow_nan && (has_min || has_max) {
319 invalid_argument!("Cannot have allow_nan=true with min_value or max_value");
320 }
321 if allow_infinity && has_min && has_max {
322 invalid_argument!("Cannot have allow_infinity=true with both min_value and max_value");
323 }
324
325 let smallest_normal = if width == 32 {
326 f32::MIN_POSITIVE as f64
327 } else {
328 f64::MIN_POSITIVE
329 };
330 let min_f = self.min.map(|v| v.to_f64());
331 let max_f = self.max.map(|v| v.to_f64());
332 let allow_subnormal = self.allow_subnormal.unwrap_or(match (min_f, max_f) {
333 (Some(lo), Some(hi)) if lo == hi => -smallest_normal < lo && lo < smallest_normal,
334 (Some(lo), Some(hi)) => lo < smallest_normal && hi > -smallest_normal,
335 (Some(lo), None) => lo < smallest_normal,
336 (None, Some(hi)) => hi > -smallest_normal,
337 (None, None) => true,
338 });
339 if allow_subnormal {
340 if min_f.is_some_and(|lo| lo >= smallest_normal) {
341 invalid_argument!(
342 "InvalidArgument: allow_subnormal=true, but min_value excludes \
343 all values below the smallest positive normal {smallest_normal}"
344 );
345 }
346 if max_f.is_some_and(|hi| hi <= -smallest_normal) {
347 invalid_argument!(
348 "InvalidArgument: allow_subnormal=true, but max_value excludes \
349 all values above the smallest negative normal -{smallest_normal}"
350 );
351 }
352 } else if let (Some(lo), Some(hi)) = (min_f, max_f) {
353 let contains_zero = lo <= 0.0 && hi >= 0.0;
354 if !contains_zero && hi < smallest_normal && lo > -smallest_normal {
355 invalid_argument!(
356 "InvalidArgument: allow_subnormal=false leaves no {width}-bit \
357 floating-point values in [{lo}, {hi}]"
358 );
359 }
360 }
361
362 let bounded_default = !allow_nan && !allow_infinity;
363 let min_value = match min_f {
364 Some(lo) => lo,
365 None if bounded_default => T::MIN.to_f64(),
366 None => f64::NEG_INFINITY,
367 };
368 let max_value = match max_f {
369 Some(hi) => hi,
370 None if bounded_default => T::MAX.to_f64(),
371 None => f64::INFINITY,
372 };
373
374 let smallest_nonzero_magnitude = if allow_subnormal {
375 if width == 32 {
376 f64::from(f32::from_bits(1))
377 } else {
378 f64::from_bits(1)
379 }
380 } else {
381 smallest_normal
382 };
383
384 FloatDrawParams {
385 width,
386 min_value,
387 max_value,
388 allow_nan,
389 allow_infinity,
390 smallest_nonzero_magnitude,
391 }
392 }
393}
394
395impl<T: Float> Generator<T> for FloatGenerator<T> {
396 fn do_draw(&self, tc: &TestCase) -> T {
397 let params = self.params.get_or_init(|| self.draw_params());
398 let v = tc.generate_float(
399 params.width,
400 params.min_value,
401 params.max_value,
402 params.allow_nan,
403 params.allow_infinity,
404 self.exclude_min,
405 self.exclude_max,
406 params.smallest_nonzero_magnitude,
407 );
408 T::from_f64(v)
409 }
410}
411
412pub fn floats<T: Float>() -> FloatGenerator<T> {
431 FloatGenerator {
432 min: None,
433 max: None,
434 exclude_min: false,
435 exclude_max: false,
436 allow_nan: None,
437 allow_infinity: None,
438 allow_subnormal: None,
439 params: OnceLock::new(),
440 }
441}
442
443#[cfg(test)]
444#[path = "../../tests/embedded/generators/numeric_tests.rs"]
445mod tests;