#![cfg(feature = "enable_pyo3")]
use crate::integer::Integer;
use crate::platform::Limb;
use alloc::vec::Vec;
use core::convert::Infallible;
use malachite_base::num::basic::traits::Zero;
#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
use pyo3::intern;
#[allow(unused_imports)]
use pyo3::{
Borrowed, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, PyErr, PyResult, Python, ffi,
types::*,
};
#[cfg_attr(docsrs, doc(cfg(feature = "enable_pyo3")))]
impl<'py> FromPyObject<'_, 'py> for Integer {
type Error = PyErr;
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, PyErr> {
let py = ob.py();
let num_owned: Bound<'_, PyInt>;
let num = if let Ok(long) = ob.cast::<PyInt>() {
long
} else {
num_owned =
unsafe { Bound::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? }
.cast_into()?;
num_owned.as_borrowed()
};
Ok(Self::from_owned_twos_complement_limbs_asc(int_to_limbs(
&num, true,
)?))
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "enable_pyo3")))]
impl<'py> IntoPyObject<'py> for Integer {
type Target = PyInt;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "enable_pyo3")))]
impl<'py> IntoPyObject<'py> for &Integer {
type Target = PyInt;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
if self == &Integer::ZERO {
return 0i32.into_pyobject(py);
}
let bytes = limbs_to_bytes(
self.twos_complement_limbs(),
self.twos_complement_limb_count(),
);
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
unsafe {
let flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
let obj = ffi::PyLong_FromNativeBytes(bytes.as_ptr().cast(), bytes.len(), flags);
Ok(Bound::from_owned_ptr(py, obj).cast_into_unchecked())
}
#[cfg(all(not(Py_LIMITED_API), not(Py_3_13)))]
unsafe {
let obj = ffi::_PyLong_FromByteArray(
bytes.as_ptr().cast(),
bytes.len(),
1, true.into(), );
Ok(Bound::from_owned_ptr(py, obj).cast_into_unchecked())
}
#[cfg(Py_LIMITED_API)]
unsafe {
let bytes_obj = PyBytes::new(py, &bytes);
let kwargs = PyDict::new(py);
kwargs.set_item(intern!(py, "signed"), true).unwrap();
py.get_type::<PyInt>()
.call_method("from_bytes", (bytes_obj, "little"), Some(&kwargs))
.expect("int.from_bytes() failed during into_pyobject()")
.cast_into_unchecked()
}
}
}
#[cfg(feature = "32_bit_limbs")]
#[inline]
fn limbs_to_bytes(limbs: impl Iterator<Item = u32>, limb_count: u64) -> Vec<u8> {
let mut bytes = Vec::with_capacity((limb_count << 3) as usize);
for limb in limbs {
for byte in limb.to_le_bytes() {
bytes.push(byte);
}
}
bytes
}
#[cfg(not(feature = "32_bit_limbs"))]
#[inline]
fn limbs_to_bytes(limbs: impl Iterator<Item = u64>, limb_count: u64) -> Vec<u8> {
let mut bytes = Vec::with_capacity((limb_count << 3) as usize);
for limb in limbs {
for byte in limb.to_le_bytes() {
bytes.push(byte);
}
}
bytes
}
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
#[inline]
fn int_to_limbs(long: &Bound<PyInt>, is_signed: bool) -> PyResult<Vec<Limb>> {
let py = long.py();
let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
if !is_signed {
flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE;
}
let n_bytes =
unsafe { ffi::PyLong_AsNativeBytes(long.as_ptr().cast(), core::ptr::null_mut(), 0, flags) };
let n_bytes: usize = n_bytes.try_into().map_err(|_| PyErr::fetch(py))?;
if n_bytes == 0 {
return Ok(Vec::new());
}
let n_limbs = n_bytes.div_ceil(size_of::<Limb>());
let mut buffer = Vec::<Limb>::with_capacity(n_limbs);
unsafe {
let written = ffi::PyLong_AsNativeBytes(
long.as_ptr().cast(),
buffer.as_mut_ptr().cast(),
(n_limbs * size_of::<Limb>()).try_into().unwrap(),
flags,
);
if written < 0 {
return Err(PyErr::fetch(py));
}
buffer.set_len(n_limbs);
};
buffer
.iter_mut()
.for_each(|limb| *limb = Limb::from_le(*limb));
Ok(buffer)
}
#[cfg(all(not(Py_LIMITED_API), not(Py_3_13)))]
#[inline]
fn int_to_limbs(long: &Bound<PyInt>, is_signed: bool) -> PyResult<Vec<Limb>> {
let py = long.py();
let n_bits = int_n_bits(long)?;
if n_bits == 0 {
return Ok(Vec::new());
}
let n_limbs = (n_bits + usize::from(is_signed)).div_ceil(size_of::<Limb>() << 3);
let mut buffer = Vec::<Limb>::with_capacity(n_limbs);
unsafe {
let error_code = ffi::_PyLong_AsByteArray(
long.as_ptr().cast(),
buffer.as_mut_ptr().cast(),
n_limbs * size_of::<Limb>(),
1, is_signed.into(), );
if error_code == -1 {
return Err(PyErr::fetch(py));
}
buffer.set_len(n_limbs);
};
buffer
.iter_mut()
.for_each(|limb| *limb = Limb::from_le(*limb));
Ok(buffer)
}
#[cfg(Py_LIMITED_API)]
#[inline]
fn int_to_limbs(long: &Bound<PyInt>, is_signed: bool) -> PyResult<Vec<Limb>> {
let n_bits = int_n_bits(long)?;
if n_bits == 0 {
return Ok(Vec::new());
}
let n_limbs = (n_bits + usize::from(is_signed)).div_ceil(size_of::<Limb>() << 3);
let py_bytes = int_to_py_bytes(long, n_limbs * size_of::<Limb>(), is_signed)?;
Ok(py_bytes
.as_bytes()
.chunks_exact(size_of::<Limb>())
.map(|chunk| Limb::from_le_bytes(chunk.try_into().unwrap()))
.collect())
}
#[cfg(Py_LIMITED_API)]
#[inline]
fn int_to_py_bytes<'py>(
long: &Bound<'py, PyInt>,
n_bytes: usize,
is_signed: bool,
) -> PyResult<Bound<'py, PyBytes>> {
let py = long.py();
let kwargs_dict = PyDict::new(py);
let kwargs = if is_signed {
kwargs_dict.set_item(intern!(py, "signed"), true)?;
Some(&kwargs_dict)
} else {
None
};
let bytes = long.call_method(
intern!(py, "to_bytes"),
(n_bytes, intern!(py, "little")),
kwargs,
)?;
Ok(bytes.cast_into()?)
}
#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
#[inline]
fn int_n_bits(long: &Bound<PyInt>) -> PyResult<usize> {
let py = long.py();
long.call_method0(intern!(py, "bit_length"))
.and_then(|ob| ob.extract())
}
#[cfg(test)]
mod tests {
use super::*;
fn prepare_python() {
Python::initialize();
}
fn rust_fib<T>() -> impl Iterator<Item = T>
where
T: From<u8>,
for<'a> &'a T: std::ops::Add<Output = T>,
{
let mut f0: T = T::from(1);
let mut f1: T = T::from(1);
std::iter::from_fn(move || {
let f2 = &f0 + &f1;
Some(std::mem::replace(&mut f0, std::mem::replace(&mut f1, f2)))
})
}
fn python_fib(py: Python<'_>) -> impl Iterator<Item = Py<PyInt>> {
let mut f0 = 1i32.into_pyobject(py).unwrap();
let mut f1 = 1i32.into_pyobject(py).unwrap();
std::iter::from_fn(move || {
let f2 = f0
.call_method1("__add__", (&f1,))
.unwrap()
.cast_into::<PyInt>()
.unwrap();
Some(std::mem::replace(&mut f0, std::mem::replace(&mut f1, f2)).unbind())
})
}
fn python_index_class(py: Python<'_>) -> Bound<'_, PyModule> {
let index_code = c"
class C:
def __init__(self, x):
self.x = x
def __index__(self):
return self.x
";
let filename = c"index.py";
let modulename = c"index";
PyModule::from_code(py, index_code, filename, modulename).unwrap()
}
#[test]
fn convert_integer() {
prepare_python();
Python::attach(|py| {
for (py_result, rs_result) in python_fib(py).zip(rust_fib::<Integer>()).take(2000) {
assert_eq!(py_result.extract::<Integer>(py).unwrap(), rs_result);
assert!(
py_result
.bind(py)
.as_any()
.eq(rs_result.clone().into_pyobject(py).unwrap())
.unwrap()
);
let rs_result = rs_result * Integer::from(-1);
let py_result = py_result.call_method0(py, "__neg__").unwrap();
assert_eq!(py_result.extract::<Integer>(py).unwrap(), rs_result);
assert!(py_result.bind(py).eq(&rs_result).unwrap());
}
});
}
#[test]
fn convert_index_class() {
prepare_python();
Python::attach(|py| {
let index = python_index_class(py);
let locals = PyDict::new(py);
locals.set_item("index", &index).unwrap();
let expr = c"index.C(10)";
let ob = py.eval(expr, None, Some(&locals)).unwrap();
let integer: Integer = ob.extract().unwrap();
assert_eq!(integer, Integer::from(10));
let expr2 = c"index.C(-10)";
let ob2 = py.eval(expr2, None, Some(&locals)).unwrap();
let integer2: Integer = ob2.extract().unwrap();
assert_eq!(integer2, Integer::from(-10));
});
}
#[test]
fn handle_zero() {
prepare_python();
Python::attach(|py| {
let zero_integer: Integer = 0u8.into_pyobject(py).unwrap().extract().unwrap();
assert_eq!(zero_integer, Integer::from(0));
let zero_integer = zero_integer.into_pyobject(py).unwrap();
assert!(
zero_integer
.as_any()
.eq(0u8.into_py_any(py).unwrap())
.unwrap()
);
});
}
#[test]
fn check_overflow() {
prepare_python();
Python::attach(|py| {
macro_rules! test {
($T:ty, $value:expr, $py:expr) => {
let value = $value;
println!("{}: {}", stringify!($T), value);
let python_value = value.clone().into_pyobject(py).unwrap();
let roundtrip_value = python_value.extract::<$T>().unwrap();
assert_eq!(value, roundtrip_value);
};
}
for i in 0..=256usize {
test!(Integer, Integer::from(i), py);
test!(Integer, -Integer::from(i), py);
test!(Integer, Integer::from(1) << i, py);
test!(Integer, -Integer::from(1) << i, py);
test!(Integer, (Integer::from(1) << i) + Integer::from(1u32), py);
test!(Integer, (-Integer::from(1) << i) + Integer::from(1u32), py);
test!(Integer, (Integer::from(1) << i) - Integer::from(1u32), py);
test!(Integer, (-Integer::from(1) << i) - Integer::from(1u32), py);
}
});
}
}