hegel/generators/
numeric.rs1use super::{BasicGenerator, Generator};
2use crate::cbor_utils::{cbor_map, cbor_serialize, map_insert};
3use ciborium::Value;
4use std::marker::PhantomData;
5
6pub trait Integer: Copy + Ord {
8 const MIN: Self;
10 const MAX: Self;
12}
13
14macro_rules! impl_integer_type {
15 ($($t:ty),*) => { $(
16 impl Integer for $t {
17 const MIN: Self = <$t>::MIN;
18 const MAX: Self = <$t>::MAX;
19 }
20 )* };
21}
22
23impl_integer_type!(
24 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
25);
26
27pub trait Float: Copy + PartialOrd {
29 const MIN: Self;
31 const MAX: Self;
33 fn to_f64(self) -> f64;
35}
36
37impl Float for f32 {
38 const MIN: Self = f32::MIN;
39 const MAX: Self = f32::MAX;
40 fn to_f64(self) -> f64 {
41 self as f64
42 }
43}
44
45impl Float for f64 {
46 const MIN: Self = f64::MIN;
47 const MAX: Self = f64::MAX;
48 fn to_f64(self) -> f64 {
49 self
50 }
51}
52
53pub(crate) fn sign_aware_lte<T: Float>(a: T, b: T) -> bool {
60 let a = a.to_f64();
61 let b = b.to_f64();
62 if a == 0.0 && b == 0.0 {
63 a.is_sign_negative() || b.is_sign_positive()
64 } else {
65 a <= b
66 }
67}
68
69pub struct IntegerGenerator<T> {
73 min: Option<T>,
74 max: Option<T>,
75 _phantom: PhantomData<T>,
76}
77
78impl<T> IntegerGenerator<T> {
79 pub fn min_value(mut self, min_value: T) -> Self {
81 self.min = Some(min_value);
82 self
83 }
84
85 pub fn max_value(mut self, max_value: T) -> Self {
87 self.max = Some(max_value);
88 self
89 }
90}
91
92impl<T: Integer + serde::Serialize> IntegerGenerator<T> {
93 fn build_schema(&self) -> Value {
94 let min = self.min.unwrap_or(T::MIN);
95 let max = self.max.unwrap_or(T::MAX);
96 assert!(min <= max, "Cannot have max_value < min_value");
97
98 cbor_map! {
99 "type" => "integer",
100 "min_value" => cbor_serialize(&min),
101 "max_value" => cbor_serialize(&max)
102 }
103 }
104}
105
106impl<T: Integer + serde::de::DeserializeOwned + serde::Serialize + Send + Sync + 'static>
107 Generator<T> for IntegerGenerator<T>
108{
109 fn as_basic(&self) -> Option<BasicGenerator<'_, T>> {
110 Some(BasicGenerator::new(self.build_schema(), |raw| {
111 super::deserialize_value(raw)
112 }))
113 }
114}
115
116pub fn integers<
122 T: Integer + serde::de::DeserializeOwned + serde::Serialize + Send + Sync + 'static,
123>() -> IntegerGenerator<T> {
124 IntegerGenerator {
125 min: None,
126 max: None,
127 _phantom: PhantomData,
128 }
129}
130
131pub struct FloatGenerator<T> {
136 min: Option<T>,
137 max: Option<T>,
138 exclude_min: bool,
139 exclude_max: bool,
140 allow_nan: Option<bool>,
141 allow_infinity: Option<bool>,
142}
143
144impl<T> FloatGenerator<T> {
145 pub fn min_value(mut self, min_value: T) -> Self {
147 self.min = Some(min_value);
148 self
149 }
150
151 pub fn max_value(mut self, max_value: T) -> Self {
153 self.max = Some(max_value);
154 self
155 }
156
157 pub fn exclude_min(mut self, exclude_min: bool) -> Self {
159 self.exclude_min = exclude_min;
160 self
161 }
162
163 pub fn exclude_max(mut self, exclude_max: bool) -> Self {
165 self.exclude_max = exclude_max;
166 self
167 }
168
169 pub fn allow_nan(mut self, allow: bool) -> Self {
171 self.allow_nan = Some(allow);
172 self
173 }
174
175 pub fn allow_infinity(mut self, allow: bool) -> Self {
177 self.allow_infinity = Some(allow);
178 self
179 }
180}
181
182impl<T: Float + serde::Serialize> FloatGenerator<T> {
183 fn build_schema(&self) -> Value {
184 let width = (std::mem::size_of::<T>() * 8) as u64;
185 let has_min = self.min.is_some();
186 let has_max = self.max.is_some();
187
188 if let (Some(min), Some(max)) = (self.min, self.max) {
189 assert!(min <= max, "Cannot have max_value < min_value");
190 if !sign_aware_lte(min, max) {
193 panic!(
194 "InvalidArgument: There are no {width}-bit floating-point \
195 values between min_value=0.0 and max_value=-0.0"
196 );
197 }
198 let min_f = min.to_f64();
202 let max_f = max.to_f64();
203 let zero_pair = min_f == 0.0 && max_f == 0.0;
204 if (min_f == max_f || zero_pair) && (self.exclude_min || self.exclude_max) {
205 panic!(
206 "InvalidArgument: exclude_min/exclude_max leave no \
207 {width}-bit floating-point values in [{min_f}, {max_f}]"
208 );
209 }
210 }
211
212 if self.exclude_min && self.min.is_some_and(|v| v.to_f64() == f64::INFINITY) {
216 panic!(
217 "InvalidArgument: exclude_min=true with min_value=+inf leaves \
218 no {width}-bit floating-point values"
219 );
220 }
221 if self.exclude_max && self.max.is_some_and(|v| v.to_f64() == f64::NEG_INFINITY) {
222 panic!(
223 "InvalidArgument: exclude_max=true with max_value=-inf leaves \
224 no {width}-bit floating-point values"
225 );
226 }
227
228 let allow_nan = self.allow_nan.unwrap_or(!has_min && !has_max);
229 let allow_infinity = self.allow_infinity.unwrap_or(!has_min || !has_max);
230
231 if allow_nan && (has_min || has_max) {
232 panic!("Cannot have allow_nan=true with min_value or max_value");
233 }
234 if allow_infinity && has_min && has_max {
235 panic!("Cannot have allow_infinity=true with both min_value and max_value");
236 }
237
238 let mut schema = cbor_map! {
239 "type" => "float",
240 "exclude_min" => self.exclude_min,
241 "exclude_max" => self.exclude_max,
242 "allow_nan" => allow_nan,
243 "allow_infinity" => allow_infinity,
244 "width" => width
245 };
246
247 if let Some(ref min) = self.min {
248 map_insert(&mut schema, "min_value", cbor_serialize(min));
249 }
250 if let Some(ref max) = self.max {
251 map_insert(&mut schema, "max_value", cbor_serialize(max));
252 }
253
254 if !allow_nan && !allow_infinity {
258 if self.min.is_none() {
259 map_insert(&mut schema, "min_value", cbor_serialize(&T::MIN));
260 }
261 if self.max.is_none() {
262 map_insert(&mut schema, "max_value", cbor_serialize(&T::MAX));
263 }
264 }
265
266 schema
267 }
268}
269
270impl<T: Float + serde::de::DeserializeOwned + serde::Serialize + Send + Sync + 'static> Generator<T>
271 for FloatGenerator<T>
272{
273 fn as_basic(&self) -> Option<BasicGenerator<'_, T>> {
274 Some(BasicGenerator::new(self.build_schema(), |raw| {
275 super::deserialize_value(raw)
276 }))
277 }
278}
279
280pub fn floats<T: Float + serde::de::DeserializeOwned + serde::Serialize + Send + Sync + 'static>()
299-> FloatGenerator<T> {
300 FloatGenerator {
301 min: None,
302 max: None,
303 exclude_min: false,
304 exclude_max: false,
305 allow_nan: None,
306 allow_infinity: None,
307 }
308}
309
310#[cfg(test)]
311#[path = "../../tests/embedded/generators/numeric_tests.rs"]
312mod tests;