Skip to main content

icydb_schema/
int_big.rs

1//! Canonical arbitrary-precision signed-integer atom.
2
3use crate::{Decimal, NumericValue};
4use candid::{CandidType, Int as WrappedInt};
5use derive_more::{Add, AddAssign, Sub, SubAssign};
6use num_bigint::BigInt;
7use serde::{Deserialize, Serialize};
8use std::{
9    fmt,
10    iter::{Product, Sum},
11    ops::{Div, DivAssign, Mul, MulAssign, Neg},
12    str::FromStr,
13};
14
15//
16// IntBig
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 signed integer used by schema and typed values.
37pub struct IntBig(WrappedInt);
38
39impl IntBig {
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 signed LEB128 byte length without allocating or encoding.
47    #[must_use]
48    pub fn leb128_len(&self) -> u64 {
49        let bits = self.magnitude_bits();
50        // Signed groups reserve a sign bit. A negative power of two needs one
51        // fewer bit than the corresponding positive value (-64 fits; 64 does
52        // not). Only a seven-bit boundary can change the resulting byte count.
53        let negative_boundary = self.0.0.sign() == num_bigint::Sign::Minus
54            && bits.is_multiple_of(7)
55            && self.0.0.trailing_zeros() == Some(bits.saturating_sub(1));
56        bits / 7 + 1 - u64::from(negative_boundary)
57    }
58
59    /// Construct from the canonical Candid signed-integer representation.
60    #[must_use]
61    pub const fn from_candid(value: WrappedInt) -> Self {
62        Self(value)
63    }
64
65    /// Construct from a `num_bigint` signed integer.
66    #[must_use]
67    pub fn from_bigint(value: BigInt) -> Self {
68        Self::from_candid(WrappedInt::from(value))
69    }
70
71    /// Borrow sign and little-endian base-2^32 magnitude limbs without allocation.
72    #[must_use]
73    pub fn sign_and_u32_digits(
74        &self,
75    ) -> (
76        bool,
77        impl DoubleEndedIterator<Item = u32> + ExactSizeIterator + '_,
78    ) {
79        (
80            self.0.0.sign() == num_bigint::Sign::Minus,
81            self.0.0.magnitude().iter_u32_digits(),
82        )
83    }
84
85    /// Convert to `i128` when the value is in range.
86    #[must_use]
87    pub fn to_i128(&self) -> Option<i128> {
88        let big = &self.0.0;
89
90        i128::try_from(big).ok()
91    }
92
93    /// Convert to `i64` when the value is in range.
94    #[must_use]
95    pub fn to_i64(&self) -> Option<i64> {
96        let big = &self.0.0;
97
98        i64::try_from(big).ok()
99    }
100
101    /// Serialize this arbitrary-precision integer for internal hash and sort-key framing.
102    #[must_use]
103    pub fn to_leb128(&self) -> Vec<u8> {
104        self.leb128_bytes().collect()
105    }
106
107    /// Iterate canonical signed LEB128 bytes with constant scratch and no allocation.
108    pub fn leb128_bytes(&self) -> impl Iterator<Item = u8> + '_ {
109        let (negative, limbs) = self.sign_and_u32_digits();
110        crate::leb128::bytes(limbs, negative, self.leb128_len())
111    }
112
113    pub(crate) fn to_sign_and_magnitude_bytes(&self) -> (bool, Vec<u8>) {
114        let (sign, magnitude) = self.0.0.to_bytes_be();
115        (sign == num_bigint::Sign::Minus, magnitude)
116    }
117
118    pub(crate) fn from_sign_and_magnitude_bytes(negative: bool, magnitude: &[u8]) -> Self {
119        let sign = if magnitude.is_empty() {
120            num_bigint::Sign::NoSign
121        } else if negative {
122            num_bigint::Sign::Minus
123        } else {
124            num_bigint::Sign::Plus
125        };
126        Self::from_bigint(BigInt::from_bytes_be(sign, magnitude))
127    }
128
129    /// Saturating addition (unbounded; equivalent to normal addition).
130    #[must_use]
131    pub fn saturating_add(self, rhs: Self) -> Self {
132        Self(self.0 + rhs.0)
133    }
134
135    /// Saturating subtraction (unbounded; equivalent to normal subtraction).
136    #[must_use]
137    pub fn saturating_sub(self, rhs: Self) -> Self {
138        Self(self.0 - rhs.0)
139    }
140}
141
142impl fmt::Display for IntBig {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        self.0.fmt(f)
145    }
146}
147
148impl FromStr for IntBig {
149    type Err = <WrappedInt as FromStr>::Err;
150
151    fn from_str(s: &str) -> Result<Self, Self::Err> {
152        WrappedInt::from_str(s).map(Self::from_candid)
153    }
154}
155
156impl Div for IntBig {
157    type Output = Self;
158
159    fn div(self, other: Self) -> Self::Output {
160        Self(self.0 / other.0)
161    }
162}
163
164impl DivAssign for IntBig {
165    fn div_assign(&mut self, other: Self) {
166        self.0 /= other.0;
167    }
168}
169
170impl From<i32> for IntBig {
171    fn from(n: i32) -> Self {
172        Self::from_candid(WrappedInt::from(n))
173    }
174}
175
176impl From<i64> for IntBig {
177    fn from(n: i64) -> Self {
178        Self::from_candid(WrappedInt::from(n))
179    }
180}
181
182impl Mul for IntBig {
183    type Output = Self;
184
185    fn mul(self, other: Self) -> Self::Output {
186        Self(self.0 * other.0)
187    }
188}
189
190impl MulAssign for IntBig {
191    fn mul_assign(&mut self, other: Self) {
192        self.0 *= other.0;
193    }
194}
195
196impl Neg for IntBig {
197    type Output = Self;
198
199    fn neg(self) -> Self::Output {
200        Self::from_bigint(-self.0.0)
201    }
202}
203
204impl NumericValue for IntBig {
205    fn try_to_decimal(&self) -> Option<Decimal> {
206        self.to_i128().and_then(Decimal::from_i128)
207    }
208
209    fn try_from_decimal(value: Decimal) -> Option<Self> {
210        value.to_i128().map(WrappedInt::from).map(Self::from_candid)
211    }
212}
213
214impl Product for IntBig {
215    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
216        iter.fold(Self::from(1), |acc, value| acc * value)
217    }
218}
219
220impl Sum for IntBig {
221    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
222        iter.fold(Self::default(), |acc, x| acc + x)
223    }
224}