Skip to main content

icydb_schema/
int_big.rs

1//! Canonical arbitrary-precision signed-integer atom.
2
3use crate::{
4    Decimal, NumericValue,
5    integer_wire::{self, IntegerWire},
6};
7use candid::{CandidType, Int as WrappedInt};
8use derive_more::{Add, AddAssign, Sub, SubAssign};
9use num_bigint::BigInt;
10use serde::{Deserialize, Serialize};
11use std::{
12    fmt,
13    iter::{Product, Sum},
14    ops::{Div, DivAssign, Mul, MulAssign, Neg},
15    str::FromStr,
16};
17
18//
19// IntBig
20//
21
22#[derive(
23    Add,
24    AddAssign,
25    CandidType,
26    Clone,
27    Debug,
28    Default,
29    Eq,
30    PartialEq,
31    Hash,
32    Ord,
33    PartialOrd,
34    Sub,
35    SubAssign,
36)]
37/// Arbitrary-precision signed integer used by schema and typed values.
38///
39/// Candid uses its native integer type; human-readable Serde uses decimal text.
40/// Binary Serde uses native small integers and tagged little-endian wide bytes.
41pub struct IntBig(WrappedInt);
42
43impl IntBig {
44    /// Return the magnitude's bit length without allocating an encoded copy.
45    #[must_use]
46    pub fn magnitude_bits(&self) -> u64 {
47        self.0.0.bits()
48    }
49
50    /// Return the exact signed LEB128 byte length without allocating or encoding.
51    #[must_use]
52    pub fn leb128_len(&self) -> u64 {
53        let bits = self.magnitude_bits();
54        // Signed groups reserve a sign bit. A negative power of two needs one
55        // fewer bit than the corresponding positive value (-64 fits; 64 does
56        // not). Only a seven-bit boundary can change the resulting byte count.
57        let negative_boundary = self.0.0.sign() == num_bigint::Sign::Minus
58            && bits.is_multiple_of(7)
59            && self.0.0.trailing_zeros() == Some(bits.saturating_sub(1));
60        bits / 7 + 1 - u64::from(negative_boundary)
61    }
62
63    /// Construct from the canonical Candid signed-integer representation.
64    #[must_use]
65    pub const fn from_candid(value: WrappedInt) -> Self {
66        Self(value)
67    }
68
69    /// Construct from a `num_bigint` signed integer.
70    #[must_use]
71    pub fn from_bigint(value: BigInt) -> Self {
72        Self::from_candid(WrappedInt::from(value))
73    }
74
75    /// Borrow sign and little-endian base-2^32 magnitude limbs without allocation.
76    #[must_use]
77    pub fn sign_and_u32_digits(
78        &self,
79    ) -> (
80        bool,
81        impl DoubleEndedIterator<Item = u32> + ExactSizeIterator + '_,
82    ) {
83        (
84            self.0.0.sign() == num_bigint::Sign::Minus,
85            self.0.0.magnitude().iter_u32_digits(),
86        )
87    }
88
89    /// Convert to `i128` when the value is in range.
90    #[must_use]
91    pub fn to_i128(&self) -> Option<i128> {
92        let big = &self.0.0;
93
94        i128::try_from(big).ok()
95    }
96
97    /// Convert to `i64` when the value is in range.
98    #[must_use]
99    pub fn to_i64(&self) -> Option<i64> {
100        let big = &self.0.0;
101
102        i64::try_from(big).ok()
103    }
104
105    /// Serialize this arbitrary-precision integer for internal hash and sort-key framing.
106    #[must_use]
107    pub fn to_leb128(&self) -> Vec<u8> {
108        self.leb128_bytes().collect()
109    }
110
111    /// Iterate canonical signed LEB128 bytes with constant scratch and no allocation.
112    pub fn leb128_bytes(&self) -> impl Iterator<Item = u8> + '_ {
113        let (negative, limbs) = self.sign_and_u32_digits();
114        crate::leb128::bytes(limbs, negative, self.leb128_len())
115    }
116
117    pub(crate) fn to_sign_and_magnitude_bytes(&self) -> (bool, Vec<u8>) {
118        let (sign, magnitude) = self.0.0.to_bytes_be();
119        (sign == num_bigint::Sign::Minus, magnitude)
120    }
121
122    pub(crate) fn from_sign_and_magnitude_bytes(negative: bool, magnitude: &[u8]) -> Self {
123        let sign = if magnitude.is_empty() {
124            num_bigint::Sign::NoSign
125        } else if negative {
126            num_bigint::Sign::Minus
127        } else {
128            num_bigint::Sign::Plus
129        };
130        Self::from_bigint(BigInt::from_bytes_be(sign, magnitude))
131    }
132
133    /// Saturating addition (unbounded; equivalent to normal addition).
134    #[must_use]
135    pub fn saturating_add(self, rhs: Self) -> Self {
136        Self(self.0 + rhs.0)
137    }
138
139    /// Saturating subtraction (unbounded; equivalent to normal subtraction).
140    #[must_use]
141    pub fn saturating_sub(self, rhs: Self) -> Self {
142        Self(self.0 - rhs.0)
143    }
144}
145
146impl<'de> Deserialize<'de> for IntBig {
147    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
148        integer_wire::deserialize_integer(deserializer)
149    }
150}
151
152impl fmt::Display for IntBig {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        self.0.fmt(f)
155    }
156}
157
158impl FromStr for IntBig {
159    type Err = <WrappedInt as FromStr>::Err;
160
161    fn from_str(s: &str) -> Result<Self, Self::Err> {
162        WrappedInt::from_str(s).map(Self::from_candid)
163    }
164}
165
166impl Div for IntBig {
167    type Output = Self;
168
169    fn div(self, other: Self) -> Self::Output {
170        Self(self.0 / other.0)
171    }
172}
173
174impl DivAssign for IntBig {
175    fn div_assign(&mut self, other: Self) {
176        self.0 /= other.0;
177    }
178}
179
180impl From<i32> for IntBig {
181    fn from(n: i32) -> Self {
182        Self::from_candid(WrappedInt::from(n))
183    }
184}
185
186impl From<i64> for IntBig {
187    fn from(n: i64) -> Self {
188        Self::from_candid(WrappedInt::from(n))
189    }
190}
191
192impl IntegerWire for IntBig {
193    fn from_signed(value: i64) -> Option<Self> {
194        Some(Self::from(value))
195    }
196
197    fn from_unsigned(value: u64) -> Self {
198        Self::from_candid(WrappedInt::from(value))
199    }
200
201    fn from_wire_bytes(value: &[u8]) -> Option<Self> {
202        integer_wire::signed_body(value)
203            .map(|body| Self::from_bigint(BigInt::from_signed_bytes_le(body)))
204    }
205}
206
207impl Mul for IntBig {
208    type Output = Self;
209
210    fn mul(self, other: Self) -> Self::Output {
211        Self(self.0 * other.0)
212    }
213}
214
215impl MulAssign for IntBig {
216    fn mul_assign(&mut self, other: Self) {
217        self.0 *= other.0;
218    }
219}
220
221impl Neg for IntBig {
222    type Output = Self;
223
224    fn neg(self) -> Self::Output {
225        Self::from_bigint(-self.0.0)
226    }
227}
228
229impl NumericValue for IntBig {
230    fn try_to_decimal(&self) -> Option<Decimal> {
231        self.to_i128().and_then(Decimal::from_i128)
232    }
233
234    fn try_from_decimal(value: Decimal) -> Option<Self> {
235        value.to_i128().map(WrappedInt::from).map(Self::from_candid)
236    }
237}
238
239impl Product for IntBig {
240    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
241        iter.fold(Self::from(1), |acc, value| acc * value)
242    }
243}
244
245impl Serialize for IntBig {
246    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
247        if serializer.is_human_readable() {
248            return serializer.collect_str(&self.0.0);
249        }
250        if let Some(value) = self.to_i64() {
251            return serializer.serialize_i64(value);
252        }
253        if let Ok(value) = u64::try_from(&self.0.0) {
254            return serializer.serialize_u64(value);
255        }
256        let (negative, limbs) = self.sign_and_u32_digits();
257        serializer.serialize_bytes(&integer_wire::signed_bytes(negative, limbs))
258    }
259}
260
261impl Sum for IntBig {
262    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
263        iter.fold(Self::default(), |acc, x| acc + x)
264    }
265}