use std::{
cmp::Ordering,
fmt::{self, Display},
mem,
ops::{Add, Mul, Neg, Sub},
sync::OnceLock,
};
use num_bigint::BigInt;
use num_traits::{FromPrimitive, Signed, ToPrimitive, Zero};
use crate::{
exception_private::{ExcType, RunResult},
hash::{HashValue, hash_python_long_int},
heap::{Heap, HeapData},
resource::{ResourceError, ResourceTracker},
value::Value,
};
pub(crate) const INT_MAX_STR_DIGITS: usize = 4300;
static INT_MAX_STR_DIGITS_THRESHOLD: OnceLock<BigInt> = OnceLock::new();
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct LongInt(pub BigInt);
impl LongInt {
pub fn new(bi: BigInt) -> Self {
Self(bi)
}
pub fn into_value(self, heap: &Heap<impl ResourceTracker>) -> Result<Value, ResourceError> {
if let Some(i) = self.0.to_i64() {
Ok(Value::Int(i))
} else {
let heap_id = heap.allocate(HeapData::LongInt(self))?;
Ok(Value::Ref(heap_id))
}
}
pub fn hash(&self) -> HashValue {
hash_python_long_int(&self.0)
}
pub fn estimate_size(&self) -> usize {
let bits = self.0.bits();
let bit_bytes = usize::try_from(bits).unwrap_or(usize::MAX).saturating_add(7) / 8;
bit_bytes + mem::size_of::<BigInt>()
}
pub fn inner(&self) -> &BigInt {
&self.0
}
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
pub fn is_negative(&self) -> bool {
self.0.is_negative()
}
pub fn to_i64(&self) -> Option<i64> {
self.0.to_i64()
}
pub fn to_f64(&self) -> Option<f64> {
self.0.to_f64()
}
pub fn partial_cmp_f64(&self, f: f64) -> Option<Ordering> {
bigint_cmp_f64(&self.0, f)
}
pub fn to_u32(&self) -> Option<u32> {
self.0.to_u32()
}
pub fn to_usize(&self) -> Option<usize> {
self.0.to_usize()
}
pub fn abs(&self) -> Self {
Self(self.0.abs())
}
pub fn bits(&self) -> u64 {
self.0.bits()
}
pub fn check_str_digits_limit(&self) -> RunResult<()> {
check_bigint_str_digits_limit(&self.0)
}
}
pub fn bigint_cmp_f64(b: &BigInt, f: f64) -> Option<Ordering> {
if f.is_nan() {
None
} else if f.is_infinite() {
Some(if f > 0.0 { Ordering::Less } else { Ordering::Greater })
} else {
let trunc = f.trunc();
let f_int = BigInt::from_f64(trunc).expect("finite f64 converts to BigInt");
match b.cmp(&f_int) {
Ordering::Equal => (f - trunc).partial_cmp(&0.0).map(Ordering::reverse),
ord => Some(ord),
}
}
}
pub fn i64_cmp_f64(a: i64, f: f64) -> Option<Ordering> {
const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
if f.is_nan() {
None
} else if f >= TWO_POW_63 {
Some(Ordering::Less) } else if f < -TWO_POW_63 {
Some(Ordering::Greater) } else {
let trunc = f.trunc();
#[expect(clippy::cast_possible_truncation, reason = "bounds-checked: -2^63 ≤ trunc < 2^63")]
match a.cmp(&(trunc as i64)) {
Ordering::Equal => (f - trunc).partial_cmp(&0.0).map(Ordering::reverse),
ord => Some(ord),
}
}
}
pub fn check_decimal_digit_count(digit_count: usize) -> RunResult<()> {
if digit_count > INT_MAX_STR_DIGITS {
return Err(ExcType::value_error_int_str_too_large(digit_count));
}
Ok(())
}
pub fn decimal_digit_count_ascii(value: &[u8]) -> usize {
value.iter().filter(|byte| byte.is_ascii_digit()).count()
}
pub fn check_bigint_str_digits_limit(value: &BigInt) -> RunResult<()> {
let threshold = int_max_str_digits_threshold();
let abs_value = value.abs();
if abs_value.bits() > threshold.bits() || (abs_value.bits() == threshold.bits() && abs_value >= *threshold) {
return Err(ExcType::value_error_int_too_large_for_str());
}
Ok(())
}
pub fn check_bits_str_digits_limit(bits: u64) -> RunResult<()> {
let estimated_digits = bits.saturating_mul(30_103) / 100_000 + 1;
if estimated_digits > INT_MAX_STR_DIGITS as u64 {
return Err(ExcType::value_error_int_too_large_for_str());
}
Ok(())
}
fn int_max_str_digits_threshold() -> &'static BigInt {
INT_MAX_STR_DIGITS_THRESHOLD.get_or_init(|| {
BigInt::from(10u8).pow(u32::try_from(INT_MAX_STR_DIGITS).expect("INT_MAX_STR_DIGITS should fit in u32"))
})
}
impl From<BigInt> for LongInt {
fn from(bi: BigInt) -> Self {
Self(bi)
}
}
impl From<i64> for LongInt {
fn from(i: i64) -> Self {
Self(BigInt::from(i))
}
}
impl Add for LongInt {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub for LongInt {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Mul for LongInt {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.0 * rhs.0)
}
}
impl Neg for LongInt {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl Display for LongInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}