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 if let Some(value_f64) = value.as_f64() {
195 if value_f64.is_zero() {
198 return decimal_is_zero(&value.as_str());
199 }
200 if value_f64.abs() < multiple {
201 return false;
202 }
203 if let Some(answer) = divides_exactly(value_f64, multiple) {
211 return answer;
212 }
213 (BigFraction::from(value_f64) / BigFraction::from(multiple))
214 .denom()
215 .is_none_or(One::is_one)
216 } else {
217 false
220 }
221}
222
223const MAX_SAFE_INTEGER: u64 = 1u64 << 53;
226
227fn decimal_is_zero(text: &str) -> bool {
229 let mantissa = text.split(['e', 'E']).next().unwrap_or(text);
230 mantissa
231 .bytes()
232 .all(|byte| !byte.is_ascii_digit() || byte == b'0')
233}
234
235#[cfg(not(feature = "arbitrary-precision"))]
238fn decimal_is_multiple_of(text: &str, divisor: u64) -> Option<bool> {
239 if text.contains(['e', 'E']) {
240 return None;
241 }
242 let text = text.strip_prefix('-').unwrap_or(text);
243 let (digits, fraction) = match text.split_once('.') {
244 Some((digits, fraction)) => (digits, Some(fraction)),
245 None => (text, None),
246 };
247 if fraction.is_some_and(|fraction| !fraction.bytes().all(|byte| byte == b'0')) {
249 return Some(false);
250 }
251 let mut remainder = 0_u64;
253 for byte in digits.bytes() {
254 remainder = (remainder * 10 + u64::from(byte - b'0')) % divisor;
255 }
256 Some(remainder == 0)
257}
258
259pub fn is_multiple_of_integer<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
260 let divisor_ok =
266 multiple > 0.0 && multiple <= MAX_SAFE_INTEGER as f64 && multiple.fract() == 0.0;
267 if divisor_ok {
268 if let Some(v) = value.as_u64() {
269 return (v % (multiple as u64)) == 0;
270 }
271 if let Some(v) = value.as_i64() {
272 return (v % (multiple as i64)) == 0;
273 }
274 #[cfg(feature = "arbitrary-precision")]
277 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
278 let divisor = num_bigint::BigInt::from(multiple as i64);
279 return bignum::is_multiple_of_bigint(&big_value, &divisor);
280 }
281 #[cfg(not(feature = "arbitrary-precision"))]
283 if value
284 .as_f64()
285 .is_none_or(|value_f64| value_f64.abs() >= MAX_SAFE_INTEGER as f64)
286 {
287 if let Some(answer) = decimal_is_multiple_of(&value.as_str(), multiple as u64) {
288 return answer;
289 }
290 }
291 }
292
293 if let Some(value_f64) = value.as_f64() {
294 if value_f64 == 0.0 && !decimal_is_zero(&value.as_str()) {
298 return false;
299 }
300 value_f64.fract() == 0. && (value_f64 % multiple) == 0.
303 } else {
304 #[cfg(feature = "arbitrary-precision")]
306 {
307 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
309 use num_bigint::BigInt;
310 let multiple_int = BigInt::from(multiple as i64);
320 return bignum::is_multiple_of_bigint(&big_value, &multiple_int);
321 }
322 false
324 }
325 #[cfg(not(feature = "arbitrary-precision"))]
327 {
328 false
329 }
330 }
331}
332
333#[cfg(feature = "arbitrary-precision")]
334pub mod bignum {
335 use fraction::BigFraction;
336 use num_bigint::BigInt;
337 use num_traits::{ToPrimitive, Zero};
338 use serde_json::Number;
339 use std::str::FromStr;
340
341 const MAX_EXPONENT_ADJUSTMENT: u32 = 1_000_000;
350
351 #[derive(Debug, Clone)]
352 struct DecimalComponents {
353 negative: bool,
354 digits: String,
355 fraction_digits: usize,
356 exponent: i64,
357 }
358
359 impl DecimalComponents {
360 fn parse(num_str: &str) -> Option<Self> {
361 let bytes = num_str.as_bytes();
362 if bytes.is_empty() {
363 return None;
364 }
365
366 let mut idx = 0;
367 let negative = if bytes[idx] == b'-' {
368 idx += 1;
369 true
370 } else {
371 false
372 };
373
374 if idx >= bytes.len() {
375 return None;
376 }
377
378 let mut digits = String::with_capacity(bytes.len());
379 let int_start = idx;
380 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
381 idx += 1;
382 }
383 if int_start == idx {
384 return None;
385 }
386 digits.push_str(&num_str[int_start..idx]);
387
388 let mut fraction_digits = 0usize;
389 if idx < bytes.len() && bytes[idx] == b'.' {
390 idx += 1;
391 let frac_start = idx;
392 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
393 idx += 1;
394 }
395 if frac_start == idx {
396 return None;
397 }
398 digits.push_str(&num_str[frac_start..idx]);
399 fraction_digits = idx - frac_start;
400 }
401
402 let mut exponent: i64 = 0;
403 if idx < bytes.len() && (bytes[idx] == b'e' || bytes[idx] == b'E') {
404 idx += 1;
405 if idx >= bytes.len() {
406 return None;
407 }
408 let mut exp_sign: i64 = 1;
409 if bytes[idx] == b'+' {
410 idx += 1;
411 } else if bytes[idx] == b'-' {
412 exp_sign = -1;
413 idx += 1;
414 }
415 let exp_start = idx;
416 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
417 idx += 1;
418 }
419 if exp_start == idx {
420 return None;
421 }
422 let exp_value = num_str[exp_start..idx].parse::<i64>().ok()?;
423 exponent = exp_value.checked_mul(exp_sign)?;
424 }
425
426 if idx != bytes.len() {
427 return None;
428 }
429
430 Some(Self {
431 negative,
432 digits,
433 fraction_digits,
434 exponent,
435 })
436 }
437
438 #[inline]
439 fn decimal_shift(&self) -> i64 {
440 self.exponent - self.fraction_digits as i64
441 }
442 }
443
444 fn digits_are_zero(s: &str) -> bool {
445 s.bytes().all(|b| b == b'0')
446 }
447
448 fn trailing_zero_count(s: &str) -> usize {
449 s.as_bytes()
450 .iter()
451 .rev()
452 .take_while(|b| **b == b'0')
453 .count()
454 }
455
456 fn append_zeros(target: &mut String, count: usize) -> Option<()> {
457 let new_len = target.len().checked_add(count)?;
458 target.reserve(count);
459 target.extend(std::iter::repeat_n('0', count));
460 debug_assert_eq!(target.len(), new_len);
461 Some(())
462 }
463
464 fn pow10_bigint(exp: usize) -> Option<BigInt> {
465 if exp == 0 {
466 return Some(BigInt::from(1));
467 }
468 let exp_u32 = u32::try_from(exp).ok()?;
469 Some(BigInt::from(10).pow(exp_u32))
470 }
471
472 fn shift_exceeds_limit(shift: i64) -> bool {
473 if shift <= 0 {
474 return false;
475 }
476 shift as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT)
477 }
478
479 fn exponent_reduction_exceeds_limit(exponent: i64) -> bool {
480 if exponent >= 0 {
481 return false;
482 }
483 match exponent.checked_abs() {
484 Some(abs) => abs as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT),
485 None => true,
486 }
487 }
488
489 pub fn try_parse_bigint(num: &Number) -> Option<BigInt> {
492 use super::MAX_SAFE_INTEGER;
493
494 let num_str = num.as_str();
495
496 if let Some(v) = num.as_i64() {
501 if v.unsigned_abs() <= MAX_SAFE_INTEGER {
502 return None;
503 }
504 }
505
506 let has_fraction_or_exponent = num_str.bytes().any(|b| b == b'.' || b == b'e' || b == b'E');
507 if !has_fraction_or_exponent {
508 return BigInt::from_str(num_str).ok();
509 }
510
511 let mut components = DecimalComponents::parse(num_str)?;
512 let mut shift = components.decimal_shift();
513
514 if shift < 0 {
515 let needed = (-shift) as usize;
516 if digits_are_zero(&components.digits) {
517 components.digits.clear();
518 components.digits.push('0');
519 shift = 0;
520 } else {
521 if exponent_reduction_exceeds_limit(components.exponent) {
522 return None;
523 }
524 let zeros = trailing_zero_count(&components.digits);
525 if zeros < needed {
526 return None;
527 }
528 let new_len = components.digits.len() - needed;
529 components.digits.truncate(new_len);
530 shift = 0;
531 }
532 }
533
534 if shift > 0 {
535 if shift_exceeds_limit(shift) {
536 return None;
537 }
538 append_zeros(&mut components.digits, shift as usize)?;
539 }
540
541 let digits_trimmed = components.digits.trim_start_matches('0');
542 let digits_ref = if digits_trimmed.is_empty() {
543 "0"
544 } else {
545 digits_trimmed
546 };
547 let mut value = BigInt::from_str(digits_ref).ok()?;
548 if components.negative && !value.is_zero() {
549 value = -value;
550 }
551 Some(value)
552 }
553
554 pub fn try_parse_bigfraction(num: &Number) -> Option<BigFraction> {
564 if num.as_i64().is_some() {
566 return None;
567 }
568
569 let num_str = num.as_str();
570
571 let mut has_decimal_point = false;
573 let mut has_exponent = false;
574 for b in num_str.bytes() {
575 if b == b'.' {
576 has_decimal_point = true;
577 } else if b == b'e' || b == b'E' {
578 has_exponent = true;
579 break;
580 }
581 }
582
583 if !has_decimal_point && !has_exponent {
584 return None;
585 }
586
587 if !has_exponent {
588 return BigFraction::from_str(num_str).ok();
589 }
590
591 let components = DecimalComponents::parse(num_str)?;
592 let shift = components.decimal_shift();
593
594 if shift >= 0 {
596 return None;
597 }
598
599 if exponent_reduction_exceeds_limit(components.exponent) {
600 return None;
601 }
602
603 let denom_power = (-shift) as usize;
604 let denominator = pow10_bigint(denom_power)?;
605 let mut numerator = BigInt::from_str(&components.digits).ok()?;
606 if components.negative && !numerator.is_zero() {
607 numerator = -numerator;
608 }
609 Some(BigFraction::from(numerator) / BigFraction::from(denominator))
610 }
611
612 pub(crate) fn compare_bigint_to_limit<T>(big: &BigInt, limit: T) -> Option<std::cmp::Ordering>
618 where
619 T: Copy + ToPrimitive,
620 {
621 use std::cmp::Ordering;
622
623 let limit_f64 = limit.to_f64()?;
624 if limit_f64.fract() == 0.0 {
625 if let Some(limit_int) = limit.to_i64() {
627 return Some(big.cmp(&BigInt::from(limit_int)));
628 }
629 if let Some(limit_int) = limit.to_u64() {
630 return Some(big.cmp(&BigInt::from(limit_int)));
631 }
632 }
633 if limit_f64 == f64::INFINITY {
634 return Some(Ordering::Less);
635 }
636 if limit_f64 == f64::NEG_INFINITY {
637 return Some(Ordering::Greater);
638 }
639 None
640 }
641
642 fn limit_as_bigfraction<T>(limit: T) -> Option<BigFraction>
646 where
647 T: Copy + ToPrimitive,
648 {
649 if limit.to_f64()?.fract() != 0.0 {
650 return None;
651 }
652 if let Some(limit_int) = limit.to_i64() {
653 return Some(BigFraction::from(limit_int));
654 }
655 limit.to_u64().map(BigFraction::from)
656 }
657
658 pub(crate) fn compare_to_limit<T>(num: &Number, limit: T) -> Option<std::cmp::Ordering>
663 where
664 T: Copy + ToPrimitive,
665 {
666 if let Some(big) = try_parse_bigint(num) {
667 return compare_bigint_to_limit(&big, limit);
668 }
669 let value = try_parse_bigfraction(num)?;
670 value.partial_cmp(&limit_as_bigfraction(limit)?)
671 }
672
673 macro_rules! define_bigint_cmp {
674 ($($fn_name:ident, $prim_type:ty, $to_prim:ident, $op:tt, $overflow_sign:expr);* $(;)?) => {
675 $(
676 pub fn $fn_name(bigint: &BigInt, value: $prim_type) -> bool {
677 if let Some(converted) = bigint.$to_prim() {
678 converted $op value
679 } else {
680 bigint.sign() == $overflow_sign
681 }
682 }
683 )*
684 };
685 }
686
687 define_bigint_cmp!(
688 bigint_ge_u64, u64, to_u64, >=, num_bigint::Sign::Plus;
689 bigint_le_u64, u64, to_u64, <=, num_bigint::Sign::Minus;
690 bigint_gt_u64, u64, to_u64, >, num_bigint::Sign::Plus;
691 bigint_lt_u64, u64, to_u64, <, num_bigint::Sign::Minus;
692 bigint_ge_i64, i64, to_i64, >=, num_bigint::Sign::Plus;
693 bigint_le_i64, i64, to_i64, <=, num_bigint::Sign::Minus;
694 bigint_gt_i64, i64, to_i64, >, num_bigint::Sign::Plus;
695 bigint_lt_i64, i64, to_i64, <, num_bigint::Sign::Minus;
696 bigint_ge_f64, f64, to_f64, >=, num_bigint::Sign::Plus;
697 bigint_le_f64, f64, to_f64, <=, num_bigint::Sign::Minus;
698 bigint_gt_f64, f64, to_f64, >, num_bigint::Sign::Plus;
699 bigint_lt_f64, f64, to_f64, <, num_bigint::Sign::Minus;
700 );
701
702 macro_rules! define_reverse_cmp {
704 ($($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);* $(;)?) => {
705 $(
706 pub fn $rev_ge(value: $prim_type, big: &$big_type) -> bool {
707 $fwd_le(big, value)
708 }
709
710 pub fn $rev_le(value: $prim_type, big: &$big_type) -> bool {
711 $fwd_ge(big, value)
712 }
713
714 pub fn $rev_gt(value: $prim_type, big: &$big_type) -> bool {
715 $fwd_lt(big, value)
716 }
717
718 pub fn $rev_lt(value: $prim_type, big: &$big_type) -> bool {
719 $fwd_gt(big, value)
720 }
721 )*
722 };
723 }
724
725 define_reverse_cmp!(
726 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;
727 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;
728 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;
729 );
730
731 pub fn is_multiple_of_bigint(value: &BigInt, multiple: &BigInt) -> bool {
733 if value.is_zero() {
736 return true;
737 }
738
739 (value % multiple).is_zero()
746 }
747
748 macro_rules! define_bigfraction_cmp {
750 ($($fn_name:ident, $prim_type:ty, $op:tt);* $(;)?) => {
751 $(
752 pub fn $fn_name(bigfrac: &BigFraction, value: $prim_type) -> bool {
753 let value_frac = BigFraction::from(value);
754 *bigfrac $op value_frac
755 }
756 )*
757 };
758 }
759
760 define_bigfraction_cmp!(
761 bigfrac_ge_u64, u64, >=;
762 bigfrac_le_u64, u64, <=;
763 bigfrac_gt_u64, u64, >;
764 bigfrac_lt_u64, u64, <;
765 bigfrac_ge_i64, i64, >=;
766 bigfrac_le_i64, i64, <=;
767 bigfrac_gt_i64, i64, >;
768 bigfrac_lt_i64, i64, <;
769 bigfrac_ge_f64, f64, >=;
770 bigfrac_le_f64, f64, <=;
771 bigfrac_gt_f64, f64, >;
772 bigfrac_lt_f64, f64, <;
773 );
774
775 define_reverse_cmp!(
776 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;
777 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;
778 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;
779 );
780
781 pub fn is_multiple_of_bigfrac(value: &BigFraction, multiple: &BigFraction) -> bool {
783 if value.is_zero() {
785 return true;
786 }
787 if multiple.is_zero() {
789 return false;
790 }
791 (value / multiple).denom().is_none_or(fraction::One::is_one)
794 }
795}
796
797#[cfg(test)]
798mod tests {
799 use super::{decimal_parts, divides_exactly};
800 use test_case::test_case;
801
802 #[test_case(0.1, Some((1, 1)); "leading zero")]
803 #[test_case(2.675, Some((2675, 3)); "three decimals")]
804 #[test_case(-0.25, Some((-25, 2)); "negative")]
805 #[test_case(7.0, Some((70, 1)); "integral")]
807 #[test_case(1e-7, Some((1, 7)); "negative exponent")]
808 #[test_case(1e300, Some((1, -300)); "positive exponent")]
809 #[test_case(f64::NAN, None; "not a number")]
810 #[test_case(f64::INFINITY, None; "infinite")]
811 fn decimal_parts_reads_the_printed_decimal(value: f64, expected: Option<(i128, i32)>) {
812 assert_eq!(decimal_parts(value), expected);
813 }
814
815 #[test_case(1_070_468.14, 0.01, true; "large amount of cents")]
817 #[test_case(1_070_468.13, 0.01, true; "another large amount of cents")]
818 #[test_case(1_070_468.145, 0.01, false; "large amount of half cents")]
819 #[test_case(19.99, 0.01, true; "small amount of cents")]
820 #[test_case(0.0075, 0.0001, true; "fourth decimal place")]
821 #[test_case(5.35, 2.675, true; "fractional divisor")]
822 #[test_case(505_661.899_999_999_97, 0.1, false; "seventeen significant digits")]
823 fn divides_exactly_answers(value: f64, multiple: f64, expected: bool) {
824 assert_eq!(divides_exactly(value, multiple), Some(expected));
825 }
826
827 #[test_case(1e300; "too large to scale into i128")]
828 #[test_case(1e-300; "too small to scale into i128")]
829 fn divides_exactly_defers_out_of_range(value: f64) {
830 assert_eq!(divides_exactly(value, 0.01), None);
831 }
832}
833
834#[cfg(all(test, feature = "arbitrary-precision"))]
835mod bignum_tests {
836 use crate::numeric::bignum;
837 use fraction::BigFraction;
838 use num_bigint::BigInt;
839 use serde_json::{Number, Value};
840 use std::cmp::Ordering;
841 use test_case::test_case;
842
843 fn number_from_str(raw: &str) -> Number {
844 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
845 Value::Number(num) => num,
846 _ => unreachable!(),
847 }
848 }
849
850 #[test_case("18446744073709551616", u64::MAX, Ordering::Greater; "above u64 limit")]
851 fn compare_bigint_to_u64_limit(big: &str, limit: u64, expected: Ordering) {
852 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
853 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
854 }
855
856 #[test_case("-18446744073709551616", i64::MIN, Ordering::Less; "below i64 limit")]
857 fn compare_bigint_to_i64_limit(big: &str, limit: i64, expected: Ordering) {
858 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
859 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
860 }
861
862 #[test_case(f64::INFINITY, Some(Ordering::Less); "infinity limit")]
865 #[test_case(f64::NEG_INFINITY, Some(Ordering::Greater); "negative infinity limit")]
866 #[test_case(0.5, None; "no exact integer form")]
867 fn compare_bigint_to_f64_limit(limit: f64, expected: Option<Ordering>) {
868 let big = BigInt::parse_bytes(b"18446744073709551616", 10).unwrap();
869 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), expected);
870 }
871
872 #[test]
873 fn bigint_parses_scientific_integer() {
874 let num = number_from_str("1e19");
875 let parsed = bignum::try_parse_bigint(&num).expect("parsed bigint");
876 assert_eq!(
877 parsed,
878 BigInt::parse_bytes(b"10000000000000000000", 10).unwrap()
879 );
880 }
881
882 #[test]
883 fn bigint_rejects_non_integer_scientific() {
884 let num = number_from_str("1.25e1");
885 assert!(bignum::try_parse_bigint(&num).is_none());
886 }
887
888 #[test]
889 fn bigfraction_parses_scientific_decimal() {
890 let num = number_from_str("1.5e-5");
891 let parsed = bignum::try_parse_bigfraction(&num).expect("parsed bigfraction");
892 let expected =
893 BigFraction::from(BigInt::from(3)) / BigFraction::from(BigInt::from(200_000));
894 assert_eq!(parsed, expected);
895 }
896
897 #[test]
898 fn bigfraction_skips_scientific_integer() {
899 let num = number_from_str("3e4");
900 assert!(bignum::try_parse_bigfraction(&num).is_none());
901 }
902}
903
904#[cfg(all(test, feature = "arbitrary-precision"))]
905mod exact_multiple_of_tests {
906 use super::is_multiple_of_integer;
907 use serde_json::{Number, Value};
908 use test_case::test_case;
909
910 fn number(raw: &str) -> Number {
911 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
912 Value::Number(num) => num,
913 _ => unreachable!(),
914 }
915 }
916
917 #[test_case("135107988821114880000000000000", 3.0, true; "multiple of three")]
920 #[test_case("135107988821114880000000000001", 3.0, false; "one past a multiple of three")]
921 #[test_case("135107988821114880000000000002", 3.0, false; "two past a multiple of three")]
922 #[test_case("18446744073709551617", 2.0, false; "odd just past u64")]
923 #[test_case("18446744073709551618", 2.0, true; "even just past u64")]
924 #[test_case("1e30", 3.0, false; "scientific not a multiple")]
925 #[test_case("1e30", 2.0, true; "scientific is a multiple")]
926 fn exact_beyond_u64(value: &str, divisor: f64, expected: bool) {
927 assert_eq!(is_multiple_of_integer(&number(value), divisor), expected);
928 }
929}