pyo3/conversions/
num_bigint.rs

1#![cfg(feature = "num-bigint")]
2//!  Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types.
3//!
4//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types.
5//!
6//! # Setup
7//!
8//! To use this feature, add this to your **`Cargo.toml`**:
9//!
10//! ```toml
11//! [dependencies]
12//! num-bigint = "*"
13#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"),  "\", features = [\"num-bigint\"] }")]
14//! ```
15//!
16//! Note that you must use compatible versions of num-bigint and PyO3.
17//! The required num-bigint version may vary based on the version of PyO3.
18//!
19//! ## Examples
20//!
21//! Using [`BigInt`] to correctly increment an arbitrary precision integer.
22//! This is not possible with Rust's native integers if the Python integer is too large,
23//! in which case it will fail its conversion and raise `OverflowError`.
24//! ```rust,no_run
25//! use num_bigint::BigInt;
26//! use pyo3::prelude::*;
27//!
28//! #[pyfunction]
29//! fn add_one(n: BigInt) -> BigInt {
30//!     n + 1
31//! }
32//!
33//! #[pymodule]
34//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
35//!     m.add_function(wrap_pyfunction!(add_one, m)?)?;
36//!     Ok(())
37//! }
38//! ```
39//!
40//! Python code:
41//! ```python
42//! from my_module import add_one
43//!
44//! n = 1 << 1337
45//! value = add_one(n)
46//!
47//! assert n + 1 == value
48//! ```
49
50#[cfg(Py_LIMITED_API)]
51use crate::types::{bytes::PyBytesMethods, PyBytes};
52use crate::{
53    conversion::IntoPyObject,
54    ffi,
55    instance::Bound,
56    types::{any::PyAnyMethods, PyInt},
57    FromPyObject, Py, PyAny, PyErr, PyResult, Python,
58};
59
60use num_bigint::{BigInt, BigUint};
61
62#[cfg(not(Py_LIMITED_API))]
63use num_bigint::Sign;
64
65// for identical functionality between BigInt and BigUint
66macro_rules! bigint_conversion {
67    ($rust_ty: ty, $is_signed: literal, $to_bytes: path) => {
68        #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
69        impl<'py> IntoPyObject<'py> for $rust_ty {
70            type Target = PyInt;
71            type Output = Bound<'py, Self::Target>;
72            type Error = PyErr;
73
74            #[inline]
75            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
76                (&self).into_pyobject(py)
77            }
78        }
79
80        #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
81        impl<'py> IntoPyObject<'py> for &$rust_ty {
82            type Target = PyInt;
83            type Output = Bound<'py, Self::Target>;
84            type Error = PyErr;
85
86            #[cfg(not(Py_LIMITED_API))]
87            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
88                use crate::ffi_ptr_ext::FfiPtrExt;
89                let bytes = $to_bytes(&self);
90                unsafe {
91                    Ok(ffi::_PyLong_FromByteArray(
92                        bytes.as_ptr().cast(),
93                        bytes.len(),
94                        1,
95                        $is_signed.into(),
96                    )
97                    .assume_owned(py)
98                    .downcast_into_unchecked())
99                }
100            }
101
102            #[cfg(Py_LIMITED_API)]
103            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
104                use $crate::py_result_ext::PyResultExt;
105                let bytes = $to_bytes(&self);
106                let bytes_obj = PyBytes::new(py, &bytes);
107                let kwargs = if $is_signed {
108                    let kwargs = crate::types::PyDict::new(py);
109                    kwargs.set_item(crate::intern!(py, "signed"), true)?;
110                    Some(kwargs)
111                } else {
112                    None
113                };
114                unsafe {
115                    py.get_type::<PyInt>()
116                        .call_method("from_bytes", (bytes_obj, "little"), kwargs.as_ref())
117                        .downcast_into_unchecked()
118                }
119            }
120        }
121    };
122}
123
124bigint_conversion!(BigUint, false, BigUint::to_bytes_le);
125bigint_conversion!(BigInt, true, BigInt::to_signed_bytes_le);
126
127#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
128impl<'py> FromPyObject<'py> for BigInt {
129    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<BigInt> {
130        let py = ob.py();
131        // fast path - checking for subclass of `int` just checks a bit in the type object
132        let num_owned: Py<PyInt>;
133        let num = if let Ok(long) = ob.downcast::<PyInt>() {
134            long
135        } else {
136            num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
137            num_owned.bind(py)
138        };
139        #[cfg(not(Py_LIMITED_API))]
140        {
141            let mut buffer = int_to_u32_vec::<true>(num)?;
142            let sign = if buffer.last().copied().is_some_and(|last| last >> 31 != 0) {
143                // BigInt::new takes an unsigned array, so need to convert from two's complement
144                // flip all bits, 'subtract' 1 (by adding one to the unsigned array)
145                let mut elements = buffer.iter_mut();
146                for element in elements.by_ref() {
147                    *element = (!*element).wrapping_add(1);
148                    if *element != 0 {
149                        // if the element didn't wrap over, no need to keep adding further ...
150                        break;
151                    }
152                }
153                // ... so just two's complement the rest
154                for element in elements {
155                    *element = !*element;
156                }
157                Sign::Minus
158            } else {
159                Sign::Plus
160            };
161            Ok(BigInt::new(sign, buffer))
162        }
163        #[cfg(Py_LIMITED_API)]
164        {
165            let n_bits = int_n_bits(num)?;
166            if n_bits == 0 {
167                return Ok(BigInt::from(0isize));
168            }
169            let bytes = int_to_py_bytes(num, (n_bits + 8) / 8, true)?;
170            Ok(BigInt::from_signed_bytes_le(bytes.as_bytes()))
171        }
172    }
173}
174
175#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
176impl<'py> FromPyObject<'py> for BigUint {
177    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<BigUint> {
178        let py = ob.py();
179        // fast path - checking for subclass of `int` just checks a bit in the type object
180        let num_owned: Py<PyInt>;
181        let num = if let Ok(long) = ob.downcast::<PyInt>() {
182            long
183        } else {
184            num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
185            num_owned.bind(py)
186        };
187        #[cfg(not(Py_LIMITED_API))]
188        {
189            let buffer = int_to_u32_vec::<false>(num)?;
190            Ok(BigUint::new(buffer))
191        }
192        #[cfg(Py_LIMITED_API)]
193        {
194            let n_bits = int_n_bits(num)?;
195            if n_bits == 0 {
196                return Ok(BigUint::from(0usize));
197            }
198            let bytes = int_to_py_bytes(num, n_bits.div_ceil(8), false)?;
199            Ok(BigUint::from_bytes_le(bytes.as_bytes()))
200        }
201    }
202}
203
204#[cfg(not(any(Py_LIMITED_API, Py_3_13)))]
205#[inline]
206fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
207    let mut buffer = Vec::new();
208    let n_bits = int_n_bits(long)?;
209    if n_bits == 0 {
210        return Ok(buffer);
211    }
212    let n_digits = if SIGNED {
213        (n_bits + 32) / 32
214    } else {
215        n_bits.div_ceil(32)
216    };
217    buffer.reserve_exact(n_digits);
218    unsafe {
219        crate::err::error_on_minusone(
220            long.py(),
221            ffi::_PyLong_AsByteArray(
222                long.as_ptr().cast(),
223                buffer.as_mut_ptr() as *mut u8,
224                n_digits * 4,
225                1,
226                SIGNED.into(),
227            ),
228        )?;
229        buffer.set_len(n_digits)
230    };
231    buffer
232        .iter_mut()
233        .for_each(|chunk| *chunk = u32::from_le(*chunk));
234
235    Ok(buffer)
236}
237
238#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
239#[inline]
240fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
241    let mut buffer = Vec::new();
242    let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
243    if !SIGNED {
244        flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE;
245    }
246    let n_bytes =
247        unsafe { ffi::PyLong_AsNativeBytes(long.as_ptr().cast(), std::ptr::null_mut(), 0, flags) };
248    let n_bytes_unsigned: usize = n_bytes
249        .try_into()
250        .map_err(|_| crate::PyErr::fetch(long.py()))?;
251    if n_bytes == 0 {
252        return Ok(buffer);
253    }
254    let n_digits = n_bytes_unsigned.div_ceil(4);
255    buffer.reserve_exact(n_digits);
256    unsafe {
257        ffi::PyLong_AsNativeBytes(
258            long.as_ptr().cast(),
259            buffer.as_mut_ptr().cast(),
260            (n_digits * 4).try_into().unwrap(),
261            flags,
262        );
263        buffer.set_len(n_digits);
264    };
265    buffer
266        .iter_mut()
267        .for_each(|chunk| *chunk = u32::from_le(*chunk));
268
269    Ok(buffer)
270}
271
272#[cfg(Py_LIMITED_API)]
273fn int_to_py_bytes<'py>(
274    long: &Bound<'py, PyInt>,
275    n_bytes: usize,
276    is_signed: bool,
277) -> PyResult<Bound<'py, PyBytes>> {
278    use crate::intern;
279    let py = long.py();
280    let kwargs = if is_signed {
281        let kwargs = crate::types::PyDict::new(py);
282        kwargs.set_item(intern!(py, "signed"), true)?;
283        Some(kwargs)
284    } else {
285        None
286    };
287    let bytes = long.call_method(
288        intern!(py, "to_bytes"),
289        (n_bytes, intern!(py, "little")),
290        kwargs.as_ref(),
291    )?;
292    Ok(bytes.downcast_into()?)
293}
294
295#[inline]
296#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
297fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult<usize> {
298    let py = long.py();
299    #[cfg(not(Py_LIMITED_API))]
300    {
301        // fast path
302        let n_bits = unsafe { ffi::_PyLong_NumBits(long.as_ptr()) };
303        if n_bits == (-1isize as usize) {
304            return Err(crate::PyErr::fetch(py));
305        }
306        Ok(n_bits)
307    }
308
309    #[cfg(Py_LIMITED_API)]
310    {
311        // slow path
312        long.call_method0(crate::intern!(py, "bit_length"))
313            .and_then(|any| any.extract())
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use crate::tests::common::generate_unique_module_name;
321    use crate::types::{PyDict, PyModule};
322    use indoc::indoc;
323    use pyo3_ffi::c_str;
324
325    fn rust_fib<T>() -> impl Iterator<Item = T>
326    where
327        T: From<u16>,
328        for<'a> &'a T: std::ops::Add<Output = T>,
329    {
330        let mut f0: T = T::from(1);
331        let mut f1: T = T::from(1);
332        std::iter::from_fn(move || {
333            let f2 = &f0 + &f1;
334            Some(std::mem::replace(&mut f0, std::mem::replace(&mut f1, f2)))
335        })
336    }
337
338    fn python_fib(py: Python<'_>) -> impl Iterator<Item = Bound<'_, PyAny>> + '_ {
339        let mut f0 = 1i32.into_pyobject(py).unwrap().into_any();
340        let mut f1 = 1i32.into_pyobject(py).unwrap().into_any();
341        std::iter::from_fn(move || {
342            let f2 = f0.call_method1("__add__", (&f1,)).unwrap();
343            Some(std::mem::replace(&mut f0, std::mem::replace(&mut f1, f2)))
344        })
345    }
346
347    #[test]
348    fn convert_biguint() {
349        Python::attach(|py| {
350            // check the first 2000 numbers in the fibonacci sequence
351            for (py_result, rs_result) in python_fib(py).zip(rust_fib::<BigUint>()).take(2000) {
352                // Python -> Rust
353                assert_eq!(py_result.extract::<BigUint>().unwrap(), rs_result);
354                // Rust -> Python
355                assert!(py_result.eq(rs_result).unwrap());
356            }
357        });
358    }
359
360    #[test]
361    fn convert_bigint() {
362        Python::attach(|py| {
363            // check the first 2000 numbers in the fibonacci sequence
364            for (py_result, rs_result) in python_fib(py).zip(rust_fib::<BigInt>()).take(2000) {
365                // Python -> Rust
366                assert_eq!(py_result.extract::<BigInt>().unwrap(), rs_result);
367                // Rust -> Python
368                assert!(py_result.eq(&rs_result).unwrap());
369
370                // negate
371
372                let rs_result = rs_result * -1;
373                let py_result = py_result.call_method0("__neg__").unwrap();
374
375                // Python -> Rust
376                assert_eq!(py_result.extract::<BigInt>().unwrap(), rs_result);
377                // Rust -> Python
378                assert!(py_result.eq(rs_result).unwrap());
379            }
380        });
381    }
382
383    fn python_index_class(py: Python<'_>) -> Bound<'_, PyModule> {
384        let index_code = c_str!(indoc!(
385            r#"
386                class C:
387                    def __init__(self, x):
388                        self.x = x
389                    def __index__(self):
390                        return self.x
391                "#
392        ));
393        PyModule::from_code(
394            py,
395            index_code,
396            c_str!("index.py"),
397            &generate_unique_module_name("index"),
398        )
399        .unwrap()
400    }
401
402    #[test]
403    fn convert_index_class() {
404        Python::attach(|py| {
405            let index = python_index_class(py);
406            let locals = PyDict::new(py);
407            locals.set_item("index", index).unwrap();
408            let ob = py
409                .eval(ffi::c_str!("index.C(10)"), None, Some(&locals))
410                .unwrap();
411            let _: BigInt = ob.extract().unwrap();
412        });
413    }
414
415    #[test]
416    fn handle_zero() {
417        Python::attach(|py| {
418            let zero: BigInt = 0i32.into_pyobject(py).unwrap().extract().unwrap();
419            assert_eq!(zero, BigInt::from(0));
420        })
421    }
422
423    /// `OverflowError` on converting Python int to BigInt, see issue #629
424    #[test]
425    fn check_overflow() {
426        Python::attach(|py| {
427            macro_rules! test {
428                ($T:ty, $value:expr, $py:expr) => {
429                    let value = $value;
430                    println!("{}: {}", stringify!($T), value);
431                    let python_value = value.clone().into_pyobject(py).unwrap();
432                    let roundtrip_value = python_value.extract::<$T>().unwrap();
433                    assert_eq!(value, roundtrip_value);
434                };
435            }
436
437            for i in 0..=256usize {
438                // test a lot of values to help catch other bugs too
439                test!(BigInt, BigInt::from(i), py);
440                test!(BigUint, BigUint::from(i), py);
441                test!(BigInt, -BigInt::from(i), py);
442                test!(BigInt, BigInt::from(1) << i, py);
443                test!(BigUint, BigUint::from(1u32) << i, py);
444                test!(BigInt, -BigInt::from(1) << i, py);
445                test!(BigInt, (BigInt::from(1) << i) + 1u32, py);
446                test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py);
447                test!(BigInt, (-BigInt::from(1) << i) + 1u32, py);
448                test!(BigInt, (BigInt::from(1) << i) - 1u32, py);
449                test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py);
450                test!(BigInt, (-BigInt::from(1) << i) - 1u32, py);
451            }
452        });
453    }
454}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here