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 {
59 if let Some(big_value) = bignum::try_parse_bigfraction(&value.to_number()) {
60 if let Some(limit_f64) = num_traits::ToPrimitive::to_f64(&limit) {
61 let limit_frac = BigFraction::from(limit_f64);
62 return big_value $op limit_frac;
63 }
64 }
65 let is_negative = value.as_str().starts_with('-');
67 if $infinity_positive {
68 !is_negative
69 } else {
70 is_negative
71 }
72 }
73 #[cfg(not(feature = "arbitrary-precision"))]
74 {
75 unreachable!("Always Some without `arbitrary-precision`")
76 }
77 }
78 }
79 )*
80 };
81}
82
83define_num_cmp!(
84 num_ge => ge, >=, true, Ordering::Greater | Ordering::Equal, num_le => le, <=, false, Ordering::Less | Ordering::Equal, num_gt => gt, >, true, Ordering::Greater,
87 num_lt => lt, <, false, Ordering::Less,
88);
89
90#[cfg(feature = "macros")]
91pub fn eq<N, T>(value: &N, limit: T) -> bool
92where
93 N: crate::JsonNumber,
94 T: Copy + num_traits::ToPrimitive,
95 u64: num_cmp::NumCmp<T>,
96 i64: num_cmp::NumCmp<T>,
97 f64: num_cmp::NumCmp<T>,
98{
99 if let Some(v) = value.as_u64() {
100 num_cmp::NumCmp::num_eq(v, limit)
101 } else if let Some(v) = value.as_i64() {
102 num_cmp::NumCmp::num_eq(v, limit)
103 } else if let Some(v) = value.as_f64() {
104 #[cfg(feature = "arbitrary-precision")]
105 if let Some(ordering) = exact_ordering(value, v, limit) {
106 return ordering == Ordering::Equal;
107 }
108 num_cmp::NumCmp::num_eq(v, limit)
109 } else {
110 #[cfg(feature = "arbitrary-precision")]
111 {
112 if let Some(big_value) = bignum::try_parse_bigfraction(&value.to_number()) {
113 if let Some(limit_f64) = num_traits::ToPrimitive::to_f64(&limit) {
114 return big_value == BigFraction::from(limit_f64);
115 }
116 }
117 false
118 }
119 #[cfg(not(feature = "arbitrary-precision"))]
120 {
121 unreachable!("Always Some without `arbitrary-precision`")
122 }
123 }
124}
125
126fn decimal_parts(value: f64) -> Option<(i128, i32)> {
131 if !value.is_finite() {
132 return None;
133 }
134 let mut buffer = zmij::Buffer::new();
135 let mut mantissa: i128 = 0;
136 let mut decimals = 0;
137 let mut exponent = 0;
138 let mut negative = false;
139 let mut fractional = false;
140 let mut bytes = buffer.format_finite(value).bytes();
141 for byte in &mut bytes {
142 match byte {
143 b'-' => negative = true,
144 b'.' => fractional = true,
145 b'e' => {
147 exponent = parse_exponent(&mut bytes)?;
148 break;
149 }
150 _ => {
151 mantissa = mantissa
152 .checked_mul(10)?
153 .checked_add(i128::from(byte.checked_sub(b'0')?))?;
154 decimals += i32::from(fractional);
155 }
156 }
157 }
158 Some((
159 if negative { -mantissa } else { mantissa },
160 decimals - exponent,
161 ))
162}
163
164fn parse_exponent(bytes: &mut impl Iterator<Item = u8>) -> Option<i32> {
166 let mut exponent = 0_i32;
167 let mut negative = false;
168 for byte in bytes {
169 match byte {
170 b'+' => {}
171 b'-' => negative = true,
172 _ => {
173 exponent = exponent
174 .checked_mul(10)?
175 .checked_add(i32::from(byte.checked_sub(b'0')?))?;
176 }
177 }
178 }
179 Some(if negative { -exponent } else { exponent })
180}
181
182fn divides_exactly(value: f64, multiple: f64) -> Option<bool> {
189 let (value_mantissa, value_decimals) = decimal_parts(value)?;
190 let (multiple_mantissa, multiple_decimals) = decimal_parts(multiple)?;
191 if multiple_mantissa == 0 {
192 return None;
193 }
194 let shared = value_decimals.min(multiple_decimals);
198 let scale = |mantissa: i128, decimals: i32| {
199 let places = u32::try_from(decimals - shared).ok()?;
200 mantissa.checked_mul(10_i128.checked_pow(places)?)
201 };
202 Some(scale(value_mantissa, multiple_decimals)? % scale(multiple_mantissa, value_decimals)? == 0)
203}
204
205pub fn is_multiple_of_float<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
206 if let Some(value_f64) = value.as_f64() {
207 if value_f64.is_zero() {
210 return true;
211 }
212 if value_f64.abs() < multiple {
213 return false;
214 }
215 if let Some(answer) = divides_exactly(value_f64, multiple) {
223 return answer;
224 }
225 (BigFraction::from(value_f64) / BigFraction::from(multiple))
226 .denom()
227 .is_none_or(One::is_one)
228 } else {
229 false
232 }
233}
234
235const MAX_SAFE_INTEGER: u64 = 1u64 << 53;
238
239pub fn is_multiple_of_integer<N: crate::JsonNumber>(value: &N, multiple: f64) -> bool {
240 let divisor_ok =
246 multiple > 0.0 && multiple <= MAX_SAFE_INTEGER as f64 && multiple.fract() == 0.0;
247 if divisor_ok {
248 if let Some(v) = value.as_u64() {
249 return (v % (multiple as u64)) == 0;
250 }
251 if let Some(v) = value.as_i64() {
252 return (v % (multiple as i64)) == 0;
253 }
254 #[cfg(feature = "arbitrary-precision")]
257 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
258 let divisor = num_bigint::BigInt::from(multiple as i64);
259 return bignum::is_multiple_of_bigint(&big_value, &divisor);
260 }
261 }
262
263 if let Some(value_f64) = value.as_f64() {
264 #[cfg(feature = "arbitrary-precision")]
268 if value_f64 == 0.0 && !bignum::is_zero_literal(&value.to_number()) {
269 return false;
270 }
271 value_f64.fract() == 0. && (value_f64 % multiple) == 0.
274 } else {
275 #[cfg(feature = "arbitrary-precision")]
277 {
278 if let Some(big_value) = bignum::try_parse_bigint(&value.to_number()) {
280 use num_bigint::BigInt;
281 let multiple_int = BigInt::from(multiple as i64);
291 return bignum::is_multiple_of_bigint(&big_value, &multiple_int);
292 }
293 false
295 }
296 #[cfg(not(feature = "arbitrary-precision"))]
297 {
298 unreachable!("Always Some without `arbitrary-precision`")
299 }
300 }
301}
302
303#[cfg(feature = "arbitrary-precision")]
304pub mod bignum {
305 use fraction::BigFraction;
306 use num_bigint::BigInt;
307 use num_traits::{ToPrimitive, Zero};
308 use serde_json::Number;
309 use std::str::FromStr;
310
311 const MAX_EXPONENT_ADJUSTMENT: u32 = 1_000_000;
320
321 #[derive(Debug, Clone)]
322 struct DecimalComponents {
323 negative: bool,
324 digits: String,
325 fraction_digits: usize,
326 exponent: i64,
327 }
328
329 impl DecimalComponents {
330 fn parse(num_str: &str) -> Option<Self> {
331 let bytes = num_str.as_bytes();
332 if bytes.is_empty() {
333 return None;
334 }
335
336 let mut idx = 0;
337 let negative = if bytes[idx] == b'-' {
338 idx += 1;
339 true
340 } else {
341 false
342 };
343
344 if idx >= bytes.len() {
345 return None;
346 }
347
348 let mut digits = String::with_capacity(bytes.len());
349 let int_start = idx;
350 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
351 idx += 1;
352 }
353 if int_start == idx {
354 return None;
355 }
356 digits.push_str(&num_str[int_start..idx]);
357
358 let mut fraction_digits = 0usize;
359 if idx < bytes.len() && bytes[idx] == b'.' {
360 idx += 1;
361 let frac_start = idx;
362 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
363 idx += 1;
364 }
365 if frac_start == idx {
366 return None;
367 }
368 digits.push_str(&num_str[frac_start..idx]);
369 fraction_digits = idx - frac_start;
370 }
371
372 let mut exponent: i64 = 0;
373 if idx < bytes.len() && (bytes[idx] == b'e' || bytes[idx] == b'E') {
374 idx += 1;
375 if idx >= bytes.len() {
376 return None;
377 }
378 let mut exp_sign: i64 = 1;
379 if bytes[idx] == b'+' {
380 idx += 1;
381 } else if bytes[idx] == b'-' {
382 exp_sign = -1;
383 idx += 1;
384 }
385 let exp_start = idx;
386 while idx < bytes.len() && bytes[idx].is_ascii_digit() {
387 idx += 1;
388 }
389 if exp_start == idx {
390 return None;
391 }
392 let exp_value = num_str[exp_start..idx].parse::<i64>().ok()?;
393 exponent = exp_value.checked_mul(exp_sign)?;
394 }
395
396 if idx != bytes.len() {
397 return None;
398 }
399
400 Some(Self {
401 negative,
402 digits,
403 fraction_digits,
404 exponent,
405 })
406 }
407
408 #[inline]
409 fn decimal_shift(&self) -> i64 {
410 self.exponent - self.fraction_digits as i64
411 }
412 }
413
414 fn digits_are_zero(s: &str) -> bool {
415 s.bytes().all(|b| b == b'0')
416 }
417
418 fn trailing_zero_count(s: &str) -> usize {
419 s.as_bytes()
420 .iter()
421 .rev()
422 .take_while(|b| **b == b'0')
423 .count()
424 }
425
426 fn append_zeros(target: &mut String, count: usize) -> Option<()> {
427 let new_len = target.len().checked_add(count)?;
428 target.reserve(count);
429 target.extend(std::iter::repeat_n('0', count));
430 debug_assert_eq!(target.len(), new_len);
431 Some(())
432 }
433
434 fn pow10_bigint(exp: usize) -> Option<BigInt> {
435 if exp == 0 {
436 return Some(BigInt::from(1));
437 }
438 let exp_u32 = u32::try_from(exp).ok()?;
439 Some(BigInt::from(10).pow(exp_u32))
440 }
441
442 fn shift_exceeds_limit(shift: i64) -> bool {
443 if shift <= 0 {
444 return false;
445 }
446 shift as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT)
447 }
448
449 fn exponent_reduction_exceeds_limit(exponent: i64) -> bool {
450 if exponent >= 0 {
451 return false;
452 }
453 match exponent.checked_abs() {
454 Some(abs) => abs as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT),
455 None => true,
456 }
457 }
458
459 pub fn try_parse_bigint(num: &Number) -> Option<BigInt> {
462 use super::MAX_SAFE_INTEGER;
463
464 let num_str = num.as_str();
465
466 if let Some(v) = num.as_i64() {
471 if v.unsigned_abs() <= MAX_SAFE_INTEGER {
472 return None;
473 }
474 }
475
476 let has_fraction_or_exponent = num_str.bytes().any(|b| b == b'.' || b == b'e' || b == b'E');
477 if !has_fraction_or_exponent {
478 return BigInt::from_str(num_str).ok();
479 }
480
481 let mut components = DecimalComponents::parse(num_str)?;
482 let mut shift = components.decimal_shift();
483
484 if shift < 0 {
485 let needed = (-shift) as usize;
486 if digits_are_zero(&components.digits) {
487 components.digits.clear();
488 components.digits.push('0');
489 shift = 0;
490 } else {
491 if exponent_reduction_exceeds_limit(components.exponent) {
492 return None;
493 }
494 let zeros = trailing_zero_count(&components.digits);
495 if zeros < needed {
496 return None;
497 }
498 let new_len = components.digits.len() - needed;
499 components.digits.truncate(new_len);
500 shift = 0;
501 }
502 }
503
504 if shift > 0 {
505 if shift_exceeds_limit(shift) {
506 return None;
507 }
508 append_zeros(&mut components.digits, shift as usize)?;
509 }
510
511 let digits_trimmed = components.digits.trim_start_matches('0');
512 let digits_ref = if digits_trimmed.is_empty() {
513 "0"
514 } else {
515 digits_trimmed
516 };
517 let mut value = BigInt::from_str(digits_ref).ok()?;
518 if components.negative && !value.is_zero() {
519 value = -value;
520 }
521 Some(value)
522 }
523
524 pub fn try_parse_bigfraction(num: &Number) -> Option<BigFraction> {
534 if num.as_i64().is_some() {
536 return None;
537 }
538
539 let num_str = num.as_str();
540
541 let mut has_decimal_point = false;
543 let mut has_exponent = false;
544 for b in num_str.bytes() {
545 if b == b'.' {
546 has_decimal_point = true;
547 } else if b == b'e' || b == b'E' {
548 has_exponent = true;
549 break;
550 }
551 }
552
553 if !has_decimal_point && !has_exponent {
554 return None;
555 }
556
557 if !has_exponent {
558 return BigFraction::from_str(num_str).ok();
559 }
560
561 let components = DecimalComponents::parse(num_str)?;
562 let shift = components.decimal_shift();
563
564 if shift >= 0 {
566 return None;
567 }
568
569 if exponent_reduction_exceeds_limit(components.exponent) {
570 return None;
571 }
572
573 let denom_power = (-shift) as usize;
574 let denominator = pow10_bigint(denom_power)?;
575 let mut numerator = BigInt::from_str(&components.digits).ok()?;
576 if components.negative && !numerator.is_zero() {
577 numerator = -numerator;
578 }
579 Some(BigFraction::from(numerator) / BigFraction::from(denominator))
580 }
581
582 pub(crate) fn compare_bigint_to_limit<T>(big: &BigInt, limit: T) -> Option<std::cmp::Ordering>
588 where
589 T: Copy + ToPrimitive,
590 {
591 use std::cmp::Ordering;
592
593 let limit_f64 = limit.to_f64()?;
594 if limit_f64.fract() == 0.0 {
595 if let Some(limit_int) = limit.to_i64() {
597 return Some(big.cmp(&BigInt::from(limit_int)));
598 }
599 if let Some(limit_int) = limit.to_u64() {
600 return Some(big.cmp(&BigInt::from(limit_int)));
601 }
602 }
603 if limit_f64 == f64::INFINITY {
604 return Some(Ordering::Less);
605 }
606 if limit_f64 == f64::NEG_INFINITY {
607 return Some(Ordering::Greater);
608 }
609 None
610 }
611
612 fn limit_as_bigfraction<T>(limit: T) -> Option<BigFraction>
616 where
617 T: Copy + ToPrimitive,
618 {
619 if limit.to_f64()?.fract() != 0.0 {
620 return None;
621 }
622 if let Some(limit_int) = limit.to_i64() {
623 return Some(BigFraction::from(limit_int));
624 }
625 limit.to_u64().map(BigFraction::from)
626 }
627
628 pub(crate) fn compare_to_limit<T>(num: &Number, limit: T) -> Option<std::cmp::Ordering>
633 where
634 T: Copy + ToPrimitive,
635 {
636 if let Some(big) = try_parse_bigint(num) {
637 return compare_bigint_to_limit(&big, limit);
638 }
639 let value = try_parse_bigfraction(num)?;
640 value.partial_cmp(&limit_as_bigfraction(limit)?)
641 }
642
643 pub(crate) fn is_zero_literal(num: &Number) -> bool {
648 DecimalComponents::parse(num.as_str())
649 .is_some_and(|components| digits_are_zero(&components.digits))
650 }
651
652 macro_rules! define_bigint_cmp {
653 ($($fn_name:ident, $prim_type:ty, $to_prim:ident, $op:tt, $overflow_sign:expr);* $(;)?) => {
654 $(
655 pub fn $fn_name(bigint: &BigInt, value: $prim_type) -> bool {
656 if let Some(converted) = bigint.$to_prim() {
657 converted $op value
658 } else {
659 bigint.sign() == $overflow_sign
660 }
661 }
662 )*
663 };
664 }
665
666 define_bigint_cmp!(
667 bigint_ge_u64, u64, to_u64, >=, num_bigint::Sign::Plus;
668 bigint_le_u64, u64, to_u64, <=, num_bigint::Sign::Minus;
669 bigint_gt_u64, u64, to_u64, >, num_bigint::Sign::Plus;
670 bigint_lt_u64, u64, to_u64, <, num_bigint::Sign::Minus;
671 bigint_ge_i64, i64, to_i64, >=, num_bigint::Sign::Plus;
672 bigint_le_i64, i64, to_i64, <=, num_bigint::Sign::Minus;
673 bigint_gt_i64, i64, to_i64, >, num_bigint::Sign::Plus;
674 bigint_lt_i64, i64, to_i64, <, num_bigint::Sign::Minus;
675 bigint_ge_f64, f64, to_f64, >=, num_bigint::Sign::Plus;
676 bigint_le_f64, f64, to_f64, <=, num_bigint::Sign::Minus;
677 bigint_gt_f64, f64, to_f64, >, num_bigint::Sign::Plus;
678 bigint_lt_f64, f64, to_f64, <, num_bigint::Sign::Minus;
679 );
680
681 macro_rules! define_reverse_cmp {
683 ($($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);* $(;)?) => {
684 $(
685 pub fn $rev_ge(value: $prim_type, big: &$big_type) -> bool {
686 $fwd_le(big, value)
687 }
688
689 pub fn $rev_le(value: $prim_type, big: &$big_type) -> bool {
690 $fwd_ge(big, value)
691 }
692
693 pub fn $rev_gt(value: $prim_type, big: &$big_type) -> bool {
694 $fwd_lt(big, value)
695 }
696
697 pub fn $rev_lt(value: $prim_type, big: &$big_type) -> bool {
698 $fwd_gt(big, value)
699 }
700 )*
701 };
702 }
703
704 define_reverse_cmp!(
705 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;
706 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;
707 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;
708 );
709
710 pub fn is_multiple_of_bigint(value: &BigInt, multiple: &BigInt) -> bool {
712 if value.is_zero() {
715 return true;
716 }
717
718 (value % multiple).is_zero()
725 }
726
727 macro_rules! define_bigfraction_cmp {
729 ($($fn_name:ident, $prim_type:ty, $op:tt);* $(;)?) => {
730 $(
731 pub fn $fn_name(bigfrac: &BigFraction, value: $prim_type) -> bool {
732 let value_frac = BigFraction::from(value);
733 *bigfrac $op value_frac
734 }
735 )*
736 };
737 }
738
739 define_bigfraction_cmp!(
740 bigfrac_ge_u64, u64, >=;
741 bigfrac_le_u64, u64, <=;
742 bigfrac_gt_u64, u64, >;
743 bigfrac_lt_u64, u64, <;
744 bigfrac_ge_i64, i64, >=;
745 bigfrac_le_i64, i64, <=;
746 bigfrac_gt_i64, i64, >;
747 bigfrac_lt_i64, i64, <;
748 bigfrac_ge_f64, f64, >=;
749 bigfrac_le_f64, f64, <=;
750 bigfrac_gt_f64, f64, >;
751 bigfrac_lt_f64, f64, <;
752 );
753
754 define_reverse_cmp!(
755 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;
756 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;
757 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;
758 );
759
760 pub fn is_multiple_of_bigfrac(value: &BigFraction, multiple: &BigFraction) -> bool {
762 if value.is_zero() {
764 return true;
765 }
766 if multiple.is_zero() {
768 return false;
769 }
770 (value / multiple).denom().is_none_or(fraction::One::is_one)
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::{decimal_parts, divides_exactly};
779 use test_case::test_case;
780
781 #[test_case(0.1, Some((1, 1)); "leading zero")]
782 #[test_case(2.675, Some((2675, 3)); "three decimals")]
783 #[test_case(-0.25, Some((-25, 2)); "negative")]
784 #[test_case(7.0, Some((70, 1)); "integral")]
786 #[test_case(1e-7, Some((1, 7)); "negative exponent")]
787 #[test_case(1e300, Some((1, -300)); "positive exponent")]
788 #[test_case(f64::NAN, None; "not a number")]
789 #[test_case(f64::INFINITY, None; "infinite")]
790 fn decimal_parts_reads_the_printed_decimal(value: f64, expected: Option<(i128, i32)>) {
791 assert_eq!(decimal_parts(value), expected);
792 }
793
794 #[test_case(1_070_468.14, 0.01, true; "large amount of cents")]
796 #[test_case(1_070_468.13, 0.01, true; "another large amount of cents")]
797 #[test_case(1_070_468.145, 0.01, false; "large amount of half cents")]
798 #[test_case(19.99, 0.01, true; "small amount of cents")]
799 #[test_case(0.0075, 0.0001, true; "fourth decimal place")]
800 #[test_case(5.35, 2.675, true; "fractional divisor")]
801 #[test_case(505_661.899_999_999_97, 0.1, false; "seventeen significant digits")]
802 fn divides_exactly_answers(value: f64, multiple: f64, expected: bool) {
803 assert_eq!(divides_exactly(value, multiple), Some(expected));
804 }
805
806 #[test_case(1e300; "too large to scale into i128")]
807 #[test_case(1e-300; "too small to scale into i128")]
808 fn divides_exactly_defers_out_of_range(value: f64) {
809 assert_eq!(divides_exactly(value, 0.01), None);
810 }
811}
812
813#[cfg(all(test, feature = "arbitrary-precision"))]
814mod bignum_tests {
815 use crate::numeric::bignum;
816 use fraction::BigFraction;
817 use num_bigint::BigInt;
818 use serde_json::{Number, Value};
819 use std::cmp::Ordering;
820 use test_case::test_case;
821
822 fn number_from_str(raw: &str) -> Number {
823 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
824 Value::Number(num) => num,
825 _ => unreachable!(),
826 }
827 }
828
829 #[test_case("18446744073709551616", u64::MAX, Ordering::Greater; "above u64 limit")]
830 fn compare_bigint_to_u64_limit(big: &str, limit: u64, expected: Ordering) {
831 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
832 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
833 }
834
835 #[test_case("-18446744073709551616", i64::MIN, Ordering::Less; "below i64 limit")]
836 fn compare_bigint_to_i64_limit(big: &str, limit: i64, expected: Ordering) {
837 let big = BigInt::parse_bytes(big.as_bytes(), 10).unwrap();
838 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), Some(expected));
839 }
840
841 #[test_case(f64::INFINITY, Some(Ordering::Less); "infinity limit")]
844 #[test_case(f64::NEG_INFINITY, Some(Ordering::Greater); "negative infinity limit")]
845 #[test_case(0.5, None; "no exact integer form")]
846 fn compare_bigint_to_f64_limit(limit: f64, expected: Option<Ordering>) {
847 let big = BigInt::parse_bytes(b"18446744073709551616", 10).unwrap();
848 assert_eq!(bignum::compare_bigint_to_limit(&big, limit), expected);
849 }
850
851 #[test]
852 fn bigint_parses_scientific_integer() {
853 let num = number_from_str("1e19");
854 let parsed = bignum::try_parse_bigint(&num).expect("parsed bigint");
855 assert_eq!(
856 parsed,
857 BigInt::parse_bytes(b"10000000000000000000", 10).unwrap()
858 );
859 }
860
861 #[test]
862 fn bigint_rejects_non_integer_scientific() {
863 let num = number_from_str("1.25e1");
864 assert!(bignum::try_parse_bigint(&num).is_none());
865 }
866
867 #[test]
868 fn bigfraction_parses_scientific_decimal() {
869 let num = number_from_str("1.5e-5");
870 let parsed = bignum::try_parse_bigfraction(&num).expect("parsed bigfraction");
871 let expected =
872 BigFraction::from(BigInt::from(3)) / BigFraction::from(BigInt::from(200_000));
873 assert_eq!(parsed, expected);
874 }
875
876 #[test]
877 fn bigfraction_skips_scientific_integer() {
878 let num = number_from_str("3e4");
879 assert!(bignum::try_parse_bigfraction(&num).is_none());
880 }
881}
882
883#[cfg(all(test, feature = "arbitrary-precision"))]
884mod exact_multiple_of_tests {
885 use super::is_multiple_of_integer;
886 use serde_json::{Number, Value};
887 use test_case::test_case;
888
889 fn number(raw: &str) -> Number {
890 match serde_json::from_str::<Value>(raw).expect("valid JSON number") {
891 Value::Number(num) => num,
892 _ => unreachable!(),
893 }
894 }
895
896 #[test_case("135107988821114880000000000000", 3.0, true; "multiple of three")]
899 #[test_case("135107988821114880000000000001", 3.0, false; "one past a multiple of three")]
900 #[test_case("135107988821114880000000000002", 3.0, false; "two past a multiple of three")]
901 #[test_case("18446744073709551617", 2.0, false; "odd just past u64")]
902 #[test_case("18446744073709551618", 2.0, true; "even just past u64")]
903 #[test_case("1e30", 3.0, false; "scientific not a multiple")]
904 #[test_case("1e30", 2.0, true; "scientific is a multiple")]
905 fn exact_beyond_u64(value: &str, divisor: f64, expected: bool) {
906 assert_eq!(is_multiple_of_integer(&number(value), divisor), expected);
907 }
908}