1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_possible_wrap,
4 clippy::cast_sign_loss,
5 clippy::cast_precision_loss,
6 clippy::float_cmp,
7 clippy::must_use_candidate
8)]
9
10use fraction::{BigFraction, One, Zero};
11#[cfg(feature = "arbitrary-precision")]
12use std::cmp::Ordering;
13
14macro_rules! define_num_cmp {
15 ($($trait_fn:ident => $fn_name:ident, $op:tt, $infinity_positive:literal, $ord_pat:pat),* $(,)?) => {
16 $(
17 pub fn $fn_name<N, T>(value: &N, limit: T) -> bool
18 where
19 N: crate::JsonNumber,
20 T: Copy + num_traits::ToPrimitive,
21 u64: num_cmp::NumCmp<T>,
22 i64: num_cmp::NumCmp<T>,
23 f64: num_cmp::NumCmp<T>,
24 {
25 if let Some(v) = value.as_u64() {
26 num_cmp::NumCmp::$trait_fn(v, limit)
27 } else if let Some(v) = value.as_i64() {
28 num_cmp::NumCmp::$trait_fn(v, limit)
29 } else if let Some(v) = value.as_f64() {
30 #[cfg(feature = "arbitrary-precision")]
33 if v <= i64::MIN as f64 || v >= u64::MAX as f64 {
34 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
35 if let Some(ordering) = bignum::compare_bigint_to_limit(&big_value, limit) {
36 return matches!(ordering, $ord_pat);
37 }
38 }
39 }
40 num_cmp::NumCmp::$trait_fn(v, limit)
41 } else {
42 #[cfg(feature = "arbitrary-precision")]
43 {
44 if let Some(big_value) = bignum::try_parse_bigfraction(&value.to_number()) {
45 if let Some(limit_f64) = num_traits::ToPrimitive::to_f64(&limit) {
46 let limit_frac = BigFraction::from(limit_f64);
47 return big_value $op limit_frac;
48 }
49 }
50 let is_negative = value.as_str().starts_with('-');
52 if $infinity_positive {
53 !is_negative
54 } else {
55 is_negative
56 }
57 }
58 #[cfg(not(feature = "arbitrary-precision"))]
59 {
60 unreachable!("Always Some without `arbitrary-precision`")
61 }
62 }
63 }
64 )*
65 };
66}
67
68define_num_cmp!(
69 num_ge => ge, >=, true, Ordering::Greater | Ordering::Equal, num_le => le, <=, false, Ordering::Less | Ordering::Equal, num_gt => gt, >, true, Ordering::Greater,
72 num_lt => lt, <, false, Ordering::Less,
73);
74
75#[cfg(feature = "macros")]
76pub fn eq<N, T>(value: &N, limit: T) -> bool
77where
78 N: crate::JsonNumber,
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 if let Some(v) = value.as_u64() {
85 num_cmp::NumCmp::num_eq(v, limit)
86 } else if let Some(v) = value.as_i64() {
87 num_cmp::NumCmp::num_eq(v, limit)
88 } else if let Some(v) = value.as_f64() {
89 num_cmp::NumCmp::num_eq(v, limit)
90 } else {
91 #[cfg(feature = "arbitrary-precision")]
92 {
93 if let Some(big_value) = bignum::try_parse_bigfraction(&value.to_number()) {
94 if let Some(limit_f64) = num_traits::ToPrimitive::to_f64(&limit) {
95 return big_value == BigFraction::from(limit_f64);
96 }
97 }
98 false
99 }
100 #[cfg(not(feature = "arbitrary-precision"))]
101 {
102 unreachable!("Always Some without `arbitrary-precision`")
103 }
104 }
105}
106
107pub fn is_multiple_of_float<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
108 if let Some(value_f64) = value.as_f64() {
109 if value_f64.is_zero() {
112 return true;
113 }
114 if value_f64.abs() < multiple {
115 return false;
116 }
117 (BigFraction::from(value_f64) / BigFraction::from(multiple))
125 .denom()
126 .is_none_or(One::is_one)
127 } else {
128 false
131 }
132}
133
134const MAX_SAFE_INTEGER: u64 = 1u64 << 53;
137
138pub fn is_multiple_of_integer<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
139 let divisor_ok =
145 multiple > 0.0 && multiple <= MAX_SAFE_INTEGER as f64 && multiple.fract() == 0.0;
146 if divisor_ok {
147 if let Some(v) = value.as_u64() {
148 return (v % (multiple as u64)) == 0;
149 }
150 if let Some(v) = value.as_i64() {
151 return (v % (multiple as i64)) == 0;
152 }
153 #[cfg(feature = "arbitrary-precision")]
156 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
157 let divisor = num_bigint::BigInt::from(multiple as i64);
158 return bignum::is_multiple_of_bigint(&big_value, &divisor);
159 }
160 }
161
162 if let Some(value_f64) = value.as_f64() {
163 value_f64.fract() == 0. && (value_f64 % multiple) == 0.
166 } else {
167 #[cfg(feature = "arbitrary-precision")]
169 {
170 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
172 use num_bigint::BigInt;
173 let multiple_int = BigInt::from(multiple as i64);
183 return bignum::is_multiple_of_bigint(&big_value, &multiple_int);
184 }
185 false
187 }
188 #[cfg(not(feature = "arbitrary-precision"))]
189 {
190 unreachable!("Always Some without `arbitrary-precision`")
191 }
192 }
193}
194
195#[cfg(feature = "arbitrary-precision")]
196pub mod bignum {
197 use fraction::BigFraction;
198 use num_bigint::BigInt;
199 use num_traits::{ToPrimitive, Zero};
200 use serde_json::Number;
201 use std::str::FromStr;
202
203 const MAX_EXPONENT_ADJUSTMENT: u32 = 1_000_000;
212
213 #[derive(Debug, Clone)]
214 struct DecimalComponents {
215 negative: bool,
216 digits: String,
217 fraction_digits: usize,
218 exponent: i64,
219 }
220
221 impl DecimalComponents {
222 fn parse(num_str: &str) -> Option<Self> {
223 let bytes = num_str.as_bytes();
224 if bytes.is_empty() {
225 return None;
226 }
227
228 let mut idx = 0;
229 let negative = if bytes[idx] == b'-' {
230 idx += 1;
231 true
232 } else {
233 false
234 };
235
236 if idx >= bytes.len() {
237 return None;
238 }
239
240 let mut digits = String::with_capacity(bytes.len());
241 let int_start = idx;
242 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
243 idx += 1;
244 }
245 if int_start == idx {
246 return None;
247 }
248 digits.push_str(&num_str[int_start..idx]);
249
250 let mut fraction_digits = 0usize;
251 if idx < bytes.len() && bytes[idx] == b'.' {
252 idx += 1;
253 let frac_start = idx;
254 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
255 idx += 1;
256 }
257 if frac_start == idx {
258 return None;
259 }
260 digits.push_str(&num_str[frac_start..idx]);
261 fraction_digits = idx - frac_start;
262 }
263
264 let mut exponent: i64 = 0;
265 if idx < bytes.len() && (bytes[idx] == b'e' || bytes[idx] == b'E') {
266 idx += 1;
267 if idx >= bytes.len() {
268 return None;
269 }
270 let mut exp_sign: i64 = 1;
271 if bytes[idx] == b'+' {
272 idx += 1;
273 } else if bytes[idx] == b'-' {
274 exp_sign = -1;
275 idx += 1;
276 }
277 let exp_start = idx;
278 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
279 idx += 1;
280 }
281 if exp_start == idx {
282 return None;
283 }
284 let exp_value = num_str[exp_start..idx].parse::<i64>().ok()?;
285 exponent = exp_value.checked_mul(exp_sign)?;
286 }
287
288 if idx != bytes.len() {
289 return None;
290 }
291
292 Some(Self {
293 negative,
294 digits,
295 fraction_digits,
296 exponent,
297 })
298 }
299
300 #[inline]
301 fn decimal_shift(&self) -> i64 {
302 self.exponent - self.fraction_digits as i64
303 }
304 }
305
306 fn digits_are_zero(s: &str) -> bool {
307 s.bytes().all(|b| b == b'0')
308 }
309
310 fn trailing_zero_count(s: &str) -> usize {
311 s.as_bytes()
312 .iter()
313 .rev()
314 .take_while(|b| **b == b'0')
315 .count()
316 }
317
318 fn append_zeros(target: &mut String, count: usize) -> Option<()> {
319 let new_len = target.len().checked_add(count)?;
320 target.reserve(count);
321 target.extend(std::iter::repeat_n('0', count));
322 debug_assert_eq!(target.len(), new_len);
323 Some(())
324 }
325
326 fn pow10_bigint(exp: usize) -> Option<BigInt> {
327 if exp == 0 {
328 return Some(BigInt::from(1));
329 }
330 let exp_u32 = u32::try_from(exp).ok()?;
331 Some(BigInt::from(10).pow(exp_u32))
332 }
333
334 fn shift_exceeds_limit(shift: i64) -> bool {
335 if shift <= 0 {
336 return false;
337 }
338 shift as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT)
339 }
340
341 fn exponent_reduction_exceeds_limit(exponent: i64) -> bool {
342 if exponent >= 0 {
343 return false;
344 }
345 match exponent.checked_abs() {
346 Some(abs) => abs as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT),
347 None => true,
348 }
349 }
350
351 pub fn try_parse_bigint(num: &Number) -> Option<BigInt> {
354 use super::MAX_SAFE_INTEGER;
355
356 let num_str = num.as_str();
357
358 if let Some(v) = num.as_i64() {
363 if v.unsigned_abs() <= MAX_SAFE_INTEGER {
364 return None;
365 }
366 }
367
368 let has_fraction_or_exponent = num_str.bytes().any(|b| b == b'.' || b == b'e' || b == b'E');
369 if !has_fraction_or_exponent {
370 return BigInt::from_str(num_str).ok();
371 }
372
373 let mut components = DecimalComponents::parse(num_str)?;
374 let mut shift = components.decimal_shift();
375
376 if shift < 0 {
377 let needed = (-shift) as usize;
378 if digits_are_zero(&components.digits) {
379 components.digits.clear();
380 components.digits.push('0');
381 shift = 0;
382 } else {
383 if exponent_reduction_exceeds_limit(components.exponent) {
384 return None;
385 }
386 let zeros = trailing_zero_count(&components.digits);
387 if zeros < needed {
388 return None;
389 }
390 let new_len = components.digits.len() - needed;
391 components.digits.truncate(new_len);
392 shift = 0;
393 }
394 }
395
396 if shift > 0 {
397 if shift_exceeds_limit(shift) {
398 return None;
399 }
400 append_zeros(&mut components.digits, shift as usize)?;
401 }
402
403 let digits_trimmed = components.digits.trim_start_matches('0');
404 let digits_ref = if digits_trimmed.is_empty() {
405 "0"
406 } else {
407 digits_trimmed
408 };
409 let mut value = BigInt::from_str(digits_ref).ok()?;
410 if components.negative && !value.is_zero() {
411 value = -value;
412 }
413 Some(value)
414 }
415
416 pub fn try_parse_bigfraction(num: &Number) -> Option<BigFraction> {
426 if num.as_i64().is_some() {
428 return None;
429 }
430
431 let num_str = num.as_str();
432
433 let mut has_decimal_point = false;
435 let mut has_exponent = false;
436 for b in num_str.bytes() {
437 if b == b'.' {
438 has_decimal_point = true;
439 } else if b == b'e' || b == b'E' {
440 has_exponent = true;
441 break;
442 }
443 }
444
445 if !has_decimal_point && !has_exponent {
446 return None;
447 }
448
449 if !has_exponent {
450 return BigFraction::from_str(num_str).ok();
451 }
452
453 let components = DecimalComponents::parse(num_str)?;
454 let shift = components.decimal_shift();
455
456 if shift >= 0 {
458 return None;
459 }
460
461 if exponent_reduction_exceeds_limit(components.exponent) {
462 return None;
463 }
464
465 let denom_power = (-shift) as usize;
466 let denominator = pow10_bigint(denom_power)?;
467 let mut numerator = BigInt::from_str(&components.digits).ok()?;
468 if components.negative && !numerator.is_zero() {
469 numerator = -numerator;
470 }
471 Some(BigFraction::from(numerator) / BigFraction::from(denominator))
472 }
473
474 pub(crate) fn compare_bigint_to_limit<T>(big: &BigInt, limit: T) -> Option<std::cmp::Ordering>
480 where
481 T: Copy + ToPrimitive,
482 {
483 use std::cmp::Ordering;
484
485 let limit_f64 = limit.to_f64()?;
486 if limit_f64.fract() == 0.0 {
487 if let Some(limit_int) = limit.to_i64() {
489 return Some(big.cmp(&BigInt::from(limit_int)));
490 }
491 if let Some(limit_int) = limit.to_u64() {
492 return Some(big.cmp(&BigInt::from(limit_int)));
493 }
494 }
495 if limit_f64 == f64::INFINITY {
496 return Some(Ordering::Less);
497 }
498 if limit_f64 == f64::NEG_INFINITY {
499 return Some(Ordering::Greater);
500 }
501 None
502 }
503
504 macro_rules! define_bigint_cmp {
505 ($($fn_name:ident, $prim_type:ty, $to_prim:ident, $op:tt, $overflow_sign:expr);* $(;)?) => {
506 $(
507 pub fn $fn_name(bigint: &BigInt, value: $prim_type) -> bool {
508 if let Some(converted) = bigint.$to_prim() {
509 converted $op value
510 } else {
511 bigint.sign() == $overflow_sign
512 }
513 }
514 )*
515 };
516 }
517
518 define_bigint_cmp!(
519 bigint_ge_u64, u64, to_u64, >=, num_bigint::Sign::Plus;
520 bigint_le_u64, u64, to_u64, <=, num_bigint::Sign::Minus;
521 bigint_gt_u64, u64, to_u64, >, num_bigint::Sign::Plus;
522 bigint_lt_u64, u64, to_u64, <, num_bigint::Sign::Minus;
523 bigint_ge_i64, i64, to_i64, >=, num_bigint::Sign::Plus;
524 bigint_le_i64, i64, to_i64, <=, num_bigint::Sign::Minus;
525 bigint_gt_i64, i64, to_i64, >, num_bigint::Sign::Plus;
526 bigint_lt_i64, i64, to_i64, <, num_bigint::Sign::Minus;
527 bigint_ge_f64, f64, to_f64, >=, num_bigint::Sign::Plus;
528 bigint_le_f64, f64, to_f64, <=, num_bigint::Sign::Minus;
529 bigint_gt_f64, f64, to_f64, >, num_bigint::Sign::Plus;
530 bigint_lt_f64, f64, to_f64, <, num_bigint::Sign::Minus;
531 );
532
533 macro_rules! define_reverse_cmp {
535 ($($rev_ge:ident, $rev_le:ident, $rev_gt:ident, $rev_lt:ident, $prim_type:ty, $big_type:ty, $fwd_ge:ident, $fwd_le:ident, $fwd_gt:ident, $fwd_lt:ident);* $(;)?) => {
536 $(
537 pub fn $rev_ge(value: $prim_type, big: &$big_type) -> bool {
538 $fwd_le(big, value)
539 }
540
541 pub fn $rev_le(value: $prim_type, big: &$big_type) -> bool {
542 $fwd_ge(big, value)
543 }
544
545 pub fn $rev_gt(value: $prim_type, big: &$big_type) -> bool {
546 $fwd_lt(big, value)
547 }
548
549 pub fn $rev_lt(value: $prim_type, big: &$big_type) -> bool {
550 $fwd_gt(big, value)
551 }
552 )*
553 };
554 }
555
556 define_reverse_cmp!(
557 u64_ge_bigint, u64_le_bigint, u64_gt_bigint, u64_lt_bigint, u64, BigInt, bigint_ge_u64, bigint_le_u64, bigint_gt_u64, bigint_lt_u64;
558 i64_ge_bigint, i64_le_bigint, i64_gt_bigint, i64_lt_bigint, i64, BigInt, bigint_ge_i64, bigint_le_i64, bigint_gt_i64, bigint_lt_i64;
559 f64_ge_bigint, f64_le_bigint, f64_gt_bigint, f64_lt_bigint, f64, BigInt, bigint_ge_f64, bigint_le_f64, bigint_gt_f64, bigint_lt_f64;
560 );
561
562 pub fn is_multiple_of_bigint(value: &BigInt, multiple: &BigInt) -> bool {
564 if value.is_zero() {
567 return true;
568 }
569
570 (value % multiple).is_zero()
577 }
578
579 macro_rules! define_bigfraction_cmp {
581 ($($fn_name:ident, $prim_type:ty, $op:tt);* $(;)?) => {
582 $(
583 pub fn $fn_name(bigfrac: &BigFraction, value: $prim_type) -> bool {
584 let value_frac = BigFraction::from(value);
585 *bigfrac $op value_frac
586 }
587 )*
588 };
589 }
590
591 define_bigfraction_cmp!(
592 bigfrac_ge_u64, u64, >=;
593 bigfrac_le_u64, u64, <=;
594 bigfrac_gt_u64, u64, >;
595 bigfrac_lt_u64, u64, <;
596 bigfrac_ge_i64, i64, >=;
597 bigfrac_le_i64, i64, <=;
598 bigfrac_gt_i64, i64, >;
599 bigfrac_lt_i64, i64, <;
600 bigfrac_ge_f64, f64, >=;
601 bigfrac_le_f64, f64, <=;
602 bigfrac_gt_f64, f64, >;
603 bigfrac_lt_f64, f64, <;
604 );
605
606 define_reverse_cmp!(
607 u64_ge_bigfrac, u64_le_bigfrac, u64_gt_bigfrac, u64_lt_bigfrac, u64, BigFraction, bigfrac_ge_u64, bigfrac_le_u64, bigfrac_gt_u64, bigfrac_lt_u64;
608 i64_ge_bigfrac, i64_le_bigfrac, i64_gt_bigfrac, i64_lt_bigfrac, i64, BigFraction, bigfrac_ge_i64, bigfrac_le_i64, bigfrac_gt_i64, bigfrac_lt_i64;
609 f64_ge_bigfrac, f64_le_bigfrac, f64_gt_bigfrac, f64_lt_bigfrac, f64, BigFraction, bigfrac_ge_f64, bigfrac_le_f64, bigfrac_gt_f64, bigfrac_lt_f64;
610 );
611
612 pub fn is_multiple_of_bigfrac(value: &BigFraction, multiple: &BigFraction) -> bool {
614 if value.is_zero() {
616 return true;
617 }
618 if multiple.is_zero() {
620 return false;
621 }
622 (value / multiple).denom().is_none_or(fraction::One::is_one)
625 }
626}
627
628#[cfg(all(test, feature = "arbitrary-precision"))]
629mod tests {
630 use super::bignum;
631 use fraction::BigFraction;
632 use num_bigint::BigInt;
633 use serde_json::{Number, Value};
634 use std::cmp::Ordering;
635 use test_case::test_case;
636
637 fn number_from_str(raw: &str) -> Number {
638 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
639 Value::Number(num) => num,
640 _ => unreachable!(),
641 }
642 }
643
644 #[test_case("18446744073709551616", u64::MAX, Ordering::Greater; "above u64 limit")]
645 fn compare_bigint_to_u64_limit(big: &str, limit: u64, expected: Ordering) {
646 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
647 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
648 }
649
650 #[test_case("-18446744073709551616", i64::MIN, Ordering::Less; "below i64 limit")]
651 fn compare_bigint_to_i64_limit(big: &str, limit: i64, expected: Ordering) {
652 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
653 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
654 }
655
656 #[test_case(f64::INFINITY, Some(Ordering::Less); "infinity limit")]
659 #[test_case(f64::NEG_INFINITY, Some(Ordering::Greater); "negative infinity limit")]
660 #[test_case(0.5, None; "no exact integer form")]
661 fn compare_bigint_to_f64_limit(limit: f64, expected: Option<Ordering>) {
662 let big = BigInt::parse_bytes(b"18446744073709551616", 10).unwrap();
663 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), expected);
664 }
665
666 #[test]
667 fn bigint_parses_scientific_integer() {
668 let num = number_from_str("1e19");
669 let parsed = bignum::try_parse_bigint(&num).expect("parsed bigint");
670 assert_eq!(
671 parsed,
672 BigInt::parse_bytes(b"10000000000000000000", 10).unwrap()
673 );
674 }
675
676 #[test]
677 fn bigint_rejects_non_integer_scientific() {
678 let num = number_from_str("1.25e1");
679 assert!(bignum::try_parse_bigint(&num).is_none());
680 }
681
682 #[test]
683 fn bigfraction_parses_scientific_decimal() {
684 let num = number_from_str("1.5e-5");
685 let parsed = bignum::try_parse_bigfraction(&num).expect("parsed bigfraction");
686 let expected =
687 BigFraction::from(BigInt::from(3)) / BigFraction::from(BigInt::from(200_000));
688 assert_eq!(parsed, expected);
689 }
690
691 #[test]
692 fn bigfraction_skips_scientific_integer() {
693 let num = number_from_str("3e4");
694 assert!(bignum::try_parse_bigfraction(&num).is_none());
695 }
696}
697
698#[cfg(all(test, feature = "arbitrary-precision"))]
699mod exact_multiple_of_tests {
700 use super::is_multiple_of_integer;
701 use serde_json::{Number, Value};
702 use test_case::test_case;
703
704 fn number(raw: &str) -> Number {
705 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
706 Value::Number(num) => num,
707 _ => unreachable!(),
708 }
709 }
710
711 #[test_case("135107988821114880000000000000", 3.0, true; "multiple of three")]
714 #[test_case("135107988821114880000000000001", 3.0, false; "one past a multiple of three")]
715 #[test_case("135107988821114880000000000002", 3.0, false; "two past a multiple of three")]
716 #[test_case("18446744073709551617", 2.0, false; "odd just past u64")]
717 #[test_case("18446744073709551618", 2.0, true; "even just past u64")]
718 #[test_case("1e30", 3.0, false; "scientific not a multiple")]
719 #[test_case("1e30", 2.0, true; "scientific is a multiple")]
720 fn exact_beyond_u64(value: &str, divisor: f64, expected: bool) {
721 assert_eq!(is_multiple_of_integer(&number(value), divisor), expected);
722 }
723}