1#![allow(clippy::must_use_candidate)]
2
3use serde_json::Number;
4
5use super::numeric;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub enum BoundOp {
9 Lt,
10 Lte,
11 Gt,
12 Gte,
13}
14
15impl BoundOp {
16 pub fn from_u8(value: u8) -> Option<Self> {
17 match value {
18 0 => Some(Self::Lt),
19 1 => Some(Self::Lte),
20 2 => Some(Self::Gt),
21 3 => Some(Self::Gte),
22 _ => None,
23 }
24 }
25}
26
27#[derive(Clone, Debug, PartialEq)]
30pub enum CompiledBound {
31 F64 {
32 op: BoundOp,
33 limit: f64,
34 },
35 #[cfg(not(feature = "arbitrary-precision"))]
36 I64 {
37 op: BoundOp,
38 limit: i64,
39 },
40 #[cfg(not(feature = "arbitrary-precision"))]
41 U64 {
42 op: BoundOp,
43 limit: u64,
44 },
45 #[cfg(feature = "arbitrary-precision")]
46 BigInt {
47 op: BoundOp,
48 limit: num_bigint::BigInt,
49 },
50 #[cfg(feature = "arbitrary-precision")]
51 BigFrac {
52 op: BoundOp,
53 limit: fraction::BigFraction,
54 },
55}
56
57#[derive(Clone, Debug, PartialEq)]
60pub enum CompiledMultipleOf {
61 #[cfg(feature = "arbitrary-precision")]
62 BigInt(num_bigint::BigInt),
63 #[cfg(feature = "arbitrary-precision")]
64 BigFrac(fraction::BigFraction),
65 Unsupported,
66}
67
68#[inline]
69fn check_primitive_bound<T>(op: BoundOp, value: &Number, limit: T) -> bool
70where
71 T: Copy + num_traits::ToPrimitive,
72 u64: num_cmp::NumCmp<T>,
73 i64: num_cmp::NumCmp<T>,
74 f64: num_cmp::NumCmp<T>,
75{
76 match op {
77 BoundOp::Lt => numeric::lt(value, limit),
78 BoundOp::Lte => numeric::le(value, limit),
79 BoundOp::Gt => numeric::gt(value, limit),
80 BoundOp::Gte => numeric::ge(value, limit),
81 }
82}
83
84#[cfg(feature = "arbitrary-precision")]
85#[inline]
86fn infinity_cmp(op: BoundOp, is_negative: bool) -> bool {
87 match op {
88 BoundOp::Gte | BoundOp::Gt => !is_negative,
89 BoundOp::Lte | BoundOp::Lt => is_negative,
90 }
91}
92
93#[cfg(feature = "arbitrary-precision")]
94#[inline]
95fn check_bigint_bound(op: BoundOp, limit: &num_bigint::BigInt, value: &Number) -> bool {
96 use fraction::BigFraction;
97
98 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
99 return match op {
100 BoundOp::Lt => instance_bigint < *limit,
101 BoundOp::Lte => instance_bigint <= *limit,
102 BoundOp::Gt => instance_bigint > *limit,
103 BoundOp::Gte => instance_bigint >= *limit,
104 };
105 }
106
107 if let Some(v) = value.as_u64() {
108 return match op {
109 BoundOp::Lt => numeric::bignum::u64_lt_bigint(v, limit),
110 BoundOp::Lte => numeric::bignum::u64_le_bigint(v, limit),
111 BoundOp::Gt => numeric::bignum::u64_gt_bigint(v, limit),
112 BoundOp::Gte => numeric::bignum::u64_ge_bigint(v, limit),
113 };
114 }
115
116 if let Some(v) = value.as_i64() {
117 return match op {
118 BoundOp::Lt => numeric::bignum::i64_lt_bigint(v, limit),
119 BoundOp::Lte => numeric::bignum::i64_le_bigint(v, limit),
120 BoundOp::Gt => numeric::bignum::i64_gt_bigint(v, limit),
121 BoundOp::Gte => numeric::bignum::i64_ge_bigint(v, limit),
122 };
123 }
124
125 if let Some(v) = value.as_f64() {
126 return match op {
127 BoundOp::Lt => numeric::bignum::f64_lt_bigint(v, limit),
128 BoundOp::Lte => numeric::bignum::f64_le_bigint(v, limit),
129 BoundOp::Gt => numeric::bignum::f64_gt_bigint(v, limit),
130 BoundOp::Gte => numeric::bignum::f64_ge_bigint(v, limit),
131 };
132 }
133
134 if let Some(instance_bigfrac) = numeric::bignum::try_parse_bigfraction(value) {
135 let limit_frac = BigFraction::from(limit.clone());
136 return match op {
137 BoundOp::Lt => instance_bigfrac < limit_frac,
138 BoundOp::Lte => instance_bigfrac <= limit_frac,
139 BoundOp::Gt => instance_bigfrac > limit_frac,
140 BoundOp::Gte => instance_bigfrac >= limit_frac,
141 };
142 }
143
144 infinity_cmp(op, value.as_str().starts_with('-'))
145}
146
147#[cfg(feature = "arbitrary-precision")]
148#[inline]
149fn check_bigfrac_bound(op: BoundOp, limit: &fraction::BigFraction, value: &Number) -> bool {
150 if let Some(instance_bigfrac) = numeric::bignum::try_parse_bigfraction(value) {
151 return match op {
152 BoundOp::Lt => instance_bigfrac < *limit,
153 BoundOp::Lte => instance_bigfrac <= *limit,
154 BoundOp::Gt => instance_bigfrac > *limit,
155 BoundOp::Gte => instance_bigfrac >= *limit,
156 };
157 }
158
159 if let Some(v) = value.as_u64() {
160 return match op {
161 BoundOp::Lt => numeric::bignum::u64_lt_bigfrac(v, limit),
162 BoundOp::Lte => numeric::bignum::u64_le_bigfrac(v, limit),
163 BoundOp::Gt => numeric::bignum::u64_gt_bigfrac(v, limit),
164 BoundOp::Gte => numeric::bignum::u64_ge_bigfrac(v, limit),
165 };
166 }
167
168 if let Some(v) = value.as_i64() {
169 return match op {
170 BoundOp::Lt => numeric::bignum::i64_lt_bigfrac(v, limit),
171 BoundOp::Lte => numeric::bignum::i64_le_bigfrac(v, limit),
172 BoundOp::Gt => numeric::bignum::i64_gt_bigfrac(v, limit),
173 BoundOp::Gte => numeric::bignum::i64_ge_bigfrac(v, limit),
174 };
175 }
176
177 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
180 let instance_frac = fraction::BigFraction::from(instance_bigint);
181 return match op {
182 BoundOp::Lt => instance_frac < *limit,
183 BoundOp::Lte => instance_frac <= *limit,
184 BoundOp::Gt => instance_frac > *limit,
185 BoundOp::Gte => instance_frac >= *limit,
186 };
187 }
188
189 if let Some(v) = value.as_f64() {
190 return match op {
191 BoundOp::Lt => numeric::bignum::f64_lt_bigfrac(v, limit),
192 BoundOp::Lte => numeric::bignum::f64_le_bigfrac(v, limit),
193 BoundOp::Gt => numeric::bignum::f64_gt_bigfrac(v, limit),
194 BoundOp::Gte => numeric::bignum::f64_ge_bigfrac(v, limit),
195 };
196 }
197
198 true
201}
202
203pub fn compile_bound(op: BoundOp, limit: &Number) -> CompiledBound {
204 #[cfg(feature = "arbitrary-precision")]
205 {
206 if let Some(value) = numeric::bignum::try_parse_bigint(limit) {
207 return CompiledBound::BigInt { op, limit: value };
208 }
209 if let Some(value) = numeric::bignum::try_parse_bigfraction(limit) {
210 return CompiledBound::BigFrac { op, limit: value };
211 }
212 }
213
214 #[cfg(not(feature = "arbitrary-precision"))]
215 {
216 if let Some(value) = limit.as_i64() {
217 return CompiledBound::I64 { op, limit: value };
218 }
219 if let Some(value) = limit.as_u64() {
220 return CompiledBound::U64 { op, limit: value };
221 }
222 }
223
224 if let Some(value) = limit.as_f64() {
225 return CompiledBound::F64 { op, limit: value };
226 }
227
228 #[cfg(feature = "arbitrary-precision")]
229 {
230 let limit = if limit.as_str().starts_with('-') {
231 f64::NEG_INFINITY
232 } else {
233 f64::INFINITY
234 };
235 CompiledBound::F64 { op, limit }
236 }
237
238 #[cfg(not(feature = "arbitrary-precision"))]
239 {
240 unreachable!("non-arbitrary-precision serde_json::Number always has an f64 representation");
241 }
242}
243
244pub fn check_bound(compiled: &CompiledBound, value: &Number) -> bool {
245 match compiled {
246 CompiledBound::F64 { op, limit } => check_primitive_bound(*op, value, *limit),
247 #[cfg(not(feature = "arbitrary-precision"))]
248 CompiledBound::I64 { op, limit } => check_primitive_bound(*op, value, *limit),
249 #[cfg(not(feature = "arbitrary-precision"))]
250 CompiledBound::U64 { op, limit } => check_primitive_bound(*op, value, *limit),
251 #[cfg(feature = "arbitrary-precision")]
252 CompiledBound::BigInt { op, limit } => check_bigint_bound(*op, limit, value),
253 #[cfg(feature = "arbitrary-precision")]
254 CompiledBound::BigFrac { op, limit } => check_bigfrac_bound(*op, limit, value),
255 }
256}
257
258#[cfg(feature = "arbitrary-precision")]
259pub fn compile_multiple_of(multiple_of: &Number) -> CompiledMultipleOf {
260 #[cfg(feature = "arbitrary-precision")]
261 {
262 if let Some(value) = numeric::bignum::try_parse_bigint(multiple_of) {
263 return CompiledMultipleOf::BigInt(value);
264 }
265 if let Some(value) = numeric::bignum::try_parse_bigfraction(multiple_of) {
266 return CompiledMultipleOf::BigFrac(value);
267 }
268 }
269
270 CompiledMultipleOf::Unsupported
271}
272
273#[cfg(feature = "arbitrary-precision")]
274pub fn check_multiple_of(compiled: &CompiledMultipleOf, value: &Number) -> bool {
275 match compiled {
276 #[cfg(feature = "arbitrary-precision")]
277 CompiledMultipleOf::BigInt(multiple) => {
278 use num_bigint::BigInt;
279
280 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
281 return numeric::bignum::is_multiple_of_bigint(&instance_bigint, multiple);
282 }
283
284 if let Some(v) = value.as_u64() {
285 let v_bigint = BigInt::from(v);
286 return numeric::bignum::is_multiple_of_bigint(&v_bigint, multiple);
287 }
288
289 if let Some(v) = value.as_i64() {
290 let v_bigint = BigInt::from(v);
291 return numeric::bignum::is_multiple_of_bigint(&v_bigint, multiple);
292 }
293
294 false
295 }
296 #[cfg(feature = "arbitrary-precision")]
297 CompiledMultipleOf::BigFrac(multiple) => {
298 use num_traits::ToPrimitive;
299
300 if let Some(instance_bigfrac) = numeric::bignum::try_parse_bigfraction(value) {
301 return numeric::bignum::is_multiple_of_bigfrac(&instance_bigfrac, multiple);
302 }
303
304 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
305 let value_frac = fraction::BigFraction::from(instance_bigint);
306 return numeric::bignum::is_multiple_of_bigfrac(&value_frac, multiple);
307 }
308
309 if let Some(v) = value.as_u64() {
310 let value_frac = fraction::BigFraction::from(v);
311 return numeric::bignum::is_multiple_of_bigfrac(&value_frac, multiple);
312 }
313
314 if let Some(v) = value.as_i64() {
315 let value_frac = fraction::BigFraction::from(v);
316 return numeric::bignum::is_multiple_of_bigfrac(&value_frac, multiple);
317 }
318
319 let multiple_f64 = multiple.to_f64().unwrap_or(f64::INFINITY);
320 numeric::is_multiple_of_float(value, multiple_f64)
321 }
322 CompiledMultipleOf::Unsupported => true,
323 }
324}
325
326#[derive(Clone, Copy, Debug, PartialEq, Eq)]
329pub enum DivisorKind {
330 Whole,
332 WholeLossy,
334 Fractional,
336}
337
338pub fn divisor_kind(divisor: &Number) -> DivisorKind {
340 #[cfg(feature = "arbitrary-precision")]
341 {
342 if numeric::bignum::try_parse_bigint(divisor).is_some() {
343 return DivisorKind::Whole;
344 }
345 if numeric::bignum::try_parse_bigfraction(divisor).is_some() {
346 return DivisorKind::Fractional;
347 }
348 }
349 match divisor.as_f64() {
350 Some(value) if value.fract() != 0. => DivisorKind::Fractional,
351 Some(value) if value.abs() <= MAX_SAFE_INTEGER_F64 => DivisorKind::Whole,
353 _ => DivisorKind::WholeLossy,
356 }
357}
358
359const MAX_SAFE_INTEGER_F64: f64 = 9_007_199_254_740_992.0;
360
361pub fn satisfies_multiple_of(divisor: &Number, value: &Number) -> bool {
363 #[cfg(feature = "arbitrary-precision")]
364 {
365 let compiled = compile_multiple_of(divisor);
366 if compiled != CompiledMultipleOf::Unsupported {
367 return check_multiple_of(&compiled, value);
368 }
369 }
370 divisor.as_f64().is_none_or(|limit| {
372 if limit.fract() == 0. {
373 numeric::is_multiple_of_integer(value, limit)
374 } else {
375 numeric::is_multiple_of_float(value, limit)
376 }
377 })
378}