1use std::cmp::Ordering;
2use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
3use std::str::FromStr;
4
5use crate::display::ParseDecimalError;
6use crate::integer::{ScaledInteger, SignedScaledInteger};
7
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(transparent)]
12pub struct Decimal<I, const D: u8>(pub I);
13
14#[cold]
15#[inline(never)]
16#[track_caller]
17fn add_overflow<I, const D: u8>(lhs: Decimal<I, D>, rhs: Decimal<I, D>) -> !
18where
19 I: ScaledInteger<D>,
20{
21 panic!("`Decimal` add overflowed; lhs={lhs}; rhs={rhs}")
22}
23
24#[cold]
25#[inline(never)]
26#[track_caller]
27fn sub_overflow<I, const D: u8>(lhs: Decimal<I, D>, rhs: Decimal<I, D>) -> !
28where
29 I: ScaledInteger<D>,
30{
31 panic!("`Decimal` sub overflowed; lhs={lhs}; rhs={rhs}")
32}
33
34#[cold]
35#[inline(never)]
36#[track_caller]
37fn mul_out_of_range<I, const D: u8>(lhs: Decimal<I, D>, rhs: Decimal<I, D>) -> !
38where
39 I: ScaledInteger<D>,
40{
41 panic!("`Decimal` mul out of range; lhs={lhs}; rhs={rhs}")
42}
43
44#[cold]
45#[inline(never)]
46#[track_caller]
47fn division_by_zero<I, const D: u8>(lhs: Decimal<I, D>, rhs: Decimal<I, D>) -> !
48where
49 I: ScaledInteger<D>,
50{
51 panic!("`Decimal` division by zero; lhs={lhs}; rhs={rhs}")
52}
53
54#[cold]
55#[inline(never)]
56#[track_caller]
57fn div_out_of_range<I, const D: u8>(lhs: Decimal<I, D>, rhs: Decimal<I, D>) -> !
58where
59 I: ScaledInteger<D>,
60{
61 panic!("`Decimal` div out of range; lhs={lhs}; rhs={rhs}")
62}
63
64#[cold]
65#[inline(never)]
66#[track_caller]
67fn rem_failed<I, const D: u8>(lhs: Decimal<I, D>, rhs: Decimal<I, D>) -> !
68where
69 I: ScaledInteger<D>,
70{
71 panic!("`Decimal` rem failed; lhs={lhs}; rhs={rhs}")
72}
73
74#[cold]
75#[inline(never)]
76#[track_caller]
77fn neg_overflow<I, const D: u8>(value: Decimal<I, D>) -> !
78where
79 I: SignedScaledInteger<D>,
80{
81 panic!("`Decimal` neg overflowed; value={value}")
82}
83
84impl<I, const D: u8> Decimal<I, D>
85where
86 I: ScaledInteger<D>,
87{
88 pub const ZERO: Decimal<I, D> = Decimal(I::ZERO);
89 pub const ONE: Decimal<I, D> = Decimal(I::SCALING_FACTOR);
90 pub const TWO: Decimal<I, D> = Decimal(I::TWO_SCALING_FACTOR);
91 pub const MIN: Decimal<I, D> = Decimal(I::MIN);
92 pub const MAX: Decimal<I, D> = Decimal(I::MAX);
93 pub const DECIMALS: u8 = D;
94 pub const SCALING_FACTOR: I = I::SCALING_FACTOR;
95
96 #[deprecated(note = "use Self::MIN")]
97 #[must_use]
98 pub const fn min() -> Self {
99 Self::MIN
100 }
101
102 #[deprecated(note = "use Self::MAX")]
103 #[must_use]
104 pub const fn max() -> Self {
105 Self::MAX
106 }
107
108 pub fn try_from_scaled(integer: I, scale: u8) -> Option<Self> {
120 match scale.cmp(&D) {
121 Ordering::Greater => {
122 #[allow(clippy::arithmetic_side_effects)]
124 let divisor = I::TEN.pow(u32::from(scale - D));
125
126 #[allow(clippy::arithmetic_side_effects)]
128 let remainder = integer % divisor;
129 if remainder != I::ZERO {
130 return None;
132 }
133
134 integer.checked_div(&divisor).map(Decimal)
135 }
136 Ordering::Less => {
137 #[allow(clippy::arithmetic_side_effects)]
139 let multiplier = I::TEN.pow(u32::from(D - scale));
140
141 integer.checked_mul(&multiplier).map(Decimal)
142 }
143 Ordering::Equal => Some(Decimal(integer)),
144 }
145 }
146
147 #[inline]
148 pub fn is_zero(&self) -> bool {
149 self.0 == I::ZERO
150 }
151
152 #[inline]
169 #[must_use]
170 pub fn quantize_round_to_zero(&self, quantum: Self) -> Self {
171 #[allow(clippy::arithmetic_side_effects)]
174 Self((self.0 / quantum.0) * quantum.0)
175 }
176}
177
178impl<I, const D: u8> num_traits::Zero for Decimal<I, D>
179where
180 I: ScaledInteger<D>,
181{
182 #[inline]
183 fn zero() -> Self {
184 Self(I::zero())
185 }
186
187 #[inline]
188 fn is_zero(&self) -> bool {
189 self.0.is_zero()
190 }
191}
192
193impl<I, const D: u8> num_traits::One for Decimal<I, D>
194where
195 I: ScaledInteger<D>,
196{
197 #[inline]
198 fn one() -> Self {
199 Self(I::SCALING_FACTOR)
200 }
201}
202
203impl<I, const D: u8> num_traits::Num for Decimal<I, D>
204where
205 I: SignedScaledInteger<D>,
206{
207 type FromStrRadixErr = ParseDecimalError<I>;
208
209 fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
210 if radix != 10 {
211 return Err(ParseDecimalError::RadixMustBe10);
212 }
213
214 Self::from_str(str)
215 }
216}
217
218impl<I, const D: u8> num_traits::Signed for Decimal<I, D>
219where
220 I: SignedScaledInteger<D>,
221{
222 fn abs(&self) -> Self {
223 Self(self.0.abs())
224 }
225
226 fn abs_sub(&self, other: &Self) -> Self {
227 Self(self.0.abs_sub(&other.0))
228 }
229
230 fn signum(&self) -> Self {
231 Self(self.0.signum())
232 }
233
234 fn is_positive(&self) -> bool {
235 self.0.is_positive()
236 }
237
238 fn is_negative(&self) -> bool {
239 self.0.is_negative()
240 }
241}
242
243impl<I, const D: u8> Add for Decimal<I, D>
244where
245 I: ScaledInteger<D>,
246{
247 type Output = Self;
248
249 #[inline]
250 #[track_caller]
251 fn add(self, rhs: Self) -> Self::Output {
252 match self.0.checked_add(&rhs.0) {
253 Some(out) => Decimal(out),
254 None => add_overflow(self, rhs),
255 }
256 }
257}
258
259impl<I, const D: u8> Sub for Decimal<I, D>
260where
261 I: ScaledInteger<D>,
262{
263 type Output = Self;
264
265 #[inline]
266 #[track_caller]
267 fn sub(self, rhs: Self) -> Self::Output {
268 match self.0.checked_sub(&rhs.0) {
269 Some(out) => Decimal(out),
270 None => sub_overflow(self, rhs),
271 }
272 }
273}
274
275impl<I, const D: u8> Mul for Decimal<I, D>
276where
277 I: ScaledInteger<D>,
278{
279 type Output = Self;
280
281 #[inline]
282 #[track_caller]
283 fn mul(self, rhs: Self) -> Self::Output {
284 match I::try_full_mul_div(self.0, rhs.0, I::SCALING_FACTOR) {
285 Some(out) => Decimal(out),
286 None => mul_out_of_range(self, rhs),
287 }
288 }
289}
290
291impl<I, const D: u8> Div for Decimal<I, D>
292where
293 I: ScaledInteger<D>,
294{
295 type Output = Self;
296
297 #[inline]
298 #[track_caller]
299 fn div(self, rhs: Self) -> Self::Output {
300 if rhs.0 == I::ZERO {
301 division_by_zero(self, rhs);
302 }
303
304 match I::try_full_mul_div(self.0, I::SCALING_FACTOR, rhs.0) {
305 Some(out) => Decimal(out),
306 None => div_out_of_range(self, rhs),
307 }
308 }
309}
310
311impl<I, const D: u8> std::ops::Rem for Decimal<I, D>
312where
313 I: ScaledInteger<D>,
314{
315 type Output = Self;
316
317 #[inline]
318 #[track_caller]
319 fn rem(self, rhs: Self) -> Self::Output {
320 match self.0.checked_rem(&rhs.0) {
321 Some(out) => Self(out),
322 None => rem_failed(self, rhs),
323 }
324 }
325}
326
327impl<I, const D: u8> Neg for Decimal<I, D>
328where
329 I: SignedScaledInteger<D>,
330{
331 type Output = Self;
332
333 #[inline]
334 #[track_caller]
335 fn neg(self) -> Self::Output {
336 match self.0.checked_neg() {
337 Some(out) => Decimal(out),
338 None => neg_overflow(self),
339 }
340 }
341}
342
343impl<I, const D: u8> AddAssign for Decimal<I, D>
344where
345 I: ScaledInteger<D>,
346{
347 #[inline]
348 #[track_caller]
349 fn add_assign(&mut self, rhs: Self) {
350 *self = *self + rhs;
351 }
352}
353
354impl<I, const D: u8> SubAssign for Decimal<I, D>
355where
356 I: ScaledInteger<D>,
357{
358 #[inline]
359 #[track_caller]
360 fn sub_assign(&mut self, rhs: Self) {
361 *self = *self - rhs;
362 }
363}
364
365impl<I, const D: u8> MulAssign for Decimal<I, D>
366where
367 I: ScaledInteger<D>,
368{
369 #[inline]
370 #[track_caller]
371 fn mul_assign(&mut self, rhs: Self) {
372 *self = *self * rhs;
373 }
374}
375
376impl<I, const D: u8> DivAssign for Decimal<I, D>
377where
378 I: ScaledInteger<D>,
379{
380 #[inline]
381 #[track_caller]
382 fn div_assign(&mut self, rhs: Self) {
383 *self = *self / rhs;
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use std::fmt::Debug;
390 use std::ops::Shr;
391
392 use malachite::base::num::basic::traits::Zero;
393 use malachite::{Integer, Rational};
394 use paste::paste;
395 use proptest::prelude::*;
396
397 use super::*;
398
399 #[test]
400 #[should_panic(expected = "`Decimal` division by zero; lhs=1.0; rhs=0.0")]
401 fn division_by_zero_message_contains_both_operands() {
402 let _ = Decimal::<i8, 1>::ONE / Decimal::ZERO;
403 }
404
405 #[test]
406 #[should_panic(expected = "`Decimal` div out of range; lhs=12.7; rhs=0.1")]
407 fn division_out_of_range_message_is_cause_neutral() {
408 let _ = Decimal::<i8, 1>::MAX / Decimal(1);
409 }
410
411 macro_rules! test_basic_ops {
412 ($underlying:ty, $decimals:literal) => {
413 paste! {
414 #[test]
415 fn [<num_traits_one_ $underlying _ $decimals _add>]() {
416 use num_traits::One;
417 assert_eq!(
418 Decimal::<$underlying, $decimals>::one(),
419 Decimal::try_from_scaled(1, 0).unwrap(),
420 );
421 assert_eq!(
422 Decimal::<$underlying, $decimals>::one(),
423 Decimal::try_from_scaled(10, 1).unwrap(),
424 );
425 assert_eq!(
426 Decimal::<$underlying, $decimals>::one(),
427 Decimal::try_from_scaled(100, 2).unwrap(),
428 );
429 }
430
431 #[test]
432 fn [<$underlying _ $decimals _add>]() {
433 assert_eq!(
434 Decimal::<$underlying, $decimals>::ONE + Decimal::ONE,
435 Decimal::TWO,
436 );
437 }
438
439 #[test]
440 fn [<$underlying _ $decimals _sub>]() {
441 assert_eq!(
442 Decimal::<$underlying, $decimals>::ONE - Decimal::ONE,
443 Decimal::ZERO,
444 )
445 }
446
447 #[test]
448 fn [<$underlying _ $decimals _mul>]() {
449 assert_eq!(
450 Decimal::<$underlying, $decimals>::ONE * Decimal::ONE,
451 Decimal::ONE,
452 );
453 }
454
455 #[test]
456 fn [<$underlying _ $decimals _div>]() {
457 assert_eq!(
458 Decimal::<$underlying, $decimals>::ONE / Decimal::ONE,
459 Decimal::ONE,
460 );
461 }
462
463 #[test]
464 fn [<$underlying _ $decimals _mul_min_by_one>]() {
465 assert_eq!(
466 Decimal::<$underlying, $decimals>::MIN
467 * Decimal::<$underlying, $decimals>::ONE,
468 Decimal::MIN
469 );
470 }
471
472 #[test]
473 fn [<$underlying _ $decimals _div_min_by_one>]() {
474 assert_eq!(
475 Decimal::<$underlying, $decimals>::MIN
476 / Decimal::<$underlying, $decimals>::ONE,
477 Decimal::MIN
478 );
479 }
480
481 #[test]
482 fn [<$underlying _ $decimals _mul_max_by_one>]() {
483 assert_eq!(
484 Decimal::<$underlying, $decimals>::MAX
485 * Decimal::<$underlying, $decimals>::ONE,
486 Decimal::MAX,
487 );
488 }
489
490 #[test]
491 fn [<$underlying _ $decimals _div_max_by_one>]() {
492 assert_eq!(
493 Decimal::<$underlying, $decimals>::MAX
494 / Decimal::<$underlying, $decimals>::ONE,
495 Decimal::MAX,
496 );
497 }
498
499 #[test]
500 fn [<$underlying _ $decimals _add_assign>]() {
501 let mut out = Decimal::<$underlying, $decimals>::ONE;
502 out += Decimal::ONE;
503
504 assert_eq!(out, Decimal::ONE + Decimal::ONE);
505 }
506
507 #[test]
508 fn [<$underlying _ $decimals _sub_assign>]() {
509 let mut out = Decimal::<$underlying, $decimals>::ONE;
510 out -= Decimal::<$underlying, $decimals>::ONE;
511
512 assert_eq!(out, Decimal::ZERO);
513 }
514
515 #[test]
516 fn [<$underlying _ $decimals _mul_assign>]() {
517 let mut out = Decimal::<$underlying, $decimals>::ONE;
518 out *= Decimal::TWO;
519
520 assert_eq!(out, Decimal::ONE + Decimal::ONE);
521 }
522
523 #[test]
524 fn [<$underlying _ $decimals _div_assign>]() {
525 let mut out = Decimal::<$underlying, $decimals>::ONE;
526 out /= Decimal::TWO;
527
528 assert_eq!(out, Decimal::ONE / Decimal::TWO);
529 }
530
531 #[test]
532 fn [<$underlying _ $decimals _quantize_toward_zero_0>]() {
533 let quantum = Decimal::<$underlying, $decimals>::try_from_scaled(5, 1).unwrap();
534 let original = Decimal::<$underlying, $decimals>::try_from_scaled(61, 1)
535 .unwrap();
536 assert_eq!(
537 original.quantize_round_to_zero(quantum),
538 Decimal::try_from_scaled(60, 1).unwrap(),
539 );
540 let original = Decimal::<$underlying, $decimals>::try_from_scaled(49, 1)
541 .unwrap();
542 assert_eq!(
543 original.quantize_round_to_zero(quantum),
544 Decimal::try_from_scaled(45, 1).unwrap(),
545 );
546 let original = Decimal::<$underlying, $decimals>::try_from_scaled(44, 1)
547 .unwrap();
548 assert_eq!(
549 original.quantize_round_to_zero(quantum),
550 Decimal::try_from_scaled(40, 1).unwrap(),
551 );
552
553 let quantum = Decimal::<$underlying, $decimals>::try_from_scaled(2, 1).unwrap();
554 let original = Decimal::<$underlying, $decimals>::try_from_scaled(61, 1)
555 .unwrap();
556 assert_eq!(
557 original.quantize_round_to_zero(quantum),
558 Decimal::try_from_scaled(60, 1).unwrap(),
559 );
560 let original = Decimal::<$underlying, $decimals>::try_from_scaled(49, 1)
561 .unwrap();
562 assert_eq!(
563 original.quantize_round_to_zero(quantum),
564 Decimal::try_from_scaled(48, 1).unwrap(),
565 );
566 let original = Decimal::<$underlying, $decimals>::try_from_scaled(44, 1)
567 .unwrap();
568 assert_eq!(
569 original.quantize_round_to_zero(quantum),
570 Decimal::try_from_scaled(44, 1).unwrap(),
571 );
572
573 let quantum = Decimal::<$underlying, $decimals>::try_from_scaled(4, 1).unwrap();
574 let original = Decimal::<$underlying, $decimals>::try_from_scaled(123, 1)
575 .unwrap();
576 assert_eq!(
577 original.quantize_round_to_zero(quantum),
578 Decimal::try_from_scaled(120, 1).unwrap(),
579 );
580 }
581 }
582 };
583 }
584
585 macro_rules! fuzz_against_primitive {
586 ($primitive:tt, $decimals:literal) => {
587 paste! {
588 proptest! {
589 #[test]
591 fn [<fuzz_primitive_ $primitive _ $decimals _add>](
592 x in $primitive::MIN..$primitive::MAX,
593 y in $primitive::MIN..$primitive::MAX,
594 ) {
595 let decimal = std::panic::catch_unwind(
596 || Decimal::<_, $decimals>(x) + Decimal(y)
597 );
598 let primitive = std::panic::catch_unwind(|| x.checked_add(y).unwrap());
599
600 match (decimal, primitive) {
601 (Ok(decimal), Ok(primitive)) => assert_eq!(decimal.0, primitive),
602 (Err(_), Err(_)) => {}
603 (decimal, primitive) => panic!(
604 "Mismatch; decimal={decimal:?}; primitive={primitive:?}"
605 )
606 }
607 }
608
609 #[test]
611 fn [<fuzz_primitive_ $primitive _ $decimals _sub>](
612 x in $primitive::MIN..$primitive::MAX,
613 y in $primitive::MIN..$primitive::MAX,
614 ) {
615 let decimal = std::panic::catch_unwind(
616 || Decimal::<_, $decimals>(x) - Decimal(y)
617 );
618 let primitive = std::panic::catch_unwind(|| x.checked_sub(y).unwrap());
619
620 match (decimal, primitive) {
621 (Ok(decimal), Ok(primitive)) => assert_eq!(decimal.0, primitive),
622 (Err(_), Err(_)) => {}
623 (decimal, primitive) => panic!(
624 "Mismatch; decimal={decimal:?}; primitive={primitive:?}",
625 )
626 }
627 }
628
629 #[test]
631 fn [<fuzz_primitive_ $primitive _ $decimals _mul>](
632 x in ($primitive::MIN.shr($primitive::BITS / 2))
633 ..($primitive::MAX.shr($primitive::BITS / 2)),
634 y in ($primitive::MIN.shr($primitive::BITS / 2))
635 ..($primitive::MAX.shr($primitive::BITS / 2)),
636 ) {
637 let decimal = std::panic::catch_unwind(
638 || Decimal::<_, $decimals>(x) * Decimal(y)
639 );
640 let primitive = std::panic::catch_unwind(
641 || x
642 .checked_mul(y)
643 .unwrap()
644 .checked_div($primitive::pow(10, $decimals))
645 .unwrap()
646 );
647
648 match (decimal, primitive) {
649 (Ok(decimal), Ok(primitive)) => assert_eq!(decimal.0, primitive),
650 (Err(_), Err(_)) => {}
651 (decimal, primitive) => panic!(
652 "Mismatch; decimal={decimal:?}; primitive={primitive:?}"
653 )
654 }
655 }
656
657 #[test]
659 fn [<fuzz_primitive_ $primitive _ $decimals _div>](
660 x in ($primitive::MIN / $primitive::pow(10, $decimals))
661 ..($primitive::MAX / $primitive::pow(10, $decimals)),
662 y in ($primitive::MIN / $primitive::pow(10, $decimals))
663 ..($primitive::MAX / $primitive::pow(10, $decimals)),
664 ) {
665 let decimal = std::panic::catch_unwind(
666 || Decimal::<_, $decimals>(x) / Decimal(y)
667 );
668 let primitive = std::panic::catch_unwind(
669 || x
670 .checked_mul($primitive::pow(10, $decimals))
671 .unwrap()
672 .checked_div(y)
673 .unwrap()
674 );
675
676 match (decimal, primitive) {
677 (Ok(decimal), Ok(primitive)) => assert_eq!(decimal.0, primitive),
678 (Err(_), Err(_)) => {}
679 (decimal, primitive) => panic!(
680 "Mismatch; decimal={decimal:?}; primitive={primitive:?}"
681 )
682 }
683 }
684 }
685 }
686 };
687 }
688
689 macro_rules! differential_fuzz {
690 ($underlying:ty, $decimals:literal) => {
691 paste! {
692 #[test]
693 fn [<differential_fuzz_ $underlying _ $decimals _add>]() {
694 differential_fuzz_add::<$underlying, $decimals>();
695 }
696
697 #[test]
698 fn [<differential_fuzz_ $underlying _ $decimals _sub>]() {
699 differential_fuzz_sub::<$underlying, $decimals>();
700 }
701
702 #[test]
703 fn [<differential_fuzz_ $underlying _ $decimals _mul>]() {
704 differential_fuzz_mul::<$underlying, $decimals>();
705 }
706
707 #[test]
708 fn [<differential_fuzz_ $underlying _ $decimals _div>]() {
709 differential_fuzz_div::<$underlying, $decimals>();
710 }
711
712 #[test]
713 fn [<differential_fuzz_ $underlying _ $decimals _add_assign>]() {
714 differential_fuzz_add_assign::<$underlying, $decimals>();
715 }
716
717 #[test]
718 fn [<differential_fuzz_ $underlying _ $decimals _sub_assign>]() {
719 differential_fuzz_sub_assign::<$underlying, $decimals>();
720 }
721
722 #[test]
723 fn [<differential_fuzz_ $underlying _ $decimals _mul_assign>]() {
724 differential_fuzz_mul_assign::<$underlying, $decimals>();
725 }
726
727 #[test]
728 fn [<differential_fuzz_ $underlying _ $decimals _div_assign>]() {
729 differential_fuzz_div_assign::<$underlying, $decimals>();
730 }
731
732 #[test]
733 fn [<differential_fuzz_ $underlying _ $decimals _from_scaled>]() {
734 differential_fuzz_from_scaled::<$underlying, $decimals>();
735 }
736 }
737 };
738 }
739
740 fn differential_fuzz_add<I, const D: u8>()
741 where
742 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe,
743 Rational: From<Decimal<I, D>>,
744 {
745 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
746 let Ok(out) = std::panic::catch_unwind(|| a + b) else {
747 return Ok(());
748 };
749 let reference_out = Rational::from(a) + Rational::from(b);
750
751 assert_eq!(Rational::from(out), reference_out);
752 });
753 }
754
755 fn differential_fuzz_sub<I, const D: u8>()
756 where
757 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe,
758 Rational: From<Decimal<I, D>>,
759 {
760 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
761 let Ok(out) = std::panic::catch_unwind(|| a - b) else {
762 return Ok(());
763 };
764 let reference_out = Rational::from(a) - Rational::from(b);
765
766 assert_eq!(Rational::from(out), reference_out);
767 });
768 }
769
770 fn differential_fuzz_mul<I, const D: u8>()
771 where
772 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe + Into<Integer>,
773 Rational: From<Decimal<I, D>>,
774 {
775 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
776 let Ok(out) = std::panic::catch_unwind(|| a * b) else {
777 return Ok(());
778 };
779 let reference_out = Rational::from(a) * Rational::from(b);
780
781 let scaling: Integer = Decimal::<I, D>::SCALING_FACTOR.into();
783 let divisor = Integer::from(reference_out.denominator_ref());
784 if scaling % divisor != Integer::ZERO {
785 return Ok(());
787 }
788
789 assert_eq!(Rational::from(out), reference_out, "{} {a:?} {b:?} {out:?} {reference_out:?}", I::SCALING_FACTOR);
790 });
791 }
792
793 fn differential_fuzz_div<I, const D: u8>()
794 where
795 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe + Into<Integer>,
796 Rational: From<Decimal<I, D>>,
797 {
798 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
799 if b == Decimal::ZERO {
800 return Ok(());
801 }
802
803 let Ok(out) = std::panic::catch_unwind(|| a / b) else {
804 return Ok(());
805 };
806 let reference_out = Rational::from(a) / Rational::from(b);
807
808 let scaling: Integer = Decimal::<I, D>::SCALING_FACTOR.into();
810 let divisor = Integer::from(reference_out.denominator_ref());
811 if scaling % divisor != Integer::ZERO {
812 return Ok(());
814 }
815
816 assert_eq!(Rational::from(out), reference_out);
817 });
818 }
819
820 fn differential_fuzz_add_assign<I, const D: u8>()
821 where
822 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe,
823 Rational: From<Decimal<I, D>>,
824 {
825 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
826 let Ok(out) = std::panic::catch_unwind(|| {
827 let mut out = a;
828 out += b;
829
830 out
831 }) else {
832 return Ok(());
833 };
834 let reference_out = Rational::from(a) + Rational::from(b);
835
836 assert_eq!(Rational::from(out), reference_out);
837 });
838 }
839
840 fn differential_fuzz_sub_assign<I, const D: u8>()
841 where
842 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe,
843 Rational: From<Decimal<I, D>>,
844 {
845 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
846 let Ok(out) = std::panic::catch_unwind(|| {
847 let mut out = a;
848 out -= b;
849
850 out
851 }) else {
852 return Ok(());
853 };
854 let reference_out = Rational::from(a) - Rational::from(b);
855
856 assert_eq!(Rational::from(out), reference_out);
857 });
858 }
859
860 fn differential_fuzz_mul_assign<I, const D: u8>()
861 where
862 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe + Into<Integer>,
863 Rational: From<Decimal<I, D>>,
864 {
865 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
866 let Ok(out) = std::panic::catch_unwind(|| {
867 let mut out = a;
868 out *= b;
869
870 out
871 }) else {
872 return Ok(());
873 };
874 let reference_out = Rational::from(a) * Rational::from(b);
875
876 let scaling: Integer = Decimal::<I, D>::SCALING_FACTOR.into();
878 let divisor = Integer::from(reference_out.denominator_ref());
879 if scaling % divisor != Integer::ZERO {
880 return Ok(());
882 }
883
884 assert_eq!(Rational::from(out), reference_out);
885 });
886 }
887
888 fn differential_fuzz_div_assign<I, const D: u8>()
889 where
890 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe + Into<Integer>,
891 Rational: From<Decimal<I, D>>,
892 {
893 proptest!(|(a: Decimal<I, D>, b: Decimal<I, D>)| {
894 let Ok(out) = std::panic::catch_unwind(|| {
895 let mut out = a;
896 out /= b;
897
898 out
899 }) else {
900 return Ok(());
901 };
902 let reference_out = Rational::from(a) / Rational::from(b);
903
904 let scaling: Integer = Decimal::<I, D>::SCALING_FACTOR.into();
906 let divisor = Integer::from(reference_out.denominator_ref());
907 if scaling % divisor != Integer::ZERO {
908 return Ok(());
910 }
911
912 assert_eq!(Rational::from(out), reference_out);
913 });
914 }
915
916 fn differential_fuzz_from_scaled<I, const D: u8>()
917 where
918 I: ScaledInteger<D> + Arbitrary + std::panic::RefUnwindSafe + Into<Integer> + TryInto<u64>,
919 Rational: From<I> + From<Decimal<I, D>>,
920 <I as TryInto<u64>>::Error: Debug,
921 {
922 proptest!(|(integer: I, decimals_percent in 0..100u64)| {
923 let max_decimals: u64 = crate::algorithms::log10(I::max_value()).try_into().unwrap();
924 let decimals = u8::try_from(decimals_percent * max_decimals / 100).unwrap();
925 let scaling = I::TEN.pow(u32::from(decimals));
926
927 let out = Decimal::try_from_scaled(integer, decimals);
928 let reference_out = Rational::from_integers(integer.into(), scaling.into());
929
930 match out {
931 Some(out) => assert_eq!(Rational::from(out), reference_out),
932 None => {
933 let scaling: Integer = Decimal::<I, D>::SCALING_FACTOR.into();
934 let remainder = &scaling % Integer::from(reference_out.denominator_ref());
935 let information = &reference_out * Rational::from(scaling);
936
937 assert!(
938 remainder != 0
939 || information > Rational::from(I::max_value())
940 || information < Rational::from(I::min_value()) ,
941 "Failed to parse valid input; integer={integer}; input_scale={decimals}; \
942 output_scale={D}",
943 );
944 }
945 }
946 });
947 }
948
949 crate::macros::apply_to_common_variants!(test_basic_ops);
950 crate::macros::apply_to_common_variants!(fuzz_against_primitive);
951 crate::macros::apply_to_common_variants!(differential_fuzz);
952}