Skip to main content

rustpython_vm/builtins/
int.rs

1use super::{PyByteArray, PyBytes, PyStr, PyType, PyTypeRef, float};
2use crate::{
3    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
4    TryFromBorrowedObject, VirtualMachine,
5    builtins::PyUtf8StrRef,
6    byte::bytes_from_object,
7    class::{PyClassDef, PyClassImpl},
8    common::{
9        format::FormatSpec,
10        hash,
11        int::{bigint_to_finite_float, bytes_to_int, true_div},
12        wtf8::Wtf8Buf,
13    },
14    convert::{IntoPyException, ToPyObject, ToPyResult},
15    function::{
16        ArgByteOrder, ArgIntoBool, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue,
17        PySsize,
18    },
19    protocol::{PyNumberMethods, handle_bytes_to_int_err, numeric_literal_from_str},
20    types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
21};
22use alloc::fmt;
23use core::cell::Cell;
24use core::ops::{Neg, Not};
25use core::ptr::NonNull;
26use malachite_bigint::{BigInt, Sign};
27use num_integer::{ExtendedGcd, Integer};
28use num_traits::{One, Pow, PrimInt, Signed, ToPrimitive, Zero};
29
30#[pyclass(module = false, name = "int")]
31#[derive(Debug)]
32pub struct PyInt {
33    value: BigInt,
34}
35
36impl fmt::Display for PyInt {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        BigInt::fmt(&self.value, f)
39    }
40}
41
42pub type PyIntRef = PyRef<PyInt>;
43
44impl<T> From<T> for PyInt
45where
46    T: Into<BigInt>,
47{
48    fn from(v: T) -> Self {
49        Self { value: v.into() }
50    }
51}
52
53// spell-checker:ignore MAXFREELIST
54thread_local! {
55    static INT_FREELIST: Cell<crate::object::FreeList<PyInt>> = const { Cell::new(crate::object::FreeList::new()) };
56}
57
58impl PyPayload for PyInt {
59    const MAX_FREELIST: usize = 100;
60    const HAS_FREELIST: bool = true;
61
62    #[inline]
63    fn class(ctx: &Context) -> &'static Py<PyType> {
64        ctx.types.int_type
65    }
66
67    fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
68        vm.ctx.new_int(self.value).into()
69    }
70
71    #[inline]
72    unsafe fn freelist_push(obj: *mut PyObject) -> bool {
73        INT_FREELIST
74            .try_with(|fl| {
75                let mut list = fl.take();
76                let stored = if list.len() < Self::MAX_FREELIST {
77                    list.push(obj);
78                    true
79                } else {
80                    false
81                };
82                fl.set(list);
83                stored
84            })
85            .unwrap_or(false)
86    }
87
88    #[inline]
89    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
90        INT_FREELIST
91            .try_with(|fl| {
92                let mut list = fl.take();
93                let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
94                fl.set(list);
95                result
96            })
97            .ok()
98            .flatten()
99    }
100}
101
102macro_rules! impl_into_pyobject_int {
103    ($($t:ty)*) => {$(
104        impl ToPyObject for $t {
105            fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
106                vm.ctx.new_int(self).into()
107            }
108        }
109    )*};
110}
111
112impl_into_pyobject_int!(isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 BigInt);
113
114macro_rules! impl_try_from_object_int {
115    ($(($t:ty, $to_prim:ident),)*) => {$(
116        impl<'a> TryFromBorrowedObject<'a> for $t {
117            fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
118                // `int` (and subclasses, including `bool`) is taken as-is.
119                // Anything else must supply `__index__`, which `try_index` calls.
120                let owned;
121                let int = if let Some(int) = obj.downcast_ref::<PyInt>() {
122                    int
123                } else {
124                    owned = obj.try_index(vm)?;
125                    &owned
126                };
127                int.try_to_primitive(vm)
128            }
129        }
130    )*};
131}
132
133impl_try_from_object_int!(
134    (isize, to_isize),
135    (i8, to_i8),
136    (i16, to_i16),
137    (i32, to_i32),
138    (i64, to_i64),
139    (i128, to_i128),
140    (usize, to_usize),
141    (u8, to_u8),
142    (u16, to_u16),
143    (u32, to_u32),
144    (u64, to_u64),
145    (u128, to_u128),
146);
147
148fn inner_pow(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult {
149    if int2.is_negative() {
150        let v1 = try_to_float(int1, vm)?;
151        let v2 = try_to_float(int2, vm)?;
152        float::float_pow(v1, v2, vm)
153    } else {
154        let value = if let Some(v2) = int2.to_u64() {
155            // malachite builds a power of two at its exact size. Otherwise it allocates
156            // `bits * v2` bits for the result plus a scratch buffer of up to the same size.
157            let base_bits = int1.bits();
158            if base_bits > 1 {
159                let bits = if int1.trailing_zeros() == Some(base_bits - 1) {
160                    (base_bits - 1)
161                        .checked_mul(v2)
162                        .and_then(|b| b.checked_add(1))
163                } else {
164                    base_bits.checked_mul(v2).and_then(|b| b.checked_mul(2))
165                };
166                reserve_result_bits(bits, vm)?;
167            }
168            return Ok(vm.ctx.new_int(Pow::pow(int1, v2)).into());
169        } else if int1.is_one() {
170            1
171        } else if int1.is_zero() {
172            0
173        } else if int1 == &BigInt::from(-1) {
174            if int2.is_odd() { -1 } else { 1 }
175        } else {
176            // missing feature: BigInt exp
177            // practically, exp over u64 is not possible to calculate anyway
178            return Ok(vm.ctx.not_implemented());
179        };
180        Ok(vm.ctx.new_int(value).into())
181    }
182}
183
184fn inner_mod(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult {
185    if int2.is_zero() {
186        Err(vm.new_zero_division_error("division by zero"))
187    } else {
188        Ok(vm.ctx.new_int(int1.mod_floor(int2)).into())
189    }
190}
191
192fn inner_floordiv(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult {
193    if int2.is_zero() {
194        Err(vm.new_zero_division_error("division by zero"))
195    } else {
196        Ok(vm.ctx.new_int(int1.div_floor(int2)).into())
197    }
198}
199
200fn inner_divmod(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult {
201    if int2.is_zero() {
202        return Err(vm.new_zero_division_error("division by zero"));
203    }
204    let (div, modulo) = int1.div_mod_floor(int2);
205    Ok(vm.new_tuple((div, modulo)).into())
206}
207
208fn inner_lshift(base: &BigInt, bits: &BigInt, vm: &VirtualMachine) -> PyResult {
209    inner_shift(
210        base,
211        bits,
212        |base, bits| base << bits,
213        |bits, vm| {
214            // CPython's limit: `(i64::MAX - 1) / 30` digits of 30 bits.
215            const MAX_DIGITS: u128 = (i64::MAX as u128 - 1) / 30;
216            let digits = bits.to_u128().map(|shift| {
217                u128::from(base.bits().div_ceil(30)) + shift / 30 + u128::from(shift % 30 != 0)
218            });
219            if digits.is_none_or(|digits| digits > MAX_DIGITS) {
220                return Err(vm.new_overflow_error("too many digits in integer"));
221            }
222            let shift = bits.to_u64().ok_or_else(|| vm.no_memory_error())?;
223            reserve_result_bits(base.bits().checked_add(shift), vm)?;
224            usize::try_from(shift).map_err(|_| vm.no_memory_error())
225        },
226        vm,
227    )
228}
229
230/// Reject sizes that cannot be allocated before calling Malachite's infallible arithmetic.
231/// This is only a preflight check: Malachite allocates its own result and scratch buffers,
232/// so an allocation failure during the operation can still abort the process.
233fn reserve_result_bits(bits: Option<u64>, vm: &VirtualMachine) -> PyResult<()> {
234    // Results below this size are not worth the extra allocation.
235    const CHECK_FROM_BITS: u64 = 1 << 26;
236    let bits = bits.ok_or_else(|| vm.no_memory_error())?;
237    if bits < CHECK_FROM_BITS {
238        return Ok(());
239    }
240    let words =
241        usize::try_from(bits.div_ceil(u64::BITS.into())).map_err(|_| vm.no_memory_error())?;
242    Vec::<u64>::new()
243        .try_reserve_exact(words)
244        .map_err(|_| vm.no_memory_error())
245}
246
247fn inner_rshift(base: &BigInt, bits: &BigInt, vm: &VirtualMachine) -> PyResult {
248    inner_shift(
249        base,
250        bits,
251        |base, bits| base >> bits,
252        |bits, _vm| Ok(bits.to_usize().unwrap_or(usize::MAX)),
253        vm,
254    )
255}
256
257fn inner_shift<F, S>(
258    base: &BigInt,
259    bits: &BigInt,
260    shift_op: F,
261    shift_bits: S,
262    vm: &VirtualMachine,
263) -> PyResult
264where
265    F: Fn(&BigInt, usize) -> BigInt,
266    S: Fn(&BigInt, &VirtualMachine) -> PyResult<usize>,
267{
268    if bits.is_negative() {
269        Err(vm.new_value_error("negative shift count"))
270    } else if base.is_zero() {
271        Ok(vm.ctx.new_int(0).into())
272    } else {
273        shift_bits(bits, vm).map(|bits| vm.ctx.new_int(shift_op(base, bits)).into())
274    }
275}
276
277fn inner_truediv(i1: &BigInt, i2: &BigInt, vm: &VirtualMachine) -> PyResult {
278    if i2.is_zero() {
279        return Err(vm.new_zero_division_error("division by zero"));
280    }
281
282    let float = true_div(i1, i2);
283
284    if float.is_infinite() {
285        Err(vm.new_overflow_error("integer division result too large for a float"))
286    } else {
287        Ok(vm.ctx.new_float(float).into())
288    }
289}
290
291impl Constructor for PyInt {
292    type Args = FuncArgs;
293
294    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
295        if cls.is(vm.ctx.types.bool_type) {
296            return Err(vm.new_type_error("int.__new__(bool) is not safe, use bool.__new__()"));
297        }
298
299        // Optimization: return exact int as-is (only for exact int type, not subclasses)
300        if cls.is(vm.ctx.types.int_type)
301            && args.args.len() == 1
302            && args.kwargs.is_empty()
303            && args.args[0].class().is(vm.ctx.types.int_type)
304        {
305            return Ok(args.args[0].clone());
306        }
307
308        let options: IntOptions = args.bind_for(vm, Self::NAME)?;
309        let value = if let OptionalArg::Present(val) = options.val_options {
310            if let OptionalArg::Present(base) = options.base {
311                let base = base
312                    .try_index(vm)?
313                    .as_bigint()
314                    .to_u32()
315                    .filter(|&v| v == 0 || (2..=36).contains(&v))
316                    .ok_or_else(|| vm.new_value_error("int() base must be >= 2 and <= 36, or 0"))?;
317                try_int_radix(&val, base, vm)
318            } else {
319                val.try_int(vm).map(|x| x.as_bigint().clone())
320            }
321        } else if let OptionalArg::Present(_) = options.base {
322            Err(vm.new_type_error("int() missing string argument"))
323        } else {
324            Ok(Zero::zero())
325        }?;
326
327        Self::with_value(cls, value, vm).map(Into::into)
328    }
329
330    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
331        unimplemented!("use slot_new")
332    }
333}
334
335impl PyInt {
336    fn with_value<T>(cls: PyTypeRef, value: T, vm: &VirtualMachine) -> PyResult<PyRef<Self>>
337    where
338        T: Into<BigInt> + ToPrimitive,
339    {
340        if cls.is(vm.ctx.types.int_type) {
341            Ok(vm.ctx.new_int(value))
342        } else if cls.is(vm.ctx.types.bool_type) {
343            Ok(vm.ctx.new_bool(!value.into().eq(&BigInt::zero())).upcast())
344        } else {
345            Self::from(value).into_ref_with_type(vm, cls)
346        }
347    }
348
349    #[must_use]
350    pub const fn as_bigint(&self) -> &BigInt {
351        &self.value
352    }
353}
354
355impl Py<PyInt> {
356    #[must_use]
357    #[inline]
358    pub const fn as_bigint(&self) -> &BigInt {
359        self.payload.as_bigint()
360    }
361
362    /// Extract the inline magnitude without the generic primitive-conversion path.
363    #[inline(always)]
364    pub(crate) fn try_to_i64_fast(&self) -> Option<i64> {
365        let bits = self.as_bigint().bits();
366        if bits > i64::BITS as u64 {
367            return None;
368        }
369        let magnitude = self.as_bigint().iter_u64_digits().next().unwrap_or(0);
370        let signed_magnitude = i64::try_from(magnitude).ok();
371        match self.as_bigint().sign() {
372            Sign::Minus if magnitude == 1u64 << 63 => Some(i64::MIN),
373            Sign::Minus => signed_magnitude.map(|value| -value),
374            Sign::NoSign | Sign::Plus => signed_magnitude,
375        }
376    }
377
378    /// Fast decimal string conversion, using i64 path when possible.
379    #[inline]
380    #[must_use]
381    pub fn to_str_radix_10(&self) -> String {
382        match self.as_bigint().to_i64() {
383            Some(i) => itoa::Buffer::new().format(i).to_owned(),
384            None => self.as_bigint().to_string(),
385        }
386    }
387
388    // _PyLong_AsUnsignedLongMask
389    #[must_use]
390    pub fn as_u32_mask(&self) -> u32 {
391        let v = self.as_bigint();
392        let out = v.iter_u32_digits().next().unwrap_or(0);
393        match v.sign() {
394            Sign::Minus => out.wrapping_neg(),
395            _ => out,
396        }
397    }
398
399    // _PyLong_AsUnsignedLongLongMask
400    #[must_use]
401    pub fn as_u64_mask(&self) -> u64 {
402        let v = self.as_bigint();
403        let mut digits = v.iter_u32_digits();
404        let out =
405            u64::from(digits.next().unwrap_or(0)) | (u64::from(digits.next().unwrap_or(0)) << 32);
406        match v.sign() {
407            Sign::Minus => out.wrapping_neg(),
408            _ => out,
409        }
410    }
411
412    // PyLong_AsUInt32, PyLong_AsUInt64 and the unsigned argument converters:
413    // a negative value for an unsigned type raises ValueError.
414    pub fn try_to_primitive<I>(&self, vm: &VirtualMachine) -> PyResult<I>
415    where
416        I: PrimInt + for<'a> TryFrom<&'a BigInt>,
417    {
418        self.to_primitive(true, vm)
419    }
420
421    // PyLong_AsUnsignedLong, PyLong_AsSize_t: a negative value for an unsigned
422    // type is out of range like any other and raises OverflowError.
423    pub fn try_to_primitive_in_range<I>(&self, vm: &VirtualMachine) -> PyResult<I>
424    where
425        I: PrimInt + for<'a> TryFrom<&'a BigInt>,
426    {
427        self.to_primitive(false, vm)
428    }
429
430    // PyLong_AsNativeBytes with or without Py_ASNATIVEBYTES_REJECT_NEGATIVE
431    fn to_primitive<I>(&self, reject_negative: bool, vm: &VirtualMachine) -> PyResult<I>
432    where
433        I: PrimInt + for<'a> TryFrom<&'a BigInt>,
434    {
435        if reject_negative && I::min_value() == I::zero() && self.as_bigint().sign() == Sign::Minus
436        {
437            return Err(vm.new_value_error("can't convert negative number to unsigned"));
438        }
439        I::try_from(self.as_bigint()).map_err(|_| {
440            vm.new_overflow_error(format!(
441                "Python int too large to convert to Rust {}",
442                core::any::type_name::<I>()
443            ))
444        })
445    }
446
447    pub(crate) fn __xor__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> {
448        self.int_op(&other, |a, b| a ^ b)
449    }
450
451    pub(crate) fn __or__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> {
452        self.int_op(&other, |a, b| a | b)
453    }
454
455    pub(crate) fn __and__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> {
456        self.int_op(&other, |a, b| a & b)
457    }
458
459    #[inline]
460    fn int_op<F>(&self, other: &PyObject, op: F) -> PyArithmeticValue<BigInt>
461    where
462        F: Fn(&BigInt, &BigInt) -> BigInt,
463    {
464        let r = other
465            .downcast_ref::<PyInt>()
466            .map(|other| op(self.as_bigint(), other.as_bigint()));
467        PyArithmeticValue::from_option(r)
468    }
469
470    #[inline]
471    fn general_op<F>(&self, other: &PyObject, op: F, vm: &VirtualMachine) -> PyResult
472    where
473        F: Fn(&BigInt, &BigInt) -> PyResult,
474    {
475        if let Some(other) = other.downcast_ref::<PyInt>() {
476            op(self.as_bigint(), other.as_bigint())
477        } else {
478            Ok(vm.ctx.not_implemented())
479        }
480    }
481
482    fn modpow(&self, other: &PyObject, modulus: &PyObject, vm: &VirtualMachine) -> PyResult {
483        if other.downcast_ref::<PyInt>().is_none() {
484            return Ok(vm.ctx.not_implemented());
485        }
486        let modulus = match modulus.downcast_ref::<PyInt>() {
487            Some(val) => val.as_bigint(),
488            None => return Ok(vm.ctx.not_implemented()),
489        };
490        if modulus.is_zero() {
491            return Err(vm.new_value_error("pow() 3rd argument cannot be 0"));
492        }
493
494        self.general_op(
495            other,
496            |a, b| {
497                let i = if b.is_negative() {
498                    // modular multiplicative inverse
499                    // based on rust-num/num-integer#10, should hopefully be published soon
500                    fn normalize(a: BigInt, n: &BigInt) -> BigInt {
501                        let a = a % n;
502                        if a.is_negative() { a + n } else { a }
503                    }
504                    fn inverse(a: BigInt, n: &BigInt) -> Option<BigInt> {
505                        let ExtendedGcd { gcd, x: c, .. } = a.extended_gcd(n);
506                        if gcd.is_one() {
507                            Some(normalize(c, n))
508                        } else {
509                            None
510                        }
511                    }
512                    let a = inverse(a % modulus, modulus).ok_or_else(|| {
513                        vm.new_value_error("base is not invertible for the given modulus")
514                    })?;
515                    let b = -b;
516                    a.modpow(&b, modulus)
517                } else {
518                    a.modpow(b, modulus)
519                };
520                Ok(vm.ctx.new_int(i).into())
521            },
522            vm,
523        )
524    }
525}
526
527#[derive(FromArgs)]
528struct RoundArgs {
529    #[pyarg(positional, optional)]
530    ndigits: Option<PyIntRef>,
531}
532
533#[pyclass(
534    itemsize = 4,
535    flags(BASETYPE, _MATCH_SELF),
536    with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
537)]
538impl Py<PyInt> {
539    #[pymethod]
540    fn __round__(zelf: PyRef<PyInt>, args: RoundArgs, vm: &VirtualMachine) -> PyRef<PyInt> {
541        if let Some(ndigits) = args.ndigits {
542            let ndigits = ndigits.as_bigint();
543            // round(12345, -2) == 12300
544            // If precision >= 0, then any integer is already rounded correctly
545            if let Some(ndigits) = ndigits.neg().to_u32()
546                && ndigits > 0
547            {
548                // Work with positive integers and negate at the end if necessary
549                let sign = if zelf.as_bigint().is_negative() {
550                    BigInt::from(-1)
551                } else {
552                    BigInt::from(1)
553                };
554                let value = zelf.as_bigint().abs();
555
556                // Divide and multiply by the power of 10 to get the approximate answer
557                let pow10 = BigInt::from(10).pow(ndigits);
558                let quotient = &value / &pow10;
559                let rounded = &quotient * &pow10;
560
561                // Malachite division uses floor rounding, Python uses half-even
562                let remainder = &value - &rounded;
563                let half_pow10 = &pow10 / BigInt::from(2);
564                let correction =
565                    if remainder > half_pow10 || (remainder == half_pow10 && quotient.is_odd()) {
566                        pow10
567                    } else {
568                        BigInt::from(0)
569                    };
570                let rounded = (rounded + correction) * sign;
571                return vm.ctx.new_int(rounded);
572            }
573        }
574        // No rounding to do, but an int subclass must still be normalized to an
575        // exact int, the way CPython's long_long() does.
576        zelf.__int__(vm).into_pyref()
577    }
578
579    #[pymethod]
580    fn __trunc__(zelf: PyRef<PyInt>, vm: &VirtualMachine) -> PyRefExact<PyInt> {
581        zelf.__int__(vm)
582    }
583
584    #[pymethod]
585    fn __floor__(zelf: PyRef<PyInt>, vm: &VirtualMachine) -> PyRefExact<PyInt> {
586        zelf.__int__(vm)
587    }
588
589    #[pymethod]
590    fn __ceil__(zelf: PyRef<PyInt>, vm: &VirtualMachine) -> PyRefExact<PyInt> {
591        zelf.__int__(vm)
592    }
593
594    #[pymethod]
595    fn __format__(
596        zelf: &Self,
597        format_spec: PyUtf8StrRef,
598        vm: &VirtualMachine,
599    ) -> PyResult<Wtf8Buf> {
600        // Empty format spec on a subclass: equivalent to str(self)
601        if format_spec.is_empty() && !zelf.class().is(vm.ctx.types.int_type) {
602            return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
603        }
604        let format_spec =
605            FormatSpec::parse(format_spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
606        if format_spec.is_decimal_int_format() {
607            check_int_to_str_digits(zelf.as_bigint(), vm)?;
608        }
609        let result = if format_spec.has_locale_format() {
610            let locale = crate::format::get_locale_info();
611            format_spec.format_int_locale(zelf.as_bigint(), &locale)
612        } else {
613            format_spec.format_int(zelf.as_bigint())
614        };
615        result
616            .map(Wtf8Buf::from_string)
617            .map_err(|err| err.into_pyexception(vm))
618    }
619
620    #[pymethod]
621    fn __sizeof__(&self) -> usize {
622        core::mem::size_of::<PyInt>() + (((self.value.bits() + 7) & !7) / 8) as usize
623    }
624
625    #[pymethod]
626    fn as_integer_ratio(&self, vm: &VirtualMachine) -> (PyRef<PyInt>, i32) {
627        (vm.ctx.new_bigint(&self.value), 1)
628    }
629
630    #[pymethod]
631    fn bit_length(&self) -> u64 {
632        self.value.bits()
633    }
634
635    #[pymethod]
636    fn conjugate(zelf: PyRef<PyInt>, vm: &VirtualMachine) -> PyRefExact<PyInt> {
637        zelf.__int__(vm)
638    }
639
640    #[pyclassmethod]
641    fn from_bytes(
642        cls: PyTypeRef,
643        args: IntFromByteArgs,
644        vm: &VirtualMachine,
645    ) -> PyResult<PyRef<PyInt>> {
646        let signed = args.signed.into();
647        // PyObject_Bytes, so an iterable of ints is as good as a buffer
648        let bytes = bytes_from_object(vm, &args.bytes)?;
649        let value = match (args.byteorder, signed) {
650            (ArgByteOrder::Big, true) => BigInt::from_signed_bytes_be(&bytes),
651            (ArgByteOrder::Big, false) => BigInt::from_bytes_be(Sign::Plus, &bytes),
652            (ArgByteOrder::Little, true) => BigInt::from_signed_bytes_le(&bytes),
653            (ArgByteOrder::Little, false) => BigInt::from_bytes_le(Sign::Plus, &bytes),
654        };
655        PyInt::with_value(cls, value, vm)
656    }
657
658    #[pymethod]
659    fn to_bytes(&self, args: IntToByteArgs, vm: &VirtualMachine) -> PyResult<PyBytes> {
660        let signed: bool = args.signed.into();
661        // Bound as `isize`, so a length past `isize::MAX` is an OverflowError rather
662        // than a failed allocation later on.
663        let byte_len = args.length;
664        let byte_len = usize::try_from(byte_len)
665            .map_err(|_| vm.new_value_error("length argument must be non-negative"))?;
666
667        let value = self.as_bigint();
668        match value.sign() {
669            Sign::Minus if !signed => {
670                return Err(vm.new_overflow_error("can't convert negative int to unsigned"));
671            }
672            Sign::NoSign => return Ok(vm.new_zeroed_bytes(byte_len)?.into()),
673            _ => {}
674        }
675
676        let mut origin_bytes = match (args.byteorder, signed) {
677            (ArgByteOrder::Big, true) => value.to_signed_bytes_be(),
678            (ArgByteOrder::Big, false) => value.to_bytes_be().1,
679            (ArgByteOrder::Little, true) => value.to_signed_bytes_le(),
680            (ArgByteOrder::Little, false) => value.to_bytes_le().1,
681        };
682
683        let origin_len = origin_bytes.len();
684        if origin_len > byte_len {
685            return Err(vm.new_overflow_error("int too big to convert"));
686        }
687
688        let mut append_bytes = vm.new_zeroed_bytes(byte_len - origin_len)?;
689        if value.sign() == Sign::Minus {
690            append_bytes.fill(255);
691        }
692
693        let bytes = match args.byteorder {
694            ArgByteOrder::Big => {
695                let mut bytes = append_bytes;
696                bytes.append(&mut origin_bytes);
697                bytes
698            }
699            ArgByteOrder::Little => {
700                let mut bytes = origin_bytes;
701                bytes.append(&mut append_bytes);
702                bytes
703            }
704        };
705        Ok(bytes.into())
706    }
707
708    #[pygetset]
709    fn real(zelf: PyRef<PyInt>, vm: &VirtualMachine) -> PyRefExact<PyInt> {
710        zelf.__int__(vm)
711    }
712
713    #[pygetset]
714    const fn imag(&self) -> usize {
715        0
716    }
717
718    #[pygetset]
719    fn numerator(zelf: PyRef<PyInt>, vm: &VirtualMachine) -> PyRefExact<PyInt> {
720        zelf.__int__(vm)
721    }
722
723    #[pygetset]
724    const fn denominator(&self) -> usize {
725        1
726    }
727
728    #[pymethod]
729    const fn is_integer(&self) -> bool {
730        true
731    }
732
733    #[pymethod]
734    fn bit_count(&self) -> u32 {
735        self.value.iter_u32_digits().map(|n| n.count_ones()).sum()
736    }
737
738    #[pymethod]
739    fn __getnewargs__(&self, vm: &VirtualMachine) -> PyObjectRef {
740        (self.value.clone(),).to_pyobject(vm)
741    }
742}
743
744#[pyclass]
745impl PyRef<PyInt> {
746    pub(crate) fn __int__(self, vm: &VirtualMachine) -> PyRefExact<PyInt> {
747        self.into_exact_or(&vm.ctx, |zelf| unsafe {
748            // TODO: this is actually safe. we need better interface
749            PyRefExact::new_unchecked(vm.ctx.new_bigint(zelf.as_bigint()))
750        })
751    }
752}
753
754impl Comparable for PyInt {
755    fn cmp(
756        zelf: &Py<Self>,
757        other: &PyObject,
758        op: PyComparisonOp,
759        _vm: &VirtualMachine,
760    ) -> PyResult<PyComparisonValue> {
761        let r = other
762            .downcast_ref::<Self>()
763            .map(|other| op.eval_ord(zelf.as_bigint().cmp(other.as_bigint())));
764        Ok(PyComparisonValue::from_option(r))
765    }
766}
767
768/// Pre-format check enforcing `sys.get_int_max_str_digits()` on int → str conversions.
769/// Mirrors CPython's PEP 644 DoS mitigation. Cheap fast-path for small values via
770/// bit-count upper bound on decimal digits.
771pub(crate) fn check_int_to_str_digits(value: &BigInt, vm: &VirtualMachine) -> PyResult<()> {
772    let limit = vm.state.int_max_str_digits.load();
773    if limit == 0 {
774        return Ok(());
775    }
776    let bits = value.bits();
777    // Below ~452 decimal digits: definitely under any reasonable limit.
778    if bits < 1500 {
779        return Ok(());
780    }
781    // Upper bound on decimal digit count: ⌈bits × log10(2)⌉ + 1, with log10(2) ≈ 0.30103.
782    // Multiply with the u64 bit count: `bits as usize * 30103` wraps on
783    // 32-bit (wasm32) once bits ≳ 142_500, which silently accepts over-limit
784    // conversions.
785    let digits_upper = bits.saturating_mul(30103) / 100_000 + 1;
786    if digits_upper > u64::try_from(limit).unwrap_or(u64::MAX) {
787        return Err(vm.new_value_error(format!(
788            "Exceeds the limit ({limit} digits) for integer string conversion; \
789             use sys.set_int_max_str_digits() to increase the limit"
790        )));
791    }
792    Ok(())
793}
794
795impl Representable for PyInt {
796    #[inline]
797    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
798        check_int_to_str_digits(zelf.as_bigint(), vm)?;
799        Ok(zelf.to_str_radix_10())
800    }
801}
802
803impl Hashable for PyInt {
804    #[inline]
805    fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<hash::PyHash> {
806        Ok(hash::hash_bigint(zelf.as_bigint()))
807    }
808}
809
810impl AsNumber for PyInt {
811    fn as_number() -> &'static PyNumberMethods {
812        static AS_NUMBER: PyNumberMethods = PyInt::AS_NUMBER;
813        &AS_NUMBER
814    }
815
816    #[inline]
817    fn clone_exact(zelf: &Py<Self>, vm: &VirtualMachine) -> PyRef<Self> {
818        vm.ctx.new_bigint(zelf.as_bigint())
819    }
820}
821
822impl PyInt {
823    pub(super) const AS_NUMBER: PyNumberMethods = PyNumberMethods {
824        add: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a + b, vm)),
825        subtract: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a - b, vm)),
826        multiply: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a * b, vm)),
827        remainder: Some(|a, b, vm| Self::number_op(a, b, inner_mod, vm)),
828        divmod: Some(|a, b, vm| Self::number_op(a, b, inner_divmod, vm)),
829        power: Some(|a, b, c, vm| {
830            if let Some(a) = a.downcast_ref::<Self>() {
831                if vm.is_none(c) {
832                    a.general_op(b, |a, b| inner_pow(a, b, vm), vm)
833                } else {
834                    a.modpow(b, c, vm)
835                }
836            } else {
837                Ok(vm.ctx.not_implemented())
838            }
839        }),
840        negative: Some(|num, vm| Self::number_downcast(num).as_bigint().neg().to_pyresult(vm)),
841        positive: Some(|num, vm| Ok(Self::number_downcast_exact(num, vm).into())),
842        absolute: Some(|num, vm| Self::number_downcast(num).as_bigint().abs().to_pyresult(vm)),
843        boolean: Some(|num, _vm| Ok(!Self::number_downcast(num).as_bigint().is_zero())),
844        invert: Some(|num, vm| Self::number_downcast(num).as_bigint().not().to_pyresult(vm)),
845        lshift: Some(|a, b, vm| Self::number_op(a, b, inner_lshift, vm)),
846        rshift: Some(|a, b, vm| Self::number_op(a, b, inner_rshift, vm)),
847        and: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a & b, vm)),
848        xor: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a ^ b, vm)),
849        or: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a | b, vm)),
850        int: Some(|num, vm| Ok(Self::number_downcast_exact(num, vm).into())),
851        float: Some(|num, vm| {
852            let zelf = Self::number_downcast(num);
853            try_to_float(zelf.as_bigint(), vm).map(|x| vm.ctx.new_float(x).into())
854        }),
855        floor_divide: Some(|a, b, vm| Self::number_op(a, b, inner_floordiv, vm)),
856        true_divide: Some(|a, b, vm| Self::number_op(a, b, inner_truediv, vm)),
857        index: Some(|num, vm| Ok(Self::number_downcast_exact(num, vm).into())),
858        ..PyNumberMethods::NOT_IMPLEMENTED
859    };
860
861    fn number_op<F, R>(a: &PyObject, b: &PyObject, op: F, vm: &VirtualMachine) -> PyResult
862    where
863        F: FnOnce(&BigInt, &BigInt, &VirtualMachine) -> R,
864        R: ToPyResult,
865    {
866        if let (Some(a), Some(b)) = (a.downcast_ref::<Self>(), b.downcast_ref::<Self>()) {
867            op(a.as_bigint(), b.as_bigint(), vm).to_pyresult(vm)
868        } else {
869            Ok(vm.ctx.not_implemented())
870        }
871    }
872}
873
874#[derive(FromArgs)]
875pub(crate) struct IntOptions {
876    // Missing means 0. None is not an int.
877    #[pyarg(positional, optional, py_default = "0")]
878    val_options: OptionalArg<PyObjectRef>,
879    // Missing means no base was passed. The shown default is 10.
880    #[pyarg(any, optional, py_default = "10")]
881    base: OptionalArg<PyObjectRef>,
882}
883
884#[derive(FromArgs)]
885struct IntFromByteArgs {
886    bytes: PyObjectRef,
887    #[pyarg(any, default = ArgByteOrder::Big)]
888    byteorder: ArgByteOrder,
889    #[pyarg(named, default = ArgIntoBool::FALSE)]
890    signed: ArgIntoBool,
891}
892
893#[derive(FromArgs)]
894struct IntToByteArgs {
895    #[pyarg(any, default = 1)]
896    length: PySsize,
897    #[pyarg(any, default = ArgByteOrder::Big)]
898    byteorder: ArgByteOrder,
899    #[pyarg(named, default = ArgIntoBool::FALSE)]
900    signed: ArgIntoBool,
901}
902
903fn try_int_radix(obj: &PyObject, base: u32, vm: &VirtualMachine) -> PyResult<BigInt> {
904    match_class!(match obj.to_owned() {
905        string @ PyStr => {
906            let s = numeric_literal_from_str(&string);
907            bytes_to_int(s.as_bytes(), base, vm.state.int_max_str_digits.load())
908                .map_err(|e| handle_bytes_to_int_err(e, obj, vm))
909        }
910        bytes @ PyBytes => {
911            bytes_to_int(bytes.as_bytes(), base, vm.state.int_max_str_digits.load())
912                .map_err(|e| handle_bytes_to_int_err(e, obj, vm))
913        }
914        bytearray @ PyByteArray => {
915            let inner = bytearray.borrow_buf();
916            bytes_to_int(&inner, base, vm.state.int_max_str_digits.load())
917                .map_err(|e| handle_bytes_to_int_err(e, obj, vm))
918        }
919        _ => Err(vm.new_type_error("int() can't convert non-string with explicit base")),
920    })
921}
922
923// Retrieve inner int value:
924pub(crate) fn get_value(obj: &PyObject) -> &BigInt {
925    obj.downcast_ref::<PyInt>().unwrap().as_bigint()
926}
927
928pub fn try_to_float(int: &BigInt, vm: &VirtualMachine) -> PyResult<f64> {
929    bigint_to_finite_float(int)
930        .ok_or_else(|| vm.new_overflow_error("int too large to convert to float"))
931}
932
933fn vectorcall_int(
934    zelf_obj: &PyObject,
935    args: Vec<PyObjectRef>,
936    nargs: usize,
937    kwnames: Option<&[PyObjectRef]>,
938    vm: &VirtualMachine,
939) -> PyResult {
940    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
941    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
942    (zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
943}
944
945pub(crate) fn init(context: &'static Context) {
946    PyInt::extend_class(context, context.types.int_type);
947    context
948        .types
949        .int_type
950        .slots
951        .vectorcall
952        .store(Some(vectorcall_int));
953}