templar_common/
number.rs

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