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