Skip to main content

icydb_schema/
nat_big.rs

1//! Canonical arbitrary-precision unsigned-integer atom.
2
3use crate::{Decimal, NumericValue};
4use candid::{CandidType, Nat as WrappedNat};
5use derive_more::{Add, AddAssign, Sub, SubAssign};
6use num_bigint::BigUint;
7use serde::{Deserialize, Serialize};
8use std::{
9    fmt,
10    iter::{Product, Sum},
11    ops::{Div, DivAssign, Mul, MulAssign},
12    str::FromStr,
13};
14
15//
16// NatBig
17//
18
19#[derive(
20    Add,
21    AddAssign,
22    CandidType,
23    Clone,
24    Debug,
25    Default,
26    Eq,
27    PartialEq,
28    Hash,
29    Ord,
30    PartialOrd,
31    Serialize,
32    Deserialize,
33    Sub,
34    SubAssign,
35)]
36/// Arbitrary-precision unsigned integer used by schema and typed values.
37pub struct NatBig(WrappedNat);
38
39impl NatBig {
40    /// Return the magnitude's bit length without allocating an encoded copy.
41    #[must_use]
42    pub fn magnitude_bits(&self) -> u64 {
43        self.0.0.bits()
44    }
45
46    /// Return the exact unsigned LEB128 byte length without allocating or encoding.
47    #[must_use]
48    pub fn leb128_len(&self) -> u64 {
49        self.magnitude_bits().div_ceil(7).max(1)
50    }
51
52    /// Construct from the canonical Candid natural-number representation.
53    #[must_use]
54    pub const fn from_candid(value: WrappedNat) -> Self {
55        Self(value)
56    }
57
58    /// Construct from a `num_bigint` unsigned integer.
59    #[must_use]
60    pub fn from_biguint(value: BigUint) -> Self {
61        Self::from_candid(WrappedNat::from(value))
62    }
63
64    /// Borrow little-endian base-2^32 limbs without allocation.
65    #[must_use]
66    pub fn u32_digits(&self) -> impl DoubleEndedIterator<Item = u32> + ExactSizeIterator + '_ {
67        self.0.0.iter_u32_digits()
68    }
69
70    /// Convert to `u128` when the value is in range.
71    #[must_use]
72    pub fn to_u128(&self) -> Option<u128> {
73        let big = &self.0.0;
74
75        u128::try_from(big).ok()
76    }
77
78    /// Convert to `u64` when the value is in range.
79    #[must_use]
80    pub fn to_u64(&self) -> Option<u64> {
81        let big = &self.0.0;
82
83        u64::try_from(big).ok()
84    }
85
86    /// Serialize this arbitrary-precision natural for internal hash and sort-key framing.
87    #[must_use]
88    pub fn to_leb128(&self) -> Vec<u8> {
89        self.leb128_bytes().collect()
90    }
91
92    /// Iterate canonical unsigned LEB128 bytes with constant scratch and no allocation.
93    pub fn leb128_bytes(&self) -> impl Iterator<Item = u8> + '_ {
94        crate::leb128::bytes(self.u32_digits(), false, self.leb128_len())
95    }
96
97    pub(crate) fn to_magnitude_bytes(&self) -> Vec<u8> {
98        self.0.0.to_bytes_be()
99    }
100
101    pub(crate) fn from_magnitude_bytes(magnitude: &[u8]) -> Self {
102        Self::from_biguint(BigUint::from_bytes_be(magnitude))
103    }
104
105    /// Saturating addition (unbounded; equivalent to normal addition).
106    #[must_use]
107    pub fn saturating_add(self, rhs: Self) -> Self {
108        Self(self.0 + rhs.0)
109    }
110
111    /// Saturating subtraction; clamps at zero on underflow.
112    #[must_use]
113    pub fn saturating_sub(self, rhs: Self) -> Self {
114        if rhs > self {
115            return Self::default();
116        }
117
118        Self(self.0 - rhs.0)
119    }
120}
121
122impl fmt::Display for NatBig {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        self.0.fmt(f)
125    }
126}
127
128impl FromStr for NatBig {
129    type Err = <WrappedNat as FromStr>::Err;
130
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        WrappedNat::from_str(s).map(Self::from_candid)
133    }
134}
135
136impl Div for NatBig {
137    type Output = Self;
138
139    fn div(self, other: Self) -> Self::Output {
140        Self(self.0 / other.0)
141    }
142}
143
144impl DivAssign for NatBig {
145    fn div_assign(&mut self, other: Self) {
146        self.0 /= other.0;
147    }
148}
149
150impl From<u64> for NatBig {
151    fn from(n: u64) -> Self {
152        Self::from_candid(WrappedNat::from(n))
153    }
154}
155
156impl From<u32> for NatBig {
157    fn from(n: u32) -> Self {
158        Self::from_candid(WrappedNat::from(n))
159    }
160}
161
162impl Mul for NatBig {
163    type Output = Self;
164
165    fn mul(self, other: Self) -> Self::Output {
166        Self(self.0 * other.0)
167    }
168}
169
170impl MulAssign for NatBig {
171    fn mul_assign(&mut self, other: Self) {
172        self.0 *= other.0;
173    }
174}
175
176impl NumericValue for NatBig {
177    fn try_to_decimal(&self) -> Option<Decimal> {
178        self.to_u128().and_then(Decimal::from_u128)
179    }
180
181    fn try_from_decimal(value: Decimal) -> Option<Self> {
182        value.to_u128().map(WrappedNat::from).map(Self::from_candid)
183    }
184}
185
186impl Product for NatBig {
187    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
188        iter.fold(Self::from(1_u32), |acc, value| acc * value)
189    }
190}
191
192impl Sum for NatBig {
193    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
194        iter.fold(Self::default(), |acc, x| acc + x)
195    }
196}
197
198impl TryFrom<i32> for NatBig {
199    type Error = std::num::TryFromIntError;
200
201    fn try_from(n: i32) -> Result<Self, Self::Error> {
202        let v = Self::from_candid(WrappedNat::from(u32::try_from(n)?));
203        Ok(v)
204    }
205}