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