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 #[cfg(feature = "arbitrary-precision")]
57 Decimal {
58 op: BoundOp,
59 limit: numeric::bignum::Decimal,
60 },
61}
62
63#[derive(Clone, Debug, PartialEq)]
66pub enum CompiledMultipleOf {
67 #[cfg(feature = "arbitrary-precision")]
68 BigInt(num_bigint::BigInt),
69 #[cfg(feature = "arbitrary-precision")]
70 BigFrac(fraction::BigFraction),
71 #[cfg(feature = "arbitrary-precision")]
72 Decimal(numeric::bignum::Decimal),
73 Unsupported,
74}
75
76#[inline]
77fn check_primitive_bound<T>(op: BoundOp, value: &Number, limit: T) -> bool
78where
79 T: Copy + num_traits::ToPrimitive,
80 u64: num_cmp::NumCmp<T>,
81 i64: num_cmp::NumCmp<T>,
82 f64: num_cmp::NumCmp<T>,
83{
84 match op {
85 BoundOp::Lt => numeric::lt(value, limit),
86 BoundOp::Lte => numeric::le(value, limit),
87 BoundOp::Gt => numeric::gt(value, limit),
88 BoundOp::Gte => numeric::ge(value, limit),
89 }
90}
91
92#[cfg(feature = "arbitrary-precision")]
93#[inline]
94fn infinity_cmp(op: BoundOp, is_negative: bool) -> bool {
95 match op {
96 BoundOp::Gte | BoundOp::Gt => !is_negative,
97 BoundOp::Lte | BoundOp::Lt => is_negative,
98 }
99}
100
101#[cfg(feature = "arbitrary-precision")]
102fn holds(op: BoundOp, ordering: std::cmp::Ordering) -> bool {
103 match op {
104 BoundOp::Lt => ordering.is_lt(),
105 BoundOp::Lte => ordering.is_le(),
106 BoundOp::Gt => ordering.is_gt(),
107 BoundOp::Gte => ordering.is_ge(),
108 }
109}
110
111#[cfg(feature = "arbitrary-precision")]
112#[inline]
113fn check_bigint_bound(op: BoundOp, limit: &num_bigint::BigInt, value: &Number) -> bool {
114 if let Some(instance) = numeric::bignum::past_digit_budget(value.as_str()) {
115 return holds(
116 op,
117 instance.cmp(&numeric::bignum::Decimal::of_integer(limit)),
118 );
119 }
120 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
121 return match op {
122 BoundOp::Lt => instance_bigint < *limit,
123 BoundOp::Lte => instance_bigint <= *limit,
124 BoundOp::Gt => instance_bigint > *limit,
125 BoundOp::Gte => instance_bigint >= *limit,
126 };
127 }
128
129 if let Some(v) = value.as_u64() {
130 return match op {
131 BoundOp::Lt => numeric::bignum::u64_lt_bigint(v, limit),
132 BoundOp::Lte => numeric::bignum::u64_le_bigint(v, limit),
133 BoundOp::Gt => numeric::bignum::u64_gt_bigint(v, limit),
134 BoundOp::Gte => numeric::bignum::u64_ge_bigint(v, limit),
135 };
136 }
137
138 if let Some(v) = value.as_i64() {
139 return match op {
140 BoundOp::Lt => numeric::bignum::i64_lt_bigint(v, limit),
141 BoundOp::Lte => numeric::bignum::i64_le_bigint(v, limit),
142 BoundOp::Gt => numeric::bignum::i64_gt_bigint(v, limit),
143 BoundOp::Gte => numeric::bignum::i64_ge_bigint(v, limit),
144 };
145 }
146
147 if let Some(v) = value.as_f64() {
148 return match op {
149 BoundOp::Lt => numeric::bignum::f64_lt_bigint(v, limit),
150 BoundOp::Lte => numeric::bignum::f64_le_bigint(v, limit),
151 BoundOp::Gt => numeric::bignum::f64_gt_bigint(v, limit),
152 BoundOp::Gte => numeric::bignum::f64_ge_bigint(v, limit),
153 };
154 }
155
156 if let Some(instance_bigfrac) = numeric::bignum::try_parse_bigfraction(value) {
157 let limit_frac = numeric::bignum::whole(limit.clone());
158 return match op {
159 BoundOp::Lt => instance_bigfrac < limit_frac,
160 BoundOp::Lte => instance_bigfrac <= limit_frac,
161 BoundOp::Gt => instance_bigfrac > limit_frac,
162 BoundOp::Gte => instance_bigfrac >= limit_frac,
163 };
164 }
165
166 infinity_cmp(op, value.as_str().starts_with('-'))
167}
168
169#[cfg(feature = "arbitrary-precision")]
170#[inline]
171fn check_bigfrac_bound(op: BoundOp, limit: &fraction::BigFraction, value: &Number) -> bool {
172 if let Some(instance) = numeric::bignum::past_digit_budget(value.as_str()) {
173 if let Some(limit) = numeric::bignum::Decimal::of_fraction(limit) {
174 return holds(op, instance.cmp(&limit));
175 }
176 }
177 if let Some(instance_bigfrac) = numeric::bignum::try_parse_bigfraction(value) {
178 return match op {
179 BoundOp::Lt => instance_bigfrac < *limit,
180 BoundOp::Lte => instance_bigfrac <= *limit,
181 BoundOp::Gt => instance_bigfrac > *limit,
182 BoundOp::Gte => instance_bigfrac >= *limit,
183 };
184 }
185
186 if let Some(v) = value.as_u64() {
187 return match op {
188 BoundOp::Lt => numeric::bignum::u64_lt_bigfrac(v, limit),
189 BoundOp::Lte => numeric::bignum::u64_le_bigfrac(v, limit),
190 BoundOp::Gt => numeric::bignum::u64_gt_bigfrac(v, limit),
191 BoundOp::Gte => numeric::bignum::u64_ge_bigfrac(v, limit),
192 };
193 }
194
195 if let Some(v) = value.as_i64() {
196 return match op {
197 BoundOp::Lt => numeric::bignum::i64_lt_bigfrac(v, limit),
198 BoundOp::Lte => numeric::bignum::i64_le_bigfrac(v, limit),
199 BoundOp::Gt => numeric::bignum::i64_gt_bigfrac(v, limit),
200 BoundOp::Gte => numeric::bignum::i64_ge_bigfrac(v, limit),
201 };
202 }
203
204 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
207 let instance_frac = numeric::bignum::whole(instance_bigint);
208 return match op {
209 BoundOp::Lt => instance_frac < *limit,
210 BoundOp::Lte => instance_frac <= *limit,
211 BoundOp::Gt => instance_frac > *limit,
212 BoundOp::Gte => instance_frac >= *limit,
213 };
214 }
215
216 if let Some(v) = value.as_f64() {
217 return match op {
218 BoundOp::Lt => numeric::bignum::f64_lt_bigfrac(v, limit),
219 BoundOp::Lte => numeric::bignum::f64_le_bigfrac(v, limit),
220 BoundOp::Gt => numeric::bignum::f64_gt_bigfrac(v, limit),
221 BoundOp::Gte => numeric::bignum::f64_ge_bigfrac(v, limit),
222 };
223 }
224
225 true
228}
229
230pub fn compile_bound(op: BoundOp, limit: &Number) -> CompiledBound {
231 #[cfg(feature = "arbitrary-precision")]
232 {
233 if let Some(limit) = numeric::bignum::past_digit_budget(limit.as_str()) {
234 return CompiledBound::Decimal { op, limit };
235 }
236 if let Some(value) = numeric::bignum::try_parse_bigint(limit) {
237 return CompiledBound::BigInt { op, limit: value };
238 }
239 if let Some(value) = numeric::bignum::try_parse_bigfraction(limit) {
240 return CompiledBound::BigFrac { op, limit: value };
241 }
242 }
243
244 #[cfg(not(feature = "arbitrary-precision"))]
245 {
246 if let Some(value) = limit.as_i64() {
247 return CompiledBound::I64 { op, limit: value };
248 }
249 if let Some(value) = limit.as_u64() {
250 return CompiledBound::U64 { op, limit: value };
251 }
252 }
253
254 if let Some(value) = limit.as_f64() {
255 return CompiledBound::F64 { op, limit: value };
256 }
257
258 let limit = if crate::JsonNumber::as_str(limit).starts_with('-') {
261 f64::NEG_INFINITY
262 } else {
263 f64::INFINITY
264 };
265 CompiledBound::F64 { op, limit }
266}
267
268pub fn check_bound(compiled: &CompiledBound, value: &Number) -> bool {
269 match compiled {
270 CompiledBound::F64 { op, limit } => check_primitive_bound(*op, value, *limit),
271 #[cfg(not(feature = "arbitrary-precision"))]
272 CompiledBound::I64 { op, limit } => check_primitive_bound(*op, value, *limit),
273 #[cfg(not(feature = "arbitrary-precision"))]
274 CompiledBound::U64 { op, limit } => check_primitive_bound(*op, value, *limit),
275 #[cfg(feature = "arbitrary-precision")]
276 CompiledBound::BigInt { op, limit } => check_bigint_bound(*op, limit, value),
277 #[cfg(feature = "arbitrary-precision")]
278 CompiledBound::BigFrac { op, limit } => check_bigfrac_bound(*op, limit, value),
279 #[cfg(feature = "arbitrary-precision")]
280 CompiledBound::Decimal { op, limit } => holds(
281 *op,
282 numeric::bignum::Decimal::parse(value.as_str()).cmp(limit),
283 ),
284 }
285}
286
287#[cfg(feature = "arbitrary-precision")]
288pub fn compile_multiple_of(multiple_of: &Number) -> CompiledMultipleOf {
289 #[cfg(feature = "arbitrary-precision")]
290 {
291 if let Some(value) = numeric::bignum::past_digit_budget(multiple_of.as_str()) {
292 return CompiledMultipleOf::Decimal(value);
293 }
294 if let Some(value) = numeric::bignum::try_parse_bigint(multiple_of) {
295 return CompiledMultipleOf::BigInt(value);
296 }
297 if let Some(value) = numeric::bignum::try_parse_bigfraction(multiple_of) {
298 return CompiledMultipleOf::BigFrac(value);
299 }
300 }
301
302 CompiledMultipleOf::Unsupported
303}
304
305#[cfg(feature = "arbitrary-precision")]
306pub fn check_multiple_of(compiled: &CompiledMultipleOf, value: &Number) -> bool {
307 use numeric::bignum::Decimal;
308
309 if let Some(instance) = numeric::bignum::past_digit_budget(value.as_str()) {
310 let multiple = match compiled {
311 CompiledMultipleOf::BigInt(multiple) => Some(Decimal::of_integer(multiple)),
312 CompiledMultipleOf::BigFrac(multiple) => Decimal::of_fraction(multiple),
313 CompiledMultipleOf::Decimal(multiple) => Some(multiple.clone()),
314 CompiledMultipleOf::Unsupported => return true,
315 };
316 if let Some(multiple) = multiple {
317 return instance.is_multiple_of(&multiple);
318 }
319 }
320 match compiled {
321 #[cfg(feature = "arbitrary-precision")]
322 CompiledMultipleOf::BigInt(multiple) => {
323 use num_bigint::BigInt;
324
325 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
326 return numeric::bignum::is_multiple_of_bigint(&instance_bigint, multiple);
327 }
328
329 if let Some(v) = value.as_u64() {
330 let v_bigint = BigInt::from(v);
331 return numeric::bignum::is_multiple_of_bigint(&v_bigint, multiple);
332 }
333
334 if let Some(v) = value.as_i64() {
335 let v_bigint = BigInt::from(v);
336 return numeric::bignum::is_multiple_of_bigint(&v_bigint, multiple);
337 }
338
339 false
340 }
341 #[cfg(feature = "arbitrary-precision")]
342 CompiledMultipleOf::BigFrac(multiple) => {
343 use num_traits::ToPrimitive;
344
345 if let Some(instance_bigfrac) = numeric::bignum::try_parse_bigfraction(value) {
346 return numeric::bignum::is_multiple_of_bigfrac(&instance_bigfrac, multiple);
347 }
348
349 if let Some(instance_bigint) = numeric::bignum::try_parse_bigint(value) {
350 let value_frac = numeric::bignum::whole(instance_bigint);
351 return numeric::bignum::is_multiple_of_bigfrac(&value_frac, multiple);
352 }
353
354 if let Some(v) = value.as_u64() {
355 let value_frac = fraction::BigFraction::from(v);
356 return numeric::bignum::is_multiple_of_bigfrac(&value_frac, multiple);
357 }
358
359 if let Some(v) = value.as_i64() {
360 let value_frac = fraction::BigFraction::from(v);
361 return numeric::bignum::is_multiple_of_bigfrac(&value_frac, multiple);
362 }
363
364 let multiple_f64 = multiple.to_f64().unwrap_or(f64::INFINITY);
365 numeric::is_multiple_of_float(value, multiple_f64)
366 }
367 CompiledMultipleOf::Decimal(multiple) => {
368 Decimal::parse(value.as_str()).is_multiple_of(multiple)
369 }
370 CompiledMultipleOf::Unsupported => true,
371 }
372}
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq)]
377pub enum DivisorKind {
378 Whole,
380 WholeLossy,
382 Fractional,
384}
385
386pub fn divisor_kind(divisor: &Number) -> DivisorKind {
388 #[cfg(feature = "arbitrary-precision")]
389 {
390 if numeric::bignum::exceeds_digit_budget(divisor.as_str()) {
391 return DivisorKind::Fractional;
392 }
393 if numeric::bignum::try_parse_bigint(divisor).is_some() {
394 return DivisorKind::Whole;
395 }
396 if numeric::bignum::try_parse_bigfraction(divisor).is_some() {
397 return DivisorKind::Fractional;
398 }
399 }
400 match divisor.as_f64() {
401 Some(value) if value.fract() != 0. => DivisorKind::Fractional,
402 Some(value) if value.abs() <= MAX_SAFE_INTEGER_F64 => DivisorKind::Whole,
404 _ => DivisorKind::WholeLossy,
407 }
408}
409
410const MAX_SAFE_INTEGER_F64: f64 = 9_007_199_254_740_992.0;
411
412pub fn satisfies_multiple_of(divisor: &Number, value: &Number) -> bool {
414 #[cfg(feature = "arbitrary-precision")]
415 {
416 let compiled = compile_multiple_of(divisor);
417 if compiled != CompiledMultipleOf::Unsupported {
418 return check_multiple_of(&compiled, value);
419 }
420 }
421 divisor.as_f64().is_none_or(|limit| {
423 if limit.fract() == 0. {
424 numeric::is_multiple_of_integer(value, limit)
425 } else {
426 numeric::is_multiple_of_float(value, limit)
427 }
428 })
429}