templar_primitives/
number.rs

1#[cfg(feature = "schemars")]
2use alloc::string::ToString;
3use alloc::{
4    fmt::{Debug, Display},
5    format,
6    str::FromStr,
7    string::String,
8    vec::Vec,
9};
10use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
11
12use primitive_types::U512;
13
14pub const FRACTIONAL_BITS: usize = 128;
15/// `floor(FRACTIONAL_BITS / log2(10))`
16pub const FRACTIONAL_DECIMAL_DIGITS: usize = 38;
17/// `floor((512 - FRACTIONAL_BITS) / log2(10))`
18pub const WHOLE_DECIMAL_DIGITS: usize = 115;
19
20/// Because `U512::exp10` is recursive, linear-time, and prone to stack overflows.
21fn u512_pow10(mut exponent: u32) -> U512 {
22    let mut y = U512::one();
23    let mut x = U512::from(10);
24
25    while exponent > 1 {
26        if exponent % 2 == 1 {
27            y *= x;
28        }
29        x *= x;
30        exponent >>= 1;
31    }
32
33    x * y
34}
35
36#[macro_export]
37macro_rules! dec {
38    ($s:literal) => {
39        <$crate::number::Decimal as core::str::FromStr>::from_str($s).unwrap()
40    };
41}
42
43#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44pub struct Decimal {
45    repr: U512,
46}
47
48impl Default for Decimal {
49    fn default() -> Self {
50        Self::ZERO
51    }
52}
53
54#[cfg(feature = "schemars")]
55impl schemars::JsonSchema for Decimal {
56    fn schema_name() -> String {
57        "Decimal".to_string()
58    }
59
60    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
61        let mut schema = gen.subschema_for::<String>().into_object();
62        schema.metadata().description = Some("512-bit fixed-precision decimal".to_string());
63        schema.string().pattern = Some("^(0|[1-9][0-9]{0,115})(\\.[0-9]{1,38})?$".to_string());
64        schema.into()
65    }
66}
67
68#[cfg(feature = "borsh")]
69impl borsh::BorshSchema for Decimal {
70    fn add_definitions_recursively(
71        definitions: &mut alloc::collections::BTreeMap<
72            borsh::schema::Declaration,
73            borsh::schema::Definition,
74        >,
75    ) {
76        <[u64; 8] as borsh::BorshSchema>::add_definitions_recursively(definitions);
77    }
78
79    fn declaration() -> borsh::schema::Declaration {
80        String::from("Decimal")
81    }
82}
83
84#[cfg(feature = "borsh")]
85impl borsh::BorshSerialize for Decimal {
86    fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
87        borsh::BorshSerialize::serialize(&self.repr.0, writer)
88    }
89}
90
91#[cfg(feature = "borsh")]
92impl borsh::BorshDeserialize for Decimal {
93    fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
94        Ok(Self {
95            repr: U512(borsh::BorshDeserialize::deserialize_reader(reader)?),
96        })
97    }
98}
99
100#[cfg(feature = "serde")]
101impl serde::Serialize for Decimal {
102    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103    where
104        S: serde::Serializer,
105    {
106        serializer.serialize_str(&self.to_fixed(FRACTIONAL_DECIMAL_DIGITS))
107    }
108}
109
110#[cfg(feature = "serde")]
111impl<'de> serde::Deserialize<'de> for Decimal {
112    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113    where
114        D: serde::Deserializer<'de>,
115    {
116        let s = <String as serde::Deserialize>::deserialize(deserializer)?;
117        Decimal::from_str(&s).map_err(serde::de::Error::custom)
118    }
119}
120
121impl Decimal {
122    /// When converting to and from strings, we do not guarantee accurate
123    /// representation of bits lower than this.
124    const REPR_EPSILON: U512 = U512([0b1000, 0, 0, 0, 0, 0, 0, 0]);
125
126    pub const MAX: Self = Self { repr: U512::MAX };
127    pub const MIN: Self = Self { repr: U512::zero() };
128
129    pub const ZERO: Self = Self { repr: U512::zero() };
130    pub const ONE_HALF: Self = Self {
131        repr: U512([0, 0x8000_0000_0000_0000, 0, 0, 0, 0, 0, 0]),
132    };
133    #[rustfmt::skip]
134    pub const LN2: Self = Self {
135        repr: U512([0xC9E3_B398_03F2_F6B0, 0xB172_17F7_D1CF_79AB, 0, 0, 0, 0, 0, 0]),
136    };
137    pub const ONE: Self = Self {
138        repr: U512([0, 0, 1, 0, 0, 0, 0, 0]),
139    };
140    pub const TWO: Self = Self {
141        repr: U512([0, 0, 2, 0, 0, 0, 0, 0]),
142    };
143    #[rustfmt::skip]
144    pub const E: Self = Self {
145        repr: U512([0xBF71_5880_9CF4_F3C9, 0xB7E1_5162_8AED_2A6A, 2, 0, 0, 0, 0, 0]),
146    };
147
148    pub fn as_repr(self) -> [u64; 8] {
149        self.repr.0
150    }
151
152    pub const fn from_repr(repr: [u64; 8]) -> Self {
153        Self { repr: U512(repr) }
154    }
155
156    pub fn is_zero(&self) -> bool {
157        self.repr.is_zero()
158    }
159
160    pub fn near_equal(self, other: Self) -> bool {
161        self.abs_diff(other).repr <= Self::REPR_EPSILON
162    }
163
164    #[must_use]
165    pub fn pow(self, mut exponent: i32) -> Self {
166        if exponent == 0 {
167            return Self::ONE;
168        }
169
170        let exponent_is_negative = if exponent < 0 {
171            exponent = -exponent;
172            true
173        } else {
174            false
175        };
176
177        let mut y = Self::ONE;
178        let mut x = self;
179
180        while exponent > 1 {
181            if exponent % 2 == 1 {
182                y *= x;
183            }
184            x *= x;
185            exponent >>= 1;
186        }
187
188        let result = x * y;
189
190        if exponent_is_negative {
191            Decimal::ONE / result
192        } else {
193            result
194        }
195    }
196
197    /// Calculates `2^exponent`.
198    pub fn pow2_int(exponent: u32) -> Option<Self> {
199        #[allow(clippy::cast_possible_truncation)]
200        if exponent > 512 - FRACTIONAL_BITS as u32 {
201            None
202        } else {
203            Some(Self {
204                repr: Self::ONE.repr << exponent,
205            })
206        }
207    }
208
209    fn pow2_frac(self) -> Self {
210        const MAX_ITERATIONS: u32 = 35; // n=35 is smallest n where n! >= 2^128
211        debug_assert!(self <= Self::ONE);
212
213        let mut sum = Self::ONE;
214        let mut term = Self::ONE;
215        let numerator = self * Self::LN2;
216
217        for n in 1..=MAX_ITERATIONS {
218            term *= numerator / n;
219            if term == Self::ZERO {
220                break;
221            }
222            sum += &term;
223        }
224
225        sum
226    }
227
228    pub fn pow2(self) -> Option<Self> {
229        let whole = u32::try_from(self.to_u128_floor()?).ok()?;
230        let frac = self - whole;
231
232        Some(Self::pow2_int(whole)? * Self::pow2_frac(frac))
233    }
234
235    #[must_use]
236    pub fn mul_pow10(self, exponent: i32) -> Option<Self> {
237        if exponent == 0 || self.is_zero() {
238            return Some(self);
239        }
240
241        let abs_exponent = exponent.abs_diff(0);
242        if (abs_exponent as usize) > WHOLE_DECIMAL_DIGITS + FRACTIONAL_DECIMAL_DIGITS {
243            return None;
244        }
245
246        #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
247        if exponent >= 0 {
248            let operand = u512_pow10(abs_exponent);
249            let shift_bits = u32::try_from(operand.bits()).ok()?;
250            // Avoid overflow.
251            if self.repr.leading_zeros() >= shift_bits {
252                let repr = self.repr * operand;
253                return Some(Self { repr });
254            }
255        } else {
256            let operand = u512_pow10(exponent.abs_diff(0));
257            let shift_bits = u32::try_from(operand.bits()).ok()?.checked_add(1)?;
258            // Do allow precision loss, but don't allow going all the way to zero.
259            if self.repr.leading_zeros().checked_add(shift_bits)? < 512 {
260                let repr = self.repr / operand;
261                return Some(Self { repr });
262            }
263        }
264
265        None
266    }
267
268    #[must_use]
269    pub fn abs_diff(self, other: Self) -> Self {
270        if self > other {
271            self - other
272        } else {
273            other - self
274        }
275    }
276
277    pub fn to_u128_floor(self) -> Option<u128> {
278        let truncated = self.repr >> FRACTIONAL_BITS;
279        if truncated.bits() <= 128 {
280            Some(truncated.as_u128())
281        } else {
282            None
283        }
284    }
285
286    pub fn to_u128_ceil(self) -> Option<u128> {
287        let truncated = self.repr >> FRACTIONAL_BITS;
288        if truncated.bits() <= 128 {
289            if self.fractional_part().is_zero() {
290                Some(truncated.as_u128())
291            } else {
292                truncated.as_u128().checked_add(1)
293            }
294        } else {
295            None
296        }
297    }
298
299    #[allow(
300        clippy::cast_precision_loss,
301        clippy::cast_possible_truncation,
302        clippy::cast_possible_wrap,
303        reason = "Lossiness is acceptable for this function"
304    )]
305    pub fn to_f64_lossy(self) -> f64 {
306        let frac = self.repr.low_u128() as f64 / f64_pow2(FRACTIONAL_BITS as i32);
307        let low = (self.repr >> FRACTIONAL_BITS).low_u128() as f64;
308        let high = (self.repr >> (FRACTIONAL_BITS * 2)).low_u128() as f64 * f64_pow2(128);
309
310        high + low + frac
311    }
312
313    pub fn to_fixed(&self, precision: usize) -> String {
314        let precision = precision.min(FRACTIONAL_DECIMAL_DIGITS);
315        let (fractional_part, overflow) = self.fractional_part_to_dec_string(precision, false);
316        let fractional_part_trimmed = fractional_part.trim_end_matches('0');
317        let repr = if overflow {
318            self.repr.saturating_add(Self::ONE.repr)
319        } else {
320            self.repr
321        };
322        if fractional_part_trimmed.is_empty() {
323            format!("{}", repr >> FRACTIONAL_BITS)
324        } else {
325            format!("{}.{fractional_part_trimmed}", repr >> FRACTIONAL_BITS)
326        }
327    }
328
329    fn fractional_part(&self) -> U512 {
330        U512([self.repr.0[0], self.repr.0[1], 0, 0, 0, 0, 0, 0])
331    }
332
333    pub fn fractional_part_as_u128_dividend(&self) -> u128 {
334        u128::from(self.repr.0[0]) | (u128::from(self.repr.0[1]) << 64)
335    }
336
337    fn epsilon_round(repr: U512) -> U512 {
338        (repr + (Self::REPR_EPSILON >> 1)) & !(Self::REPR_EPSILON - 1)
339    }
340
341    fn fractional_part_to_dec_string(&self, precision: usize, round_up: bool) -> (String, bool) {
342        let mut s = Vec::with_capacity(precision);
343        let mut f = self.fractional_part();
344        let mut overflow = false;
345
346        if round_up {
347            let plus_two = f.saturating_add(2.into());
348            overflow = plus_two.0[2] != 0;
349            f = U512([plus_two.0[0], plus_two.0[1], 0, 0, 0, 0, 0, 0]);
350        }
351
352        for _ in 0..precision {
353            if f.is_zero() {
354                break;
355            }
356
357            f *= 10;
358
359            let digit = (f / Self::ONE.repr).low_u64();
360            #[allow(clippy::cast_possible_truncation)]
361            s.push(digit as u8 + b'0');
362
363            f %= Self::ONE.repr;
364        }
365
366        if !round_up && !f.is_zero() && (U512::MAX - 2 >= self.repr) {
367            return self.fractional_part_to_dec_string(precision, true);
368        }
369
370        // Safety: all digits are guaranteed to be in range 0x30..=0x39
371        (unsafe { String::from_utf8_unchecked(s) }, overflow)
372    }
373}
374
375#[inline]
376#[allow(clippy::cast_sign_loss)]
377/// Calculates 2^exponent as an f64
378const fn f64_pow2(exponent: i32) -> f64 {
379    debug_assert!(
380        !(exponent < -1022 || exponent > 1023),
381        "Exponent out of range for f64"
382    );
383    f64::from_bits(((1023 + exponent) as u64) << 52)
384}
385
386pub mod error {
387    use thiserror::Error;
388
389    #[derive(Debug, Error)]
390    #[error("Failed to parse decimal")]
391    pub struct DecimalParseError;
392}
393
394impl FromStr for Decimal {
395    type Err = error::DecimalParseError;
396
397    fn from_str(s: &str) -> Result<Self, Self::Err> {
398        let (whole, frac) = if let Some((whole, frac)) = s.split_once('.') {
399            (whole, Some(frac))
400        } else {
401            (s, None)
402        };
403
404        let whole =
405            U512::from_dec_str(whole).map_err(|_| error::DecimalParseError)? << FRACTIONAL_BITS;
406
407        if let Some(frac) = frac {
408            let mut f = U512::zero();
409            let mut div = 10u128;
410
411            for c in frac.chars().take(FRACTIONAL_DECIMAL_DIGITS) {
412                if let Some(d) = c.to_digit(10) {
413                    if d != 0 {
414                        let d = (U512::from(d) << (FRACTIONAL_BITS * 2)) / div;
415                        f += d;
416                    }
417                    if let Some(next_div) = div.checked_mul(10) {
418                        div = next_div;
419                    } else {
420                        break;
421                    }
422                } else {
423                    break;
424                }
425            }
426
427            Ok(Self {
428                repr: whole.saturating_add(Decimal::epsilon_round(f >> FRACTIONAL_BITS)),
429            })
430        } else {
431            Ok(Self { repr: whole })
432        }
433    }
434}
435
436impl Display for Decimal {
437    fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
438        write!(f, "{}", self.to_f64_lossy())
439    }
440}
441
442impl Debug for Decimal {
443    fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
444        write!(f, "{}", self.to_fixed(FRACTIONAL_DECIMAL_DIGITS))
445    }
446}
447
448macro_rules! impl_self {
449    ($s:ty,$t:ty) => {
450        impl Add<$t> for $s {
451            type Output = Decimal;
452
453            fn add(self, rhs: $t) -> Decimal {
454                Decimal {
455                    repr: self.repr.add(rhs.repr),
456                }
457            }
458        }
459
460        impl Sub<$t> for $s {
461            type Output = Decimal;
462
463            fn sub(self, rhs: $t) -> Decimal {
464                Decimal {
465                    repr: self.repr.sub(rhs.repr),
466                }
467            }
468        }
469
470        impl Mul<$t> for $s {
471            type Output = Decimal;
472
473            fn mul(self, rhs: $t) -> Decimal {
474                #[allow(clippy::cast_possible_truncation)]
475                let mut shr = FRACTIONAL_BITS as u32;
476                let shr_self = self.repr.trailing_zeros().min(shr);
477                let self_repr = self.repr >> shr_self;
478                shr -= shr_self;
479                let shr_rhs = rhs.repr.trailing_zeros().min(shr);
480                let rhs_repr = rhs.repr >> shr_rhs;
481                shr -= shr_rhs;
482                Decimal {
483                    repr: (self_repr * rhs_repr) >> shr,
484                }
485            }
486        }
487
488        impl Div<$t> for $s {
489            type Output = Decimal;
490
491            fn div(self, rhs: $t) -> Decimal {
492                #[allow(clippy::cast_possible_truncation)]
493                let mut sh = FRACTIONAL_BITS as u32;
494                let sh_self = self.repr.leading_zeros().min(sh);
495                let self_repr = self.repr << sh_self;
496                sh -= sh_self;
497                let sh_rhs = rhs.repr.trailing_zeros().min(sh);
498                let rhs_repr = rhs.repr >> sh_rhs;
499                sh -= sh_rhs;
500                Decimal {
501                    repr: (self_repr / rhs_repr) << sh,
502                }
503            }
504        }
505    };
506}
507
508impl_self!(Decimal, Decimal);
509impl_self!(&Decimal, Decimal);
510impl_self!(Decimal, &Decimal);
511impl_self!(&Decimal, &Decimal);
512
513macro_rules! impl_self_assign {
514    ($s:ty,$t:ty) => {
515        impl AddAssign<$t> for $s {
516            fn add_assign(&mut self, rhs: $t) {
517                self.repr += rhs.repr;
518            }
519        }
520
521        impl SubAssign<$t> for $s {
522            fn sub_assign(&mut self, rhs: $t) {
523                self.repr -= rhs.repr;
524            }
525        }
526
527        impl DivAssign<$t> for $s {
528            fn div_assign(&mut self, rhs: $t) {
529                self.repr = (*self / rhs).repr;
530            }
531        }
532
533        impl MulAssign<$t> for $s {
534            fn mul_assign(&mut self, rhs: $t) {
535                self.repr = (*self * rhs).repr;
536            }
537        }
538    };
539}
540
541impl_self_assign!(Decimal, Decimal);
542impl_self_assign!(Decimal, &Decimal);
543
544macro_rules! impl_int {
545    ($t:ty) => {
546        impl_int!(@from $t);
547        impl_int!(@ops $t, Decimal);
548        impl_int!(@ops $t, &Decimal);
549    };
550
551    (@from $t:ty) => {
552        impl From<$t> for Decimal {
553            fn from(value: $t) -> Self {
554                Self {
555                    repr: U512::from(value) << FRACTIONAL_BITS,
556                }
557            }
558        }
559    };
560
561    (@ops $t:ty,$s:ty) => {
562        impl Mul<$t> for $s {
563            type Output = Decimal;
564
565            fn mul(self, rhs: $t) -> Decimal {
566                Decimal { repr: self.repr * U512::from(rhs) }
567            }
568        }
569
570        impl Mul<$s> for $t {
571            type Output = Decimal;
572
573            fn mul(self, rhs: $s) -> Decimal {
574                Decimal { repr: U512::from(self) * rhs.repr }
575            }
576        }
577
578        impl Div<$t> for $s {
579            type Output = Decimal;
580
581            fn div(self, rhs: $t) -> Decimal {
582                Decimal { repr: self.repr / U512::from(rhs) }
583            }
584        }
585
586        impl Div<$s> for $t {
587            type Output = Decimal;
588
589            fn div(self, rhs: $s) -> Decimal {
590                Decimal::from(self) / rhs
591            }
592        }
593
594        impl Add<$t> for $s {
595            type Output = Decimal;
596
597            fn add(self, rhs: $t) -> Decimal {
598                self + Decimal::from(rhs)
599            }
600        }
601
602        impl Add<$s> for $t {
603            type Output = Decimal;
604
605            fn add(self, rhs: $s) -> Decimal {
606                Decimal::from(self) + rhs
607            }
608        }
609
610        impl Sub<$t> for $s {
611            type Output = Decimal;
612
613            fn sub(self, rhs: $t) -> Decimal {
614                self - Decimal::from(rhs)
615            }
616        }
617
618        impl Sub<$s> for $t {
619            type Output = Decimal;
620
621            fn sub(self, rhs: $s) -> Decimal {
622                Decimal::from(self) - rhs
623            }
624        }
625
626        impl PartialEq<$t> for $s {
627            fn eq(&self, other: &$t) -> bool {
628                self.repr == Decimal::from(*other).repr
629            }
630        }
631
632        impl PartialOrd<$t> for $s {
633            fn partial_cmp(&self, other: &$t) -> Option<core::cmp::Ordering> {
634                self.repr.partial_cmp(&Decimal::from(*other).repr)
635            }
636        }
637    };
638}
639
640impl_int!(u8);
641impl_int!(u16);
642impl_int!(u32);
643impl_int!(u64);
644impl_int!(u128);
645impl_int!(::primitive_types::U256);
646
647macro_rules! impl_from_const {
648    ($t:ty,$name:ident) => {
649        impl Decimal {
650            pub const fn $name(value: $t) -> Self {
651                Self {
652                    repr: U512([0, 0, value as u64, 0, 0, 0, 0, 0]),
653                }
654            }
655        }
656    };
657}
658
659impl_from_const!(u8, from_u8);
660impl_from_const!(u16, from_u16);
661impl_from_const!(u32, from_u32);
662impl_from_const!(u64, from_u64);
663
664#[cfg(test)]
665mod tests {
666    use alloc::string::ToString;
667    use primitive_types::U256;
668    use rand::Rng;
669    use rstest::rstest;
670
671    use super::*;
672
673    macro_rules! println {
674        ($($arg:tt)*) => {
675            #[cfg(feature = "std")]
676            std::println!($($arg)*);
677            #[cfg(not(feature = "std"))]
678            let _ = format_args!($($arg)*);
679        };
680    }
681
682    // These functions are intentionally implemented using mathematical
683    // operations instead of bitwise operations, so as to test the
684    // correctness of the mathematical operators.
685
686    fn with_upper_u128(n: u128) -> Decimal {
687        let mut d = Decimal::from(n);
688        d *= Decimal::from(u128::pow(2, 64));
689        d *= Decimal::from(u128::pow(2, 64));
690        d
691    }
692
693    fn get_upper_u128(mut d: Decimal) -> u128 {
694        d /= Decimal::from(u128::pow(2, 64));
695        d /= Decimal::from(u128::pow(2, 64));
696        d.to_u128_floor().unwrap()
697    }
698
699    #[rstest]
700    #[test]
701    fn const_constructors_are_correct() {
702        assert_eq!(Decimal::from_u8(0).to_u128_ceil().unwrap(), 0);
703        assert_eq!(Decimal::from_u8(u8::MAX).to_u128_ceil().unwrap(), 0xff);
704        assert_eq!(Decimal::from_u16(0).to_u128_ceil().unwrap(), 0);
705        assert_eq!(Decimal::from_u16(u16::MAX).to_u128_ceil().unwrap(), 0xffff);
706        assert_eq!(Decimal::from_u32(0).to_u128_ceil().unwrap(), 0);
707        assert_eq!(
708            Decimal::from_u32(u32::MAX).to_u128_ceil().unwrap(),
709            0xffff_ffff,
710        );
711        assert_eq!(Decimal::from_u64(0).to_u128_ceil().unwrap(), 0);
712        assert_eq!(
713            Decimal::from_u64(u64::MAX).to_u128_ceil().unwrap(),
714            0xffff_ffff_ffff_ffff,
715        );
716    }
717
718    #[rstest]
719    #[case(0, 0)]
720    #[case(0, 1)]
721    #[case(1, 0)]
722    #[case(1, 1)]
723    #[case(2_934_570_000_008_u128, 9_595_959_283_u128)]
724    #[case(u128::MAX, 0)]
725    #[case(0, u128::MAX)]
726    #[test]
727    fn addition(#[case] a: u128, #[case] b: u128) {
728        assert_eq!(Decimal::from(a) + Decimal::from(b), a + b);
729        assert_eq!(
730            get_upper_u128(with_upper_u128(a) + with_upper_u128(b)),
731            a + b,
732        );
733    }
734
735    #[rstest]
736    #[case(0, 0)]
737    #[case(1, 0)]
738    #[case(1, 1)]
739    #[case(2_934_570_000_008_u128, 9_595_959_283_u128)]
740    #[case(u128::MAX, 0)]
741    #[case(u128::MAX, 1)]
742    #[case(u128::MAX, u128::MAX / 2)]
743    #[case(u128::MAX, u128::MAX)]
744    #[test]
745    fn subtraction(#[case] a: u128, #[case] b: u128) {
746        assert_eq!(Decimal::from(a) - Decimal::from(b), a - b);
747        assert_eq!(
748            get_upper_u128(with_upper_u128(a) - with_upper_u128(b)),
749            a - b,
750        );
751    }
752
753    #[rstest]
754    #[case(0, 0)]
755    #[case(0, 1)]
756    #[case(1, 0)]
757    #[case(1, 1)]
758    #[case(2, 2)]
759    #[case(u128::MAX, 0)]
760    #[case(u128::MAX, 1)]
761    #[case(0, u128::MAX)]
762    #[case(1, u128::MAX)]
763    #[test]
764    fn multiplication(#[case] a: u128, #[case] b: u128) {
765        assert_eq!(Decimal::from(a) * Decimal::from(b), a * b);
766        assert_eq!(get_upper_u128(with_upper_u128(a) * b), a * b);
767        assert_eq!(get_upper_u128(a * with_upper_u128(b)), a * b);
768    }
769
770    #[rstest]
771    #[case(0, 1)]
772    #[case(1, 1)]
773    #[case(1, 2)]
774    #[case(u128::MAX, u128::MAX)]
775    #[case(u128::MAX, 1)]
776    #[case(0, u128::MAX)]
777    #[case(1, u128::MAX)]
778    #[case(1, 10)]
779    #[case(3, 10_000)]
780    #[test]
781    fn division(#[case] a: u128, #[case] b: u128) {
782        #[allow(clippy::cast_precision_loss)]
783        let quotient = a as f64 / b as f64;
784        let abs_difference_lte = |d: Decimal, f: f64| (d.to_f64_lossy() - f).abs() <= 1e-200;
785        assert!(abs_difference_lte(
786            Decimal::from(a) / Decimal::from(b),
787            quotient,
788        ));
789        assert!(abs_difference_lte(
790            with_upper_u128(a) / with_upper_u128(b),
791            quotient,
792        ));
793    }
794
795    #[rstest]
796    #[case(12, 2)]
797    #[case(2, 32)]
798    #[case(1, 0)]
799    #[case(0, 0)]
800    #[case(0, 1)]
801    #[case(1, 1)]
802    #[test]
803    fn power(#[case] x: u128, #[case] n: u32) {
804        #[allow(clippy::cast_possible_wrap)]
805        let n_i32 = n as i32;
806        assert_eq!(Decimal::from(x).pow(n_i32), Decimal::from(x.pow(n)));
807    }
808
809    #[test]
810    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
811    fn pow10_valid_range() {
812        assert_eq!(
813            Decimal::ONE.mul_pow10(-(FRACTIONAL_DECIMAL_DIGITS as i32) - 1),
814            None,
815        );
816        for i in -(FRACTIONAL_DECIMAL_DIGITS as i32)..=(WHOLE_DECIMAL_DIGITS as i32) {
817            println!("10^{i} = {:?}", Decimal::ONE.mul_pow10(i).unwrap());
818        }
819        assert_eq!(
820            Decimal::ONE.mul_pow10((WHOLE_DECIMAL_DIGITS as i32) + 1),
821            None,
822        );
823    }
824
825    #[rstest]
826    #[case(0, 0)]
827    #[case(0, 1)]
828    #[case(0, -1)]
829    #[case(1, 0)]
830    #[case(1, 1)]
831    #[case(1, -1)]
832    #[case(1, i32::try_from(WHOLE_DECIMAL_DIGITS).unwrap())]
833    #[case(1, i32::try_from(FRACTIONAL_DECIMAL_DIGITS).unwrap())]
834    #[case(1, -i32::try_from(FRACTIONAL_DECIMAL_DIGITS).unwrap())]
835    #[case(12, 20)]
836    #[case(12, 0)]
837    #[case(12, -20)]
838    #[case(u128::MAX, 0)]
839    #[case(u128::MAX, -20)]
840    #[test]
841    fn mul_pow10(#[case] x: u128, #[case] n: i32) {
842        #[allow(clippy::cast_sign_loss)]
843        if n >= 0 {
844            assert_eq!(
845                Decimal::from(x).mul_pow10(n).unwrap(),
846                Decimal::from(x) * Decimal::from(10u32).pow(n),
847            );
848        } else {
849            assert!(Decimal::from(x)
850                .mul_pow10(n)
851                .unwrap()
852                .near_equal(Decimal::from(x) / U256::exp10(-n as usize)));
853        }
854    }
855
856    #[test]
857    fn constants_are_accurate() {
858        assert_eq!(Decimal::ZERO.to_u128_floor().unwrap(), 0);
859        assert!((Decimal::ONE_HALF.to_f64_lossy() - 0.5_f64).abs() < 1e-200);
860        assert_eq!(Decimal::ONE.to_u128_floor().unwrap(), 1);
861        assert_eq!(Decimal::TWO.to_u128_floor().unwrap(), 2);
862    }
863
864    #[rstest]
865    #[case(Decimal::ONE, 0)]
866    #[case(Decimal::ONE_HALF, 1u128 << 127)]
867    #[test]
868    fn get_fractional_dividend(#[case] value: Decimal, #[case] expected: u128) {
869        assert_eq!(value.fractional_part_as_u128_dividend(), expected);
870    }
871
872    #[cfg(feature = "serde")]
873    #[rstest]
874    #[case(Decimal::ONE)]
875    #[case(Decimal::TWO)]
876    #[case(Decimal::ZERO)]
877    #[case(Decimal::ONE_HALF)]
878    #[case(Decimal::from(u128::MAX))]
879    #[case(Decimal::from(u64::MAX) / Decimal::from(u128::MAX))]
880    #[test]
881    fn serialization(#[case] value: Decimal) {
882        let serialized = serde_json::to_string(&value).unwrap();
883        let deserialized: Decimal = serde_json::from_str(&serialized).unwrap();
884
885        assert!(value.near_equal(deserialized));
886    }
887
888    #[test]
889    fn from_self_string_serialization_precision() {
890        const ITERATIONS: usize = 1_024;
891        const TRANSFORMATIONS: usize = 16;
892
893        let mut rng = rand::thread_rng();
894
895        let mut max_error = U512::zero();
896        let mut error_distribution = [0u32; 16];
897        let mut value_with_max_error = Decimal::ZERO;
898
899        #[allow(clippy::cast_possible_truncation)]
900        for _ in 0..ITERATIONS {
901            let actual = Decimal {
902                repr: U512(rng.gen()),
903            };
904
905            let mut s = actual.to_fixed(FRACTIONAL_DECIMAL_DIGITS);
906            for _ in 0..(TRANSFORMATIONS - 1) {
907                s = Decimal::from_str(&s)
908                    .unwrap()
909                    .to_fixed(FRACTIONAL_DECIMAL_DIGITS);
910            }
911            let parsed = Decimal::from_str(&s).unwrap();
912
913            let e = actual.abs_diff(parsed).repr;
914
915            if e > max_error {
916                max_error = e;
917                value_with_max_error = actual;
918            }
919
920            error_distribution[e.0[0] as usize] += 1;
921        }
922
923        println!("Error distribution:");
924        for (i, x) in error_distribution.iter().enumerate() {
925            println!("\t{i}: {x:b}");
926        }
927        println!("Max error: {:?}", max_error.0);
928
929        assert!(
930            max_error <= Decimal::REPR_EPSILON,
931            "Stringification error of repr {:?} is repr {:?}",
932            value_with_max_error.repr.0,
933            max_error.0,
934        );
935    }
936
937    #[test]
938    #[allow(clippy::cast_precision_loss)]
939    fn from_f64_string_serialization_precision() {
940        const ITERATIONS: usize = 10_000;
941        let mut rng = rand::thread_rng();
942        let epsilon = Decimal {
943            repr: Decimal::REPR_EPSILON,
944        }
945        .to_f64_lossy();
946
947        let t = |f: f64| {
948            let actual = f.abs();
949            let string = actual.to_string();
950            let parsed = Decimal::from_str(&string).unwrap();
951
952            let e = (parsed.to_f64_lossy() - actual).abs();
953
954            assert!(e <= epsilon, "Stringification error of f64 {actual} is {e}");
955        };
956
957        for _ in 0..ITERATIONS {
958            t(rng.gen::<f64>() * rng.gen::<u128>() as f64);
959        }
960    }
961
962    #[test]
963    fn round_up_repr() {
964        let cases = [
965            Decimal {
966                #[rustfmt::skip]
967                repr: U512([ 0x0966_4E4C_9169_501F, 0xB226_2812_5CF2_3CD0, 1, 0, 0, 0, 0, 0 ]),
968            },
969            Decimal {
970                repr: U512([u64::MAX, u64::MAX, 1, 0, 0, 0, 0, 0]),
971                // 1.99999999999999999999999999999999999999706126412294428123007815865694438580...
972            },
973            Decimal {
974                repr: U512([u64::MAX - 1, u64::MAX, 1, 0, 0, 0, 0, 0]),
975            },
976            Decimal { repr: U512::MAX },
977            Decimal {
978                repr: U512::MAX.saturating_sub(U512::one()),
979            },
980            Decimal { repr: U512::zero() },
981        ];
982
983        for case in cases {
984            let p: Decimal = case.to_fixed(FRACTIONAL_DECIMAL_DIGITS).parse().unwrap();
985
986            println!("{:x?}", case.repr.0);
987            println!("{:x?}", p.repr.0);
988            println!("|{p:?} - {case:?}| = {:?}", p.abs_diff(case).as_repr());
989
990            assert!(p.near_equal(case));
991        }
992    }
993
994    #[test]
995    fn round_up_str() {
996        // Cases that are (generally) not evenly representable in binary fraction.
997        let cases = [
998            "1",
999            "0",
1000            "1.6958947224456518",
1001            "2.79",
1002            "0.6",
1003            "10.6",
1004            "0.01",
1005            "0.599999999999999999999999999999999999",
1006        ];
1007        for case in cases {
1008            println!("Testing {case}...");
1009            let n = Decimal::from_str(case).unwrap();
1010            let s = n.to_fixed(FRACTIONAL_DECIMAL_DIGITS);
1011            let parsed = Decimal::from_str(&s).unwrap();
1012            assert_eq!(n, parsed);
1013            println!("{n:?}");
1014            println!("");
1015        }
1016    }
1017
1018    #[test]
1019    #[allow(clippy::float_cmp)]
1020    fn const_pow2() {
1021        for i in -1022..=1023 {
1022            let f = f64_pow2(i);
1023            let e = 2f64.powi(i);
1024            assert_eq!(f, e);
1025        }
1026    }
1027}