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
14#[cfg(feature = "arbitrary-precision")]
21#[inline]
22fn exact_ordering<N, T>(value: &N, rounded: f64, limit: T) -> Option<Ordering>
23where
24 N: crate::JsonNumber,
25 T: Copy + num_traits::ToPrimitive,
26 f64: num_cmp::NumCmp<T>,
27{
28 let saturated = rounded <= i64::MIN as f64 || rounded >= u64::MAX as f64;
29 if !saturated && !num_cmp::NumCmp::num_eq(rounded, limit) {
30 return None;
31 }
32 bignum::compare_to_limit(&value.to_number(), limit)
33}
34
35macro_rules! define_num_cmp {
36 ($($trait_fn:ident => $fn_name:ident, $op:tt, $infinity_positive:literal, $ord_pat:pat),* $(,)?) => {
37 $(
38 pub fn $fn_name<N, T>(value: &N, limit: T) -> bool
39 where
40 N: crate::JsonNumber,
41 T: Copy + num_traits::ToPrimitive,
42 u64: num_cmp::NumCmp<T>,
43 i64: num_cmp::NumCmp<T>,
44 f64: num_cmp::NumCmp<T>,
45 {
46 if let Some(v) = value.as_u64() {
47 num_cmp::NumCmp::$trait_fn(v, limit)
48 } else if let Some(v) = value.as_i64() {
49 num_cmp::NumCmp::$trait_fn(v, limit)
50 } else if let Some(v) = value.as_f64() {
51 #[cfg(feature = "arbitrary-precision")]
52 if let Some(ordering) = exact_ordering(value, v, limit) {
53 return matches!(ordering, $ord_pat);
54 }
55 num_cmp::NumCmp::$trait_fn(v, limit)
56 } else {
57 #[cfg(feature = "arbitrary-precision")]
58 if let Some(big_value) = bignum::try_parse_bigfraction(&value.to_number()) {
59 if let Some(limit_f64) = num_traits::ToPrimitive::to_f64(&limit) {
60 let limit_frac = BigFraction::from(limit_f64);
61 return big_value $op limit_frac;
62 }
63 }
64 let is_negative = value.as_str().starts_with('-');
66 if $infinity_positive {
67 !is_negative
68 } else {
69 is_negative
70 }
71 }
72 }
73 )*
74 };
75}
76
77define_num_cmp!(
78 num_ge => ge, >=, true, Ordering::Greater | Ordering::Equal, num_le => le, <=, false, Ordering::Less | Ordering::Equal, num_gt => gt, >, true, Ordering::Greater,
81 num_lt => lt, <, false, Ordering::Less,
82);
83
84#[cfg(feature = "macros")]
85pub fn eq<N, T>(value: &N, limit: T) -> bool
86where
87 N: crate::JsonNumber,
88 T: Copy + num_traits::ToPrimitive,
89 u64: num_cmp::NumCmp<T>,
90 i64: num_cmp::NumCmp<T>,
91 f64: num_cmp::NumCmp<T>,
92{
93 if let Some(v) = value.as_u64() {
94 num_cmp::NumCmp::num_eq(v, limit)
95 } else if let Some(v) = value.as_i64() {
96 num_cmp::NumCmp::num_eq(v, limit)
97 } else if let Some(v) = value.as_f64() {
98 #[cfg(feature = "arbitrary-precision")]
99 if let Some(ordering) = exact_ordering(value, v, limit) {
100 return ordering == Ordering::Equal;
101 }
102 num_cmp::NumCmp::num_eq(v, limit)
103 } else {
104 #[cfg(feature = "arbitrary-precision")]
105 if let Some(big_value) = bignum::try_parse_bigfraction(&value.to_number()) {
106 if let Some(limit_f64) = num_traits::ToPrimitive::to_f64(&limit) {
107 return big_value == BigFraction::from(limit_f64);
108 }
109 }
110 false
111 }
112}
113
114fn decimal_parts(value: f64) -> Option<(i128, i32)> {
119 if !value.is_finite() {
120 return None;
121 }
122 let mut buffer = zmij::Buffer::new();
123 let mut mantissa: i128 = 0;
124 let mut decimals = 0;
125 let mut exponent = 0;
126 let mut negative = false;
127 let mut fractional = false;
128 let mut bytes = buffer.format_finite(value).bytes();
129 for byte in &mut bytes {
130 match byte {
131 b'-' => negative = true,
132 b'.' => fractional = true,
133 b'e' => {
135 exponent = parse_exponent(&mut bytes)?;
136 break;
137 }
138 _ => {
139 mantissa = mantissa
140 .checked_mul(10)?
141 .checked_add(i128::from(byte.checked_sub(b'0')?))?;
142 decimals += i32::from(fractional);
143 }
144 }
145 }
146 Some((
147 if negative { -mantissa } else { mantissa },
148 decimals - exponent,
149 ))
150}
151
152fn parse_exponent(bytes: &mut impl Iterator<Item = u8>) -> Option<i32> {
154 let mut exponent = 0_i32;
155 let mut negative = false;
156 for byte in bytes {
157 match byte {
158 b'+' => {}
159 b'-' => negative = true,
160 _ => {
161 exponent = exponent
162 .checked_mul(10)?
163 .checked_add(i32::from(byte.checked_sub(b'0')?))?;
164 }
165 }
166 }
167 Some(if negative { -exponent } else { exponent })
168}
169
170fn divides_exactly(value: f64, multiple: f64) -> Option<bool> {
177 let (value_mantissa, value_decimals) = decimal_parts(value)?;
178 let (multiple_mantissa, multiple_decimals) = decimal_parts(multiple)?;
179 if multiple_mantissa == 0 {
180 return None;
181 }
182 let shared = value_decimals.min(multiple_decimals);
186 let scale = |mantissa: i128, decimals: i32| {
187 let places = u32::try_from(decimals - shared).ok()?;
188 mantissa.checked_mul(10_i128.checked_pow(places)?)
189 };
190 Some(scale(value_mantissa, multiple_decimals)? % scale(multiple_mantissa, value_decimals)? == 0)
191}
192
193pub fn is_multiple_of_float<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
194 #[cfg(feature = "arbitrary-precision")]
195 if let Some(decimal) = bignum::past_digit_budget(&value.as_str()) {
196 return bignum::Decimal::of_limit(multiple)
197 .is_some_and(|multiple| decimal.is_multiple_of(&multiple));
198 }
199 if let Some(value_f64) = value.as_f64() {
200 if value_f64.is_zero() {
203 return decimal_is_zero(&value.as_str());
204 }
205 if value_f64.abs() < multiple {
206 return false;
207 }
208 if let Some(answer) = divides_exactly(value_f64, multiple) {
216 return answer;
217 }
218 (BigFraction::from(value_f64) / BigFraction::from(multiple))
219 .denom()
220 .is_none_or(One::is_one)
221 } else {
222 false
225 }
226}
227
228const MAX_SAFE_INTEGER: u64 = 1u64 << 53;
231
232fn decimal_is_zero(text: &str) -> bool {
234 let mantissa = text.split(['e', 'E']).next().unwrap_or(text);
235 mantissa
236 .bytes()
237 .all(|byte| !byte.is_ascii_digit() || byte == b'0')
238}
239
240#[cfg(not(feature = "arbitrary-precision"))]
243pub(crate) fn decimal_is_multiple_of(text: &str, divisor: u64) -> Option<bool> {
244 if text.contains(['e', 'E']) {
245 return None;
246 }
247 let text = text.strip_prefix('-').unwrap_or(text);
248 let (digits, fraction) = match text.split_once('.') {
249 Some((digits, fraction)) => (digits, Some(fraction)),
250 None => (text, None),
251 };
252 if fraction.is_some_and(|fraction| !fraction.bytes().all(|byte| byte == b'0')) {
254 return Some(false);
255 }
256 let mut remainder = 0_u64;
258 for byte in digits.bytes() {
259 remainder = (remainder * 10 + u64::from(byte - b'0')) % divisor;
260 }
261 Some(remainder == 0)
262}
263
264pub fn is_multiple_of_integer<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
265 let divisor_ok =
271 multiple > 0.0 && multiple <= MAX_SAFE_INTEGER as f64 && multiple.fract() == 0.0;
272 if divisor_ok {
273 if let Some(v) = value.as_u64() {
274 return (v % (multiple as u64)) == 0;
275 }
276 if let Some(v) = value.as_i64() {
277 return (v % (multiple as i64)) == 0;
278 }
279 #[cfg(feature = "arbitrary-precision")]
282 if let Some(decimal) = bignum::past_digit_budget(&value.as_str()) {
283 return bignum::Decimal::of_limit(multiple)
284 .is_some_and(|multiple| decimal.is_multiple_of(&multiple));
285 }
286 #[cfg(feature = "arbitrary-precision")]
287 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
288 let divisor = num_bigint::BigInt::from(multiple as i64);
289 return bignum::is_multiple_of_bigint(&big_value, &divisor);
290 }
291 #[cfg(not(feature = "arbitrary-precision"))]
293 if value
294 .as_f64()
295 .is_none_or(|value_f64| value_f64.abs() >= MAX_SAFE_INTEGER as f64)
296 {
297 if let Some(answer) = decimal_is_multiple_of(&value.as_str(), multiple as u64) {
298 return answer;
299 }
300 }
301 }
302
303 if let Some(value_f64) = value.as_f64() {
304 if value_f64 == 0.0 && !decimal_is_zero(&value.as_str()) {
308 return false;
309 }
310 value_f64.fract() == 0. && (value_f64 % multiple) == 0.
313 } else {
314 #[cfg(feature = "arbitrary-precision")]
316 {
317 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
319 use num_bigint::BigInt;
320 let multiple_int = BigInt::from(multiple as i64);
330 return bignum::is_multiple_of_bigint(&big_value, &multiple_int);
331 }
332 false
334 }
335 #[cfg(not(feature = "arbitrary-precision"))]
337 {
338 false
339 }
340 }
341}
342
343#[cfg(feature = "arbitrary-precision")]
344pub mod bignum {
345 use fraction::BigFraction;
346 use num_bigint::BigInt;
347 use num_traits::{ToPrimitive, Zero};
348 use serde_json::Number;
349 use std::str::FromStr;
350
351 const MAX_DIGITS: u64 = 10_000;
355
356 #[inline]
359 pub fn exceeds_digit_budget(text: &str) -> bool {
360 exceeds_digit_budget_with(text, has_exponent(text))
361 }
362
363 fn has_exponent(text: &str) -> bool {
364 memchr::memchr2(b'e', b'E', text.as_bytes()).is_some()
365 }
366
367 fn exceeds_digit_budget_with(text: &str, has_exponent: bool) -> bool {
368 if !has_exponent {
370 return text.len() as u64 > MAX_DIGITS;
371 }
372 let mut digits: u64 = 0;
373 let mut fraction: i64 = 0;
374 let mut in_fraction = false;
375 let mut exponent: i64 = 0;
376 let mut exponent_negative = false;
377 let mut in_exponent = false;
378 for byte in text.bytes() {
379 match byte {
380 b'0'..=b'9' if in_exponent => {
381 exponent = exponent
382 .saturating_mul(10)
383 .saturating_add(i64::from(byte - b'0'));
384 }
385 b'0'..=b'9' => {
386 digits += 1;
387 fraction += i64::from(in_fraction);
388 }
389 b'.' => in_fraction = true,
390 b'e' | b'E' => in_exponent = true,
391 b'-' if in_exponent => exponent_negative = true,
392 _ => {}
393 }
394 }
395 let exponent = if exponent_negative {
396 -exponent
397 } else {
398 exponent
399 };
400 digits.saturating_add(exponent.saturating_sub(fraction).unsigned_abs()) > MAX_DIGITS
401 }
402
403 pub fn past_digit_budget(text: &str) -> Option<Decimal> {
405 exceeds_digit_budget(text).then(|| Decimal::parse(text))
406 }
407
408 #[derive(Debug, Clone, PartialEq, Eq)]
411 pub struct Decimal {
412 negative: bool,
413 digits: Vec<u8>,
414 exponent: i64,
415 }
416
417 impl Decimal {
418 pub fn parse(text: &str) -> Self {
422 let mut negative = false;
423 let mut digits = Vec::new();
424 let mut fraction: i64 = 0;
425 let mut in_fraction = false;
426 let mut exponent: i64 = 0;
427 let mut exponent_negative = false;
428 let mut in_exponent = false;
429 for byte in text.bytes() {
430 match byte {
431 b'0'..=b'9' if in_exponent => {
432 exponent = exponent
433 .saturating_mul(10)
434 .saturating_add(i64::from(byte - b'0'));
435 }
436 b'0'..=b'9' => {
437 if !(digits.is_empty() && byte == b'0') {
438 digits.push(byte);
439 }
440 fraction += i64::from(in_fraction);
441 }
442 b'.' => in_fraction = true,
443 b'e' | b'E' => in_exponent = true,
444 b'-' if in_exponent => exponent_negative = true,
445 b'-' => negative = true,
446 _ => {}
447 }
448 }
449 let trailing = digits
450 .iter()
451 .rev()
452 .take_while(|&&byte| byte == b'0')
453 .count();
454 digits.truncate(digits.len() - trailing);
455 if digits.is_empty() {
456 return Self {
457 negative: false,
458 digits,
459 exponent: 0,
460 };
461 }
462 let exponent = if exponent_negative {
463 -exponent
464 } else {
465 exponent
466 };
467 Self {
468 negative,
469 digits,
470 exponent: exponent
471 .saturating_sub(fraction)
472 .saturating_add(trailing as i64),
473 }
474 }
475
476 pub fn of_limit<T: Copy + ToPrimitive>(limit: T) -> Option<Self> {
478 let float = limit.to_f64()?;
479 if float.fract() == 0.0 {
480 if let Some(integer) = limit.to_i64() {
481 return Some(Self::parse(&integer.to_string()));
482 }
483 if let Some(integer) = limit.to_u64() {
484 return Some(Self::parse(&integer.to_string()));
485 }
486 }
487 float
488 .is_finite()
489 .then(|| Self::parse(zmij::Buffer::new().format_finite(float)))
490 }
491
492 pub fn of_integer(value: &BigInt) -> Self {
493 Self::parse(&value.to_string())
494 }
495
496 pub fn of_fraction(value: &BigFraction) -> Option<Self> {
498 use num_bigint::BigUint;
499 use num_traits::One;
500
501 let (numerator, denominator) = (value.numer()?, value.denom()?);
502 let twos = denominator.trailing_zeros().unwrap_or(0);
505 let mut rest = denominator >> twos;
506 let five = BigUint::from(5_u8);
507 let mut fives = 0_u64;
508 while (&rest % &five).is_zero() {
509 rest /= &five;
510 fives += 1;
511 }
512 if !rest.is_one() {
513 return None;
514 }
515 let places = twos.max(fives);
516 let digits = numerator
517 * BigUint::from(2_u8).pow(u32::try_from(places - twos).ok()?)
518 * five.pow(u32::try_from(places - fives).ok()?);
519 let sign = if value.is_sign_negative() { "-" } else { "" };
520 Some(Self::parse(&format!("{sign}{digits}e-{places}")))
521 }
522
523 pub fn is_integer(&self) -> bool {
524 self.exponent >= 0
525 }
526
527 pub fn is_multiple_of(&self, multiple: &Self) -> bool {
528 use num_bigint::BigUint;
529
530 if self.digits.is_empty() {
531 return true;
532 }
533 if multiple.digits.is_empty() {
534 return false;
535 }
536 let Ok(shift) =
539 u64::try_from(i128::from(self.exponent) - i128::from(multiple.exponent))
540 else {
541 return false;
542 };
543 let divisor = digit_chunks(&multiple.digits)
544 .fold(BigUint::zero(), |value, (scale, chunk)| {
545 value * scale + chunk
546 });
547 let remainder = digit_chunks(&self.digits)
548 .fold(BigUint::zero(), |value, (scale, chunk)| {
549 (value * scale + chunk) % &divisor
550 });
551 let scale = BigUint::from(10_u8).modpow(&BigUint::from(shift), &divisor);
552 (remainder * scale % &divisor).is_zero()
553 }
554 }
555
556 fn digit_chunks(digits: &[u8]) -> impl Iterator<Item = (num_bigint::BigUint, u64)> + '_ {
558 digits.chunks(19).map(|chunk| {
559 let value = chunk
560 .iter()
561 .fold(0_u64, |value, byte| value * 10 + u64::from(byte - b'0'));
562 (
563 num_bigint::BigUint::from(10_u64.pow(chunk.len() as u32)),
564 value,
565 )
566 })
567 }
568
569 impl PartialOrd for Decimal {
570 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
571 Some(self.cmp(other))
572 }
573 }
574
575 impl Ord for Decimal {
576 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
577 let sign = |value: &Self| match (value.digits.is_empty(), value.negative) {
578 (true, _) => 0,
579 (false, true) => -1,
580 (false, false) => 1,
581 };
582 let leading = |value: &Self| value.digits.len() as i128 + i128::from(value.exponent);
585 sign(self).cmp(&sign(other)).then_with(|| {
586 let magnitude = leading(self)
587 .cmp(&leading(other))
588 .then_with(|| self.digits.cmp(&other.digits));
589 if self.negative {
590 magnitude.reverse()
591 } else {
592 magnitude
593 }
594 })
595 }
596 }
597
598 #[derive(Debug, Clone)]
599 struct DecimalComponents {
600 negative: bool,
601 digits: String,
602 fraction_digits: usize,
603 exponent: i64,
604 }
605
606 impl DecimalComponents {
607 fn parse(num_str: &str) -> Option<Self> {
608 let bytes = num_str.as_bytes();
609 if bytes.is_empty() {
610 return None;
611 }
612
613 let mut idx = 0;
614 let negative = if bytes[idx] == b'-' {
615 idx += 1;
616 true
617 } else {
618 false
619 };
620
621 if idx >= bytes.len() {
622 return None;
623 }
624
625 let mut digits = String::with_capacity(bytes.len());
626 let int_start = idx;
627 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
628 idx += 1;
629 }
630 if int_start == idx {
631 return None;
632 }
633 digits.push_str(&num_str[int_start..idx]);
634
635 let mut fraction_digits = 0usize;
636 if idx < bytes.len() && bytes[idx] == b'.' {
637 idx += 1;
638 let frac_start = idx;
639 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
640 idx += 1;
641 }
642 if frac_start == idx {
643 return None;
644 }
645 digits.push_str(&num_str[frac_start..idx]);
646 fraction_digits = idx - frac_start;
647 }
648
649 let mut exponent: i64 = 0;
650 if idx < bytes.len() && (bytes[idx] == b'e' || bytes[idx] == b'E') {
651 idx += 1;
652 if idx >= bytes.len() {
653 return None;
654 }
655 let mut exp_sign: i64 = 1;
656 if bytes[idx] == b'+' {
657 idx += 1;
658 } else if bytes[idx] == b'-' {
659 exp_sign = -1;
660 idx += 1;
661 }
662 let exp_start = idx;
663 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
664 idx += 1;
665 }
666 if exp_start == idx {
667 return None;
668 }
669 let exp_value = num_str[exp_start..idx].parse::<i64>().ok()?;
670 exponent = exp_value.checked_mul(exp_sign)?;
671 }
672
673 if idx != bytes.len() {
674 return None;
675 }
676
677 Some(Self {
678 negative,
679 digits,
680 fraction_digits,
681 exponent,
682 })
683 }
684
685 #[inline]
686 fn decimal_shift(&self) -> i64 {
687 self.exponent - self.fraction_digits as i64
688 }
689 }
690
691 fn digits_are_zero(s: &str) -> bool {
692 s.bytes().all(|b| b == b'0')
693 }
694
695 fn trailing_zero_count(s: &str) -> usize {
696 s.as_bytes()
697 .iter()
698 .rev()
699 .take_while(|b| **b == b'0')
700 .count()
701 }
702
703 fn append_zeros(target: &mut String, count: usize) -> Option<()> {
704 let new_len = target.len().checked_add(count)?;
705 target.reserve(count);
706 target.extend(std::iter::repeat_n('0', count));
707 debug_assert_eq!(target.len(), new_len);
708 Some(())
709 }
710
711 fn pow10_bigint(exp: usize) -> Option<BigInt> {
712 if exp == 0 {
713 return Some(BigInt::from(1));
714 }
715 let exp_u32 = u32::try_from(exp).ok()?;
716 Some(BigInt::from(10).pow(exp_u32))
717 }
718
719 pub fn whole(value: BigInt) -> BigFraction {
722 let (sign, magnitude) = value.into_parts();
723 let fraction = BigFraction::new_raw(magnitude, num_bigint::BigUint::from(1_u8));
724 if sign == num_bigint::Sign::Minus {
725 -fraction
726 } else {
727 fraction
728 }
729 }
730
731 pub fn try_parse_bigint(num: &Number) -> Option<BigInt> {
734 use super::MAX_SAFE_INTEGER;
735
736 let num_str = num.as_str();
737
738 if let Some(v) = num.as_i64() {
743 if v.unsigned_abs() <= MAX_SAFE_INTEGER {
744 return None;
745 }
746 }
747
748 let has_exponent = has_exponent(num_str);
749 if exceeds_digit_budget_with(num_str, has_exponent) {
750 return None;
751 }
752
753 if !has_exponent && !num_str.as_bytes().contains(&b'.') {
754 return BigInt::from_str(num_str).ok();
755 }
756
757 let mut components = DecimalComponents::parse(num_str)?;
758 let mut shift = components.decimal_shift();
759
760 if shift < 0 {
761 let needed = (-shift) as usize;
762 if digits_are_zero(&components.digits) {
763 components.digits.clear();
764 components.digits.push('0');
765 shift = 0;
766 } else {
767 let zeros = trailing_zero_count(&components.digits);
768 if zeros < needed {
769 return None;
770 }
771 let new_len = components.digits.len() - needed;
772 components.digits.truncate(new_len);
773 shift = 0;
774 }
775 }
776
777 if shift > 0 {
778 append_zeros(&mut components.digits, shift as usize)?;
779 }
780
781 let digits_trimmed = components.digits.trim_start_matches('0');
782 let digits_ref = if digits_trimmed.is_empty() {
783 "0"
784 } else {
785 digits_trimmed
786 };
787 let mut value = BigInt::from_str(digits_ref).ok()?;
788 if components.negative && !value.is_zero() {
789 value = -value;
790 }
791 Some(value)
792 }
793
794 pub fn try_parse_bigfraction(num: &Number) -> Option<BigFraction> {
804 if num.as_i64().is_some() {
806 return None;
807 }
808
809 let num_str = num.as_str();
810
811 let has_exponent = has_exponent(num_str);
812 if !has_exponent && !num_str.as_bytes().contains(&b'.') {
813 return None;
814 }
815
816 if exceeds_digit_budget_with(num_str, has_exponent) {
817 return None;
818 }
819
820 if !has_exponent {
821 return BigFraction::from_str(num_str).ok();
822 }
823
824 let components = DecimalComponents::parse(num_str)?;
825 let shift = components.decimal_shift();
826
827 if shift >= 0 {
829 return None;
830 }
831
832 let denom_power = (-shift) as usize;
833 let denominator = pow10_bigint(denom_power)?;
834 let mut numerator = BigInt::from_str(&components.digits).ok()?;
835 if components.negative && !numerator.is_zero() {
836 numerator = -numerator;
837 }
838 Some(whole(numerator) / whole(denominator))
839 }
840
841 pub(crate) fn compare_bigint_to_limit<T>(big: &BigInt, limit: T) -> Option<std::cmp::Ordering>
847 where
848 T: Copy + ToPrimitive,
849 {
850 use std::cmp::Ordering;
851
852 let limit_f64 = limit.to_f64()?;
853 if limit_f64.fract() == 0.0 {
854 if let Some(limit_int) = limit.to_i64() {
856 return Some(big.cmp(&BigInt::from(limit_int)));
857 }
858 if let Some(limit_int) = limit.to_u64() {
859 return Some(big.cmp(&BigInt::from(limit_int)));
860 }
861 }
862 if limit_f64 == f64::INFINITY {
863 return Some(Ordering::Less);
864 }
865 if limit_f64 == f64::NEG_INFINITY {
866 return Some(Ordering::Greater);
867 }
868 None
869 }
870
871 fn limit_as_bigfraction<T>(limit: T) -> Option<BigFraction>
875 where
876 T: Copy + ToPrimitive,
877 {
878 if limit.to_f64()?.fract() != 0.0 {
879 return None;
880 }
881 if let Some(limit_int) = limit.to_i64() {
882 return Some(BigFraction::from(limit_int));
883 }
884 limit.to_u64().map(BigFraction::from)
885 }
886
887 pub(crate) fn compare_to_limit<T>(num: &Number, limit: T) -> Option<std::cmp::Ordering>
892 where
893 T: Copy + ToPrimitive,
894 {
895 if let Some(value) = past_digit_budget(num.as_str()) {
896 return Some(value.cmp(&Decimal::of_limit(limit)?));
897 }
898 if let Some(big) = try_parse_bigint(num) {
899 return compare_bigint_to_limit(&big, limit);
900 }
901 let value = try_parse_bigfraction(num)?;
902 value.partial_cmp(&limit_as_bigfraction(limit)?)
903 }
904
905 macro_rules! define_bigint_cmp {
906 ($($fn_name:ident, $prim_type:ty, $to_prim:ident, $op:tt, $overflow_sign:expr);* $(;)?) => {
907 $(
908 pub fn $fn_name(bigint: &BigInt, value: $prim_type) -> bool {
909 if let Some(converted) = bigint.$to_prim() {
910 converted $op value
911 } else {
912 bigint.sign() == $overflow_sign
913 }
914 }
915 )*
916 };
917 }
918
919 define_bigint_cmp!(
920 bigint_ge_u64, u64, to_u64, >=, num_bigint::Sign::Plus;
921 bigint_le_u64, u64, to_u64, <=, num_bigint::Sign::Minus;
922 bigint_gt_u64, u64, to_u64, >, num_bigint::Sign::Plus;
923 bigint_lt_u64, u64, to_u64, <, num_bigint::Sign::Minus;
924 bigint_ge_i64, i64, to_i64, >=, num_bigint::Sign::Plus;
925 bigint_le_i64, i64, to_i64, <=, num_bigint::Sign::Minus;
926 bigint_gt_i64, i64, to_i64, >, num_bigint::Sign::Plus;
927 bigint_lt_i64, i64, to_i64, <, num_bigint::Sign::Minus;
928 bigint_ge_f64, f64, to_f64, >=, num_bigint::Sign::Plus;
929 bigint_le_f64, f64, to_f64, <=, num_bigint::Sign::Minus;
930 bigint_gt_f64, f64, to_f64, >, num_bigint::Sign::Plus;
931 bigint_lt_f64, f64, to_f64, <, num_bigint::Sign::Minus;
932 );
933
934 macro_rules! define_reverse_cmp {
936 ($($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);* $(;)?) => {
937 $(
938 pub fn $rev_ge(value: $prim_type, big: &$big_type) -> bool {
939 $fwd_le(big, value)
940 }
941
942 pub fn $rev_le(value: $prim_type, big: &$big_type) -> bool {
943 $fwd_ge(big, value)
944 }
945
946 pub fn $rev_gt(value: $prim_type, big: &$big_type) -> bool {
947 $fwd_lt(big, value)
948 }
949
950 pub fn $rev_lt(value: $prim_type, big: &$big_type) -> bool {
951 $fwd_gt(big, value)
952 }
953 )*
954 };
955 }
956
957 define_reverse_cmp!(
958 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;
959 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;
960 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;
961 );
962
963 pub fn is_multiple_of_bigint(value: &BigInt, multiple: &BigInt) -> bool {
965 if value.is_zero() {
968 return true;
969 }
970
971 (value % multiple).is_zero()
978 }
979
980 macro_rules! define_bigfraction_cmp {
982 ($($fn_name:ident, $prim_type:ty, $op:tt);* $(;)?) => {
983 $(
984 pub fn $fn_name(bigfrac: &BigFraction, value: $prim_type) -> bool {
985 let value_frac = BigFraction::from(value);
986 *bigfrac $op value_frac
987 }
988 )*
989 };
990 }
991
992 define_bigfraction_cmp!(
993 bigfrac_ge_u64, u64, >=;
994 bigfrac_le_u64, u64, <=;
995 bigfrac_gt_u64, u64, >;
996 bigfrac_lt_u64, u64, <;
997 bigfrac_ge_i64, i64, >=;
998 bigfrac_le_i64, i64, <=;
999 bigfrac_gt_i64, i64, >;
1000 bigfrac_lt_i64, i64, <;
1001 bigfrac_ge_f64, f64, >=;
1002 bigfrac_le_f64, f64, <=;
1003 bigfrac_gt_f64, f64, >;
1004 bigfrac_lt_f64, f64, <;
1005 );
1006
1007 define_reverse_cmp!(
1008 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;
1009 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;
1010 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;
1011 );
1012
1013 pub fn is_multiple_of_bigfrac(value: &BigFraction, multiple: &BigFraction) -> bool {
1015 if value.is_zero() {
1017 return true;
1018 }
1019 if multiple.is_zero() {
1021 return false;
1022 }
1023 (value / multiple).denom().is_none_or(fraction::One::is_one)
1026 }
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use super::{decimal_parts, divides_exactly};
1032 use test_case::test_case;
1033
1034 #[test_case(0.1, Some((1, 1)); "leading zero")]
1035 #[test_case(2.675, Some((2675, 3)); "three decimals")]
1036 #[test_case(-0.25, Some((-25, 2)); "negative")]
1037 #[test_case(7.0, Some((70, 1)); "integral")]
1039 #[test_case(1e-7, Some((1, 7)); "negative exponent")]
1040 #[test_case(1e300, Some((1, -300)); "positive exponent")]
1041 #[test_case(f64::NAN, None; "not a number")]
1042 #[test_case(f64::INFINITY, None; "infinite")]
1043 fn decimal_parts_reads_the_printed_decimal(value: f64, expected: Option<(i128, i32)>) {
1044 assert_eq!(decimal_parts(value), expected);
1045 }
1046
1047 #[test_case(1_070_468.14, 0.01, true; "large amount of cents")]
1049 #[test_case(1_070_468.13, 0.01, true; "another large amount of cents")]
1050 #[test_case(1_070_468.145, 0.01, false; "large amount of half cents")]
1051 #[test_case(19.99, 0.01, true; "small amount of cents")]
1052 #[test_case(0.0075, 0.0001, true; "fourth decimal place")]
1053 #[test_case(5.35, 2.675, true; "fractional divisor")]
1054 #[test_case(505_661.899_999_999_97, 0.1, false; "seventeen significant digits")]
1055 fn divides_exactly_answers(value: f64, multiple: f64, expected: bool) {
1056 assert_eq!(divides_exactly(value, multiple), Some(expected));
1057 }
1058
1059 #[test_case(1e300; "too large to scale into i128")]
1060 #[test_case(1e-300; "too small to scale into i128")]
1061 fn divides_exactly_defers_out_of_range(value: f64) {
1062 assert_eq!(divides_exactly(value, 0.01), None);
1063 }
1064}
1065
1066#[cfg(all(test, feature = "arbitrary-precision"))]
1067mod bignum_tests {
1068 use crate::numeric::bignum;
1069 use fraction::BigFraction;
1070 use num_bigint::BigInt;
1071 use serde_json::{Number, Value};
1072 use std::cmp::Ordering;
1073 use test_case::test_case;
1074
1075 fn number_from_str(raw: &str) -> Number {
1076 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
1077 Value::Number(num) => num,
1078 _ => unreachable!(),
1079 }
1080 }
1081
1082 #[test_case("18446744073709551616", u64::MAX, Ordering::Greater; "above u64 limit")]
1083 fn compare_bigint_to_u64_limit(big: &str, limit: u64, expected: Ordering) {
1084 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
1085 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
1086 }
1087
1088 #[test_case("-18446744073709551616", i64::MIN, Ordering::Less; "below i64 limit")]
1089 fn compare_bigint_to_i64_limit(big: &str, limit: i64, expected: Ordering) {
1090 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
1091 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
1092 }
1093
1094 #[test_case(f64::INFINITY, Some(Ordering::Less); "infinity limit")]
1097 #[test_case(f64::NEG_INFINITY, Some(Ordering::Greater); "negative infinity limit")]
1098 #[test_case(0.5, None; "no exact integer form")]
1099 fn compare_bigint_to_f64_limit(limit: f64, expected: Option<Ordering>) {
1100 let big = BigInt::parse_bytes(b"18446744073709551616", 10).unwrap();
1101 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), expected);
1102 }
1103
1104 #[test]
1105 fn bigint_parses_scientific_integer() {
1106 let num = number_from_str("1e19");
1107 let parsed = bignum::try_parse_bigint(&num).expect("parsed bigint");
1108 assert_eq!(
1109 parsed,
1110 BigInt::parse_bytes(b"10000000000000000000", 10).unwrap()
1111 );
1112 }
1113
1114 #[test]
1115 fn bigint_rejects_non_integer_scientific() {
1116 let num = number_from_str("1.25e1");
1117 assert!(bignum::try_parse_bigint(&num).is_none());
1118 }
1119
1120 #[test]
1121 fn bigfraction_parses_scientific_decimal() {
1122 let num = number_from_str("1.5e-5");
1123 let parsed = bignum::try_parse_bigfraction(&num).expect("parsed bigfraction");
1124 let expected =
1125 BigFraction::from(BigInt::from(3)) / BigFraction::from(BigInt::from(200_000));
1126 assert_eq!(parsed, expected);
1127 }
1128
1129 #[test]
1130 fn bigfraction_skips_scientific_integer() {
1131 let num = number_from_str("3e4");
1132 assert!(bignum::try_parse_bigfraction(&num).is_none());
1133 }
1134}
1135
1136#[cfg(all(test, feature = "arbitrary-precision"))]
1137mod exact_multiple_of_tests {
1138 use super::is_multiple_of_integer;
1139 use serde_json::{Number, Value};
1140 use test_case::test_case;
1141
1142 fn number(raw: &str) -> Number {
1143 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
1144 Value::Number(num) => num,
1145 _ => unreachable!(),
1146 }
1147 }
1148
1149 #[test_case("135107988821114880000000000000", 3.0, true; "multiple of three")]
1152 #[test_case("135107988821114880000000000001", 3.0, false; "one past a multiple of three")]
1153 #[test_case("135107988821114880000000000002", 3.0, false; "two past a multiple of three")]
1154 #[test_case("18446744073709551617", 2.0, false; "odd just past u64")]
1155 #[test_case("18446744073709551618", 2.0, true; "even just past u64")]
1156 #[test_case("1e30", 3.0, false; "scientific not a multiple")]
1157 #[test_case("1e30", 2.0, true; "scientific is a multiple")]
1158 fn exact_beyond_u64(value: &str, divisor: f64, expected: bool) {
1159 assert_eq!(is_multiple_of_integer(&number(value), divisor), expected);
1160 }
1161}