Skip to main content

const_decimal/
decimal.rs

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    /// Losslessly converts a scaled integer to this type.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use const_decimal::Decimal;
114    ///
115    /// let five = Decimal::<u64, 3>::try_from_scaled(5, 0).unwrap();
116    /// assert_eq!(five, Decimal::TWO + Decimal::TWO + Decimal::ONE);
117    /// assert_eq!(five.0, 5000);
118    /// ```
119    pub fn try_from_scaled(integer: I, scale: u8) -> Option<Self> {
120        match scale.cmp(&D) {
121            Ordering::Greater => {
122                // SAFETY: We know `scale > D` so this cannot underflow.
123                #[allow(clippy::arithmetic_side_effects)]
124                let divisor = I::TEN.pow(u32::from(scale - D));
125
126                // SAFETY: `divisor` cannot be zero as `x.pow(y)` cannot return 0.
127                #[allow(clippy::arithmetic_side_effects)]
128                let remainder = integer % divisor;
129                if remainder != I::ZERO {
130                    // NB: Cast would lose precision.
131                    return None;
132                }
133
134                integer.checked_div(&divisor).map(Decimal)
135            }
136            Ordering::Less => {
137                // SAFETY: We know `scale < D` so this cannot underflow.
138                #[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    /// Round a number to a multiple of a given `quantum` toward zero.
153    /// general ref: <https://en.wikipedia.org/wiki/Quantization_(signal_processing)>
154    ///
155    /// By default, rust is rounding towards zero and so does this method.
156    ///
157    /// # Example:
158    /// ```rust
159    /// use const_decimal::Decimal;
160    /// // 11.65
161    /// let d = Decimal::<i64, 5>::try_from_scaled(1165, 2).unwrap();
162    /// // Allow only increments of 0.5
163    /// let quantum = Decimal::<i64, 5>::try_from_scaled(5, 1).unwrap();
164    /// let q = d.quantize_round_to_zero(quantum);
165    /// // 11.5 rounded down to the nearest `quantum`.
166    /// assert_eq!(q, Decimal::try_from_scaled(115, 1).unwrap());
167    /// ```
168    #[inline]
169    #[must_use]
170    pub fn quantize_round_to_zero(&self, quantum: Self) -> Self {
171        // SAFETY: We know the multiplication cannot overflow as we previously divided
172        // by the same number (and rust is rounding towards zero by default).
173        #[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                    /// Addition functions the same as regular unsigned integer addition.
590                    #[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                    /// Subtraction functions the same as regular unsigned integer addition.
610                    #[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                    /// Multiplication requires the result to be divided by the scaling factor.
630                    #[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                    /// Division requires the numerator to first be scaled by the scaling factor.
658                    #[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            // If the multiplication contains truncation ignore it.
782            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                // TODO: Can we assert they are within N of each other?
786                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            // If the division contains truncation ignore it.
809            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                // TODO: Can we assert they are within N of each other?
813                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            // If the multiplication contains truncation ignore it.
877            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                // TODO: Can we assert they are within N of each other?
881                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            // If the division contains truncation ignore it.
905            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                // TODO: Can we assert they are within N of each other?
909                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}