icydb-schema 0.257.10

Bounded public schema proposal contract for IcyDB
Documentation
//! Canonical arbitrary-precision signed-integer atom.

use crate::{
    Decimal, NumericValue,
    integer_wire::{self, IntegerWire},
};
use candid::{CandidType, Int as WrappedInt};
use derive_more::{Add, AddAssign, Sub, SubAssign};
use num_bigint::BigInt;
use serde::{Deserialize, Serialize};
use std::{
    fmt,
    iter::{Product, Sum},
    ops::{Div, DivAssign, Mul, MulAssign, Neg},
    str::FromStr,
};

//
// IntBig
//

#[derive(
    Add,
    AddAssign,
    CandidType,
    Clone,
    Debug,
    Default,
    Eq,
    PartialEq,
    Hash,
    Ord,
    PartialOrd,
    Sub,
    SubAssign,
)]
/// Arbitrary-precision signed integer used by schema and typed values.
///
/// Candid uses its native integer type; human-readable Serde uses decimal text.
/// Binary Serde uses native small integers and tagged little-endian wide bytes.
pub struct IntBig(WrappedInt);

impl IntBig {
    /// Return the magnitude's bit length without allocating an encoded copy.
    #[must_use]
    pub fn magnitude_bits(&self) -> u64 {
        self.0.0.bits()
    }

    /// Return the exact signed LEB128 byte length without allocating or encoding.
    #[must_use]
    pub fn leb128_len(&self) -> u64 {
        let bits = self.magnitude_bits();
        // Signed groups reserve a sign bit. A negative power of two needs one
        // fewer bit than the corresponding positive value (-64 fits; 64 does
        // not). Only a seven-bit boundary can change the resulting byte count.
        let negative_boundary = self.0.0.sign() == num_bigint::Sign::Minus
            && bits.is_multiple_of(7)
            && self.0.0.trailing_zeros() == Some(bits.saturating_sub(1));
        bits / 7 + 1 - u64::from(negative_boundary)
    }

    /// Construct from the canonical Candid signed-integer representation.
    #[must_use]
    pub const fn from_candid(value: WrappedInt) -> Self {
        Self(value)
    }

    /// Construct from a `num_bigint` signed integer.
    #[must_use]
    pub fn from_bigint(value: BigInt) -> Self {
        Self::from_candid(WrappedInt::from(value))
    }

    /// Borrow sign and little-endian base-2^32 magnitude limbs without allocation.
    #[must_use]
    pub fn sign_and_u32_digits(
        &self,
    ) -> (
        bool,
        impl DoubleEndedIterator<Item = u32> + ExactSizeIterator + '_,
    ) {
        (
            self.0.0.sign() == num_bigint::Sign::Minus,
            self.0.0.magnitude().iter_u32_digits(),
        )
    }

    /// Convert to `i128` when the value is in range.
    #[must_use]
    pub fn to_i128(&self) -> Option<i128> {
        let big = &self.0.0;

        i128::try_from(big).ok()
    }

    /// Convert to `i64` when the value is in range.
    #[must_use]
    pub fn to_i64(&self) -> Option<i64> {
        let big = &self.0.0;

        i64::try_from(big).ok()
    }

    /// Serialize this arbitrary-precision integer for internal hash and sort-key framing.
    #[must_use]
    pub fn to_leb128(&self) -> Vec<u8> {
        self.leb128_bytes().collect()
    }

    /// Iterate canonical signed LEB128 bytes with constant scratch and no allocation.
    pub fn leb128_bytes(&self) -> impl Iterator<Item = u8> + '_ {
        let (negative, limbs) = self.sign_and_u32_digits();
        crate::leb128::bytes(limbs, negative, self.leb128_len())
    }

    pub(crate) fn to_sign_and_magnitude_bytes(&self) -> (bool, Vec<u8>) {
        let (sign, magnitude) = self.0.0.to_bytes_be();
        (sign == num_bigint::Sign::Minus, magnitude)
    }

    pub(crate) fn from_sign_and_magnitude_bytes(negative: bool, magnitude: &[u8]) -> Self {
        let sign = if magnitude.is_empty() {
            num_bigint::Sign::NoSign
        } else if negative {
            num_bigint::Sign::Minus
        } else {
            num_bigint::Sign::Plus
        };
        Self::from_bigint(BigInt::from_bytes_be(sign, magnitude))
    }

    /// Saturating addition (unbounded; equivalent to normal addition).
    #[must_use]
    pub fn saturating_add(self, rhs: Self) -> Self {
        Self(self.0 + rhs.0)
    }

    /// Saturating subtraction (unbounded; equivalent to normal subtraction).
    #[must_use]
    pub fn saturating_sub(self, rhs: Self) -> Self {
        Self(self.0 - rhs.0)
    }
}

impl<'de> Deserialize<'de> for IntBig {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        integer_wire::deserialize_integer(deserializer)
    }
}

impl fmt::Display for IntBig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl FromStr for IntBig {
    type Err = <WrappedInt as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        WrappedInt::from_str(s).map(Self::from_candid)
    }
}

impl Div for IntBig {
    type Output = Self;

    fn div(self, other: Self) -> Self::Output {
        Self(self.0 / other.0)
    }
}

impl DivAssign for IntBig {
    fn div_assign(&mut self, other: Self) {
        self.0 /= other.0;
    }
}

impl From<i32> for IntBig {
    fn from(n: i32) -> Self {
        Self::from_candid(WrappedInt::from(n))
    }
}

impl From<i64> for IntBig {
    fn from(n: i64) -> Self {
        Self::from_candid(WrappedInt::from(n))
    }
}

impl IntegerWire for IntBig {
    fn from_signed(value: i64) -> Option<Self> {
        Some(Self::from(value))
    }

    fn from_unsigned(value: u64) -> Self {
        Self::from_candid(WrappedInt::from(value))
    }

    fn from_wire_bytes(value: &[u8]) -> Option<Self> {
        integer_wire::signed_body(value)
            .map(|body| Self::from_bigint(BigInt::from_signed_bytes_le(body)))
    }
}

impl Mul for IntBig {
    type Output = Self;

    fn mul(self, other: Self) -> Self::Output {
        Self(self.0 * other.0)
    }
}

impl MulAssign for IntBig {
    fn mul_assign(&mut self, other: Self) {
        self.0 *= other.0;
    }
}

impl Neg for IntBig {
    type Output = Self;

    fn neg(self) -> Self::Output {
        Self::from_bigint(-self.0.0)
    }
}

impl NumericValue for IntBig {
    fn try_to_decimal(&self) -> Option<Decimal> {
        self.to_i128().and_then(Decimal::from_i128)
    }

    fn try_from_decimal(value: Decimal) -> Option<Self> {
        value.to_i128().map(WrappedInt::from).map(Self::from_candid)
    }
}

impl Product for IntBig {
    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::from(1), |acc, value| acc * value)
    }
}

impl Serialize for IntBig {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        if serializer.is_human_readable() {
            return serializer.collect_str(&self.0.0);
        }
        if let Some(value) = self.to_i64() {
            return serializer.serialize_i64(value);
        }
        if let Ok(value) = u64::try_from(&self.0.0) {
            return serializer.serialize_u64(value);
        }
        let (negative, limbs) = self.sign_and_u32_digits();
        serializer.serialize_bytes(&integer_wire::signed_bytes(negative, limbs))
    }
}

impl Sum for IntBig {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::default(), |acc, x| acc + x)
    }
}