Skip to main content

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, std::num::nb_index, types::PyInt, Borrowed, Bound, FromPyObject,
54    PyAny, PyErr, PyResult, Python,
55};
56
57use num_bigint::{BigInt, BigUint};
58
59#[cfg(feature = "experimental-inspect")]
60use crate::inspect::PyStaticExpr;
61#[cfg(feature = "experimental-inspect")]
62use crate::PyTypeInfo;
63#[cfg(not(Py_LIMITED_API))]
64use num_bigint::Sign;
65
66// for identical functionality between BigInt and BigUint
67macro_rules! bigint_conversion {
68    ($rust_ty: ty, $is_signed: literal) => {
69        #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
70        impl<'py> IntoPyObject<'py> for $rust_ty {
71            type Target = PyInt;
72            type Output = Bound<'py, Self::Target>;
73            type Error = PyErr;
74
75            #[cfg(feature = "experimental-inspect")]
76            const OUTPUT_TYPE: PyStaticExpr = <&$rust_ty>::OUTPUT_TYPE;
77
78            #[inline]
79            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
80                (&self).into_pyobject(py)
81            }
82        }
83
84        #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
85        impl<'py> IntoPyObject<'py> for &$rust_ty {
86            type Target = PyInt;
87            type Output = Bound<'py, Self::Target>;
88            type Error = PyErr;
89
90            #[cfg(feature = "experimental-inspect")]
91            const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT;
92
93            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
94                use num_traits::ToBytes;
95
96                #[cfg(all(not(Py_LIMITED_API), Py_3_13))]
97                {
98                    use crate::conversions::std::num::int_from_ne_bytes;
99                    let bytes = self.to_ne_bytes();
100                    Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes))
101                }
102
103                #[cfg(all(not(Py_LIMITED_API), not(Py_3_13)))]
104                {
105                    use crate::conversions::std::num::int_from_le_bytes;
106                    let bytes = self.to_le_bytes();
107                    Ok(int_from_le_bytes::<{ $is_signed }>(py, &bytes))
108                }
109
110                #[cfg(Py_LIMITED_API)]
111                {
112                    use $crate::py_result_ext::PyResultExt;
113                    use $crate::types::any::PyAnyMethods;
114                    let bytes = self.to_le_bytes();
115                    let bytes_obj = PyBytes::new(py, &bytes);
116                    let kwargs = if $is_signed {
117                        let kwargs = crate::types::PyDict::new(py);
118                        kwargs.set_item(crate::intern!(py, "signed"), true)?;
119                        Some(kwargs)
120                    } else {
121                        None
122                    };
123                    unsafe {
124                        py.get_type::<PyInt>()
125                            .call_method("from_bytes", (bytes_obj, "little"), kwargs.as_ref())
126                            .cast_into_unchecked()
127                    }
128                }
129            }
130        }
131    };
132}
133
134bigint_conversion!(BigUint, false);
135bigint_conversion!(BigInt, true);
136
137#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
138impl<'py> FromPyObject<'_, 'py> for BigInt {
139    type Error = PyErr;
140
141    #[cfg(feature = "experimental-inspect")]
142    const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT;
143
144    fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<BigInt, Self::Error> {
145        // fast path - checking for subclass of `int` just checks a bit in the type object
146        let num_owned: Bound<'_, PyInt>;
147        let num = if let Ok(long) = ob.cast::<PyInt>() {
148            long
149        } else {
150            num_owned = nb_index(&ob)?;
151            num_owned.as_borrowed()
152        };
153        #[cfg(not(Py_LIMITED_API))]
154        {
155            let mut buffer = int_to_u32_vec::<true>(&num)?;
156            let sign = if buffer.last().copied().is_some_and(|last| last >> 31 != 0) {
157                // BigInt::new takes an unsigned array, so need to convert from two's complement
158                // flip all bits, 'subtract' 1 (by adding one to the unsigned array)
159                let mut elements = buffer.iter_mut();
160                for element in elements.by_ref() {
161                    *element = (!*element).wrapping_add(1);
162                    if *element != 0 {
163                        // if the element didn't wrap over, no need to keep adding further ...
164                        break;
165                    }
166                }
167                // ... so just two's complement the rest
168                for element in elements {
169                    *element = !*element;
170                }
171                Sign::Minus
172            } else {
173                Sign::Plus
174            };
175            Ok(BigInt::new(sign, buffer))
176        }
177        #[cfg(Py_LIMITED_API)]
178        {
179            let n_bits = int_n_bits(&num)?;
180            if n_bits == 0 {
181                return Ok(BigInt::from(0isize));
182            }
183            let bytes = int_to_py_bytes(&num, (n_bits + 8) / 8, true)?;
184            Ok(BigInt::from_signed_bytes_le(bytes.as_bytes()))
185        }
186    }
187}
188
189#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
190impl<'py> FromPyObject<'_, 'py> for BigUint {
191    type Error = PyErr;
192
193    #[cfg(feature = "experimental-inspect")]
194    const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT;
195
196    fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<BigUint, Self::Error> {
197        // fast path - checking for subclass of `int` just checks a bit in the type object
198        let num_owned: Bound<'_, PyInt>;
199        let num = if let Ok(long) = ob.cast::<PyInt>() {
200            long
201        } else {
202            num_owned = nb_index(&ob)?;
203            num_owned.as_borrowed()
204        };
205        #[cfg(not(Py_LIMITED_API))]
206        {
207            let buffer = int_to_u32_vec::<false>(&num)?;
208            Ok(BigUint::new(buffer))
209        }
210        #[cfg(Py_LIMITED_API)]
211        {
212            let n_bits = int_n_bits(&num)?;
213            if n_bits == 0 {
214                return Ok(BigUint::from(0usize));
215            }
216            let bytes = int_to_py_bytes(&num, n_bits.div_ceil(8), false)?;
217            Ok(BigUint::from_bytes_le(bytes.as_bytes()))
218        }
219    }
220}
221
222#[cfg(not(any(Py_LIMITED_API, Py_3_13)))]
223#[inline]
224fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
225    use crate::ffi;
226
227    let mut buffer = Vec::new();
228    let n_bits = int_n_bits(long)?;
229    if n_bits == 0 {
230        return Ok(buffer);
231    }
232    let n_digits = if SIGNED {
233        (n_bits + 32) / 32
234    } else {
235        n_bits.div_ceil(32)
236    };
237    buffer.reserve_exact(n_digits);
238    unsafe {
239        crate::err::error_on_minusone(
240            long.py(),
241            ffi::_PyLong_AsByteArray(
242                long.as_ptr().cast(),
243                buffer.as_mut_ptr() as *mut u8,
244                n_digits * 4,
245                1,
246                SIGNED.into(),
247            ),
248        )?;
249        buffer.set_len(n_digits)
250    };
251    buffer
252        .iter_mut()
253        .for_each(|chunk| *chunk = u32::from_le(*chunk));
254
255    Ok(buffer)
256}
257
258#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
259#[inline]
260fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
261    use crate::ffi;
262
263    let mut buffer = Vec::new();
264    let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
265    if !SIGNED {
266        flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE;
267    }
268    let n_bytes =
269        unsafe { ffi::PyLong_AsNativeBytes(long.as_ptr().cast(), std::ptr::null_mut(), 0, flags) };
270    let n_bytes_unsigned: usize = n_bytes
271        .try_into()
272        .map_err(|_| crate::PyErr::fetch(long.py()))?;
273    if n_bytes == 0 {
274        return Ok(buffer);
275    }
276    let n_digits = n_bytes_unsigned.div_ceil(4);
277    buffer.reserve_exact(n_digits);
278    unsafe {
279        ffi::PyLong_AsNativeBytes(
280            long.as_ptr().cast(),
281            buffer.as_mut_ptr().cast(),
282            (n_digits * 4).try_into().unwrap(),
283            flags,
284        );
285        buffer.set_len(n_digits);
286    };
287    buffer
288        .iter_mut()
289        .for_each(|chunk| *chunk = u32::from_le(*chunk));
290
291    Ok(buffer)
292}
293
294#[cfg(Py_LIMITED_API)]
295fn int_to_py_bytes<'py>(
296    long: &Bound<'py, PyInt>,
297    n_bytes: usize,
298    is_signed: bool,
299) -> PyResult<Bound<'py, PyBytes>> {
300    use crate::intern;
301    use crate::types::any::PyAnyMethods;
302    let py = long.py();
303    let kwargs = if is_signed {
304        let kwargs = crate::types::PyDict::new(py);
305        kwargs.set_item(intern!(py, "signed"), true)?;
306        Some(kwargs)
307    } else {
308        None
309    };
310    let bytes = long.call_method(
311        intern!(py, "to_bytes"),
312        (n_bytes, intern!(py, "little")),
313        kwargs.as_ref(),
314    )?;
315    Ok(bytes.cast_into()?)
316}
317
318#[inline]
319#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
320fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult<usize> {
321    let py = long.py();
322    #[cfg(not(Py_LIMITED_API))]
323    {
324        // fast path
325        let n_bits = unsafe { crate::ffi::_PyLong_NumBits(long.as_ptr()) };
326        if n_bits == (-1isize as usize) {
327            return Err(crate::PyErr::fetch(py));
328        }
329        Ok(n_bits)
330    }
331
332    #[cfg(Py_LIMITED_API)]
333    {
334        // slow path
335        use crate::types::PyAnyMethods;
336        long.call_method0(crate::intern!(py, "bit_length"))
337            .and_then(|any| any.extract())
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344    use crate::exceptions::PyTypeError;
345    use crate::test_utils::generate_unique_module_name;
346    use crate::types::{PyAnyMethods as _, PyDict, PyModule};
347    use pyo3_ffi::c_str;
348
349    fn rust_fib<T>() -> impl Iterator<Item = T>
350    where
351        T: From<u16>,
352        for<'a> &'a T: std::ops::Add<Output = T>,
353    {
354        let mut f0: T = T::from(1);
355        let mut f1: T = T::from(1);
356        std::iter::from_fn(move || {
357            let f2 = &f0 + &f1;
358            Some(std::mem::replace(&mut f0, std::mem::replace(&mut f1, f2)))
359        })
360    }
361
362    fn python_fib(py: Python<'_>) -> impl Iterator<Item = Bound<'_, PyAny>> + '_ {
363        let mut f0 = 1i32.into_pyobject(py).unwrap().into_any();
364        let mut f1 = 1i32.into_pyobject(py).unwrap().into_any();
365        std::iter::from_fn(move || {
366            let f2 = f0.call_method1("__add__", (&f1,)).unwrap();
367            Some(std::mem::replace(&mut f0, std::mem::replace(&mut f1, f2)))
368        })
369    }
370
371    #[test]
372    fn convert_biguint() {
373        Python::attach(|py| {
374            // check the first 2000 numbers in the fibonacci sequence
375            for (py_result, rs_result) in python_fib(py).zip(rust_fib::<BigUint>()).take(2000) {
376                // Python -> Rust
377                assert_eq!(py_result.extract::<BigUint>().unwrap(), rs_result);
378                // Rust -> Python
379                assert!(py_result.eq(rs_result).unwrap());
380            }
381        });
382    }
383
384    #[test]
385    fn convert_bigint() {
386        Python::attach(|py| {
387            // check the first 2000 numbers in the fibonacci sequence
388            for (py_result, rs_result) in python_fib(py).zip(rust_fib::<BigInt>()).take(2000) {
389                // Python -> Rust
390                assert_eq!(py_result.extract::<BigInt>().unwrap(), rs_result);
391                // Rust -> Python
392                assert!(py_result.eq(&rs_result).unwrap());
393
394                // negate
395
396                let rs_result = rs_result * -1;
397                let py_result = py_result.call_method0("__neg__").unwrap();
398
399                // Python -> Rust
400                assert_eq!(py_result.extract::<BigInt>().unwrap(), rs_result);
401                // Rust -> Python
402                assert!(py_result.eq(rs_result).unwrap());
403            }
404        });
405    }
406
407    fn python_index_class(py: Python<'_>) -> Bound<'_, PyModule> {
408        let index_code = c_str!(
409            r#"
410class C:
411    def __init__(self, x):
412        self.x = x
413    def __index__(self):
414        return self.x
415"#
416        );
417        PyModule::from_code(
418            py,
419            index_code,
420            c"index.py",
421            &generate_unique_module_name("index"),
422        )
423        .unwrap()
424    }
425
426    #[test]
427    fn convert_index_class() {
428        Python::attach(|py| {
429            let index = python_index_class(py);
430            let locals = PyDict::new(py);
431            locals.set_item("index", index).unwrap();
432            let ob = py.eval(c"index.C(10)", None, Some(&locals)).unwrap();
433            let _: BigInt = ob.extract().unwrap();
434            let _: BigUint = ob.extract().unwrap();
435        });
436    }
437
438    #[test]
439    fn handle_zero() {
440        Python::attach(|py| {
441            let zero: BigInt = 0i32.into_pyobject(py).unwrap().extract().unwrap();
442            assert_eq!(zero, BigInt::from(0));
443        })
444    }
445
446    /// `OverflowError` on converting Python int to BigInt, see issue #629
447    #[test]
448    fn check_overflow() {
449        Python::attach(|py| {
450            macro_rules! test {
451                ($T:ty, $value:expr, $py:expr) => {
452                    let value = $value;
453                    println!("{}: {}", stringify!($T), value);
454                    let python_value = value.clone().into_pyobject(py).unwrap();
455                    let roundtrip_value = python_value.extract::<$T>().unwrap();
456                    assert_eq!(value, roundtrip_value);
457                };
458            }
459
460            for i in 0..=256usize {
461                // test a lot of values to help catch other bugs too
462                test!(BigInt, BigInt::from(i), py);
463                test!(BigUint, BigUint::from(i), py);
464                test!(BigInt, -BigInt::from(i), py);
465                test!(BigInt, BigInt::from(1) << i, py);
466                test!(BigUint, BigUint::from(1u32) << i, py);
467                test!(BigInt, -BigInt::from(1) << i, py);
468                test!(BigInt, (BigInt::from(1) << i) + 1u32, py);
469                test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py);
470                test!(BigInt, (-BigInt::from(1) << i) + 1u32, py);
471                test!(BigInt, (BigInt::from(1) << i) - 1u32, py);
472                test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py);
473                test!(BigInt, (-BigInt::from(1) << i) - 1u32, py);
474            }
475        });
476    }
477
478    #[test]
479    fn from_py_float_type_error() {
480        Python::attach(|py| {
481            let obj = (12.3f64).into_pyobject(py).unwrap();
482            let err = obj.extract::<BigInt>().unwrap_err();
483            assert!(err.is_instance_of::<PyTypeError>(py));
484
485            let err = obj.extract::<BigUint>().unwrap_err();
486            assert!(err.is_instance_of::<PyTypeError>(py));
487        });
488    }
489}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here