Merge pull request #1670 from mejrs/num-dependency
Add docs + slight refactor for num-complex and num-bigint dependencies
This commit is contained in:
commit
05e4bdf69c
|
@ -69,6 +69,14 @@ The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use R
|
|||
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
|
||||
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.
|
||||
|
||||
### `num-bigint`
|
||||
|
||||
This feature adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conversions into its [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUInt.html) types.
|
||||
|
||||
### `num-complex`
|
||||
|
||||
This feature adds a dependency on [num-complex](https://docs.rs/num-complex) and enables conversions into its [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type.
|
||||
|
||||
### `serde`
|
||||
|
||||
The `serde` feature enables (de)serialization of Py<T> objects via [serde](https://serde.rs/).
|
||||
|
|
|
@ -80,12 +80,12 @@
|
|||
//! [`#[pyclass]`](crate::proc_macro::pyclass). This adds a dependency on the
|
||||
//! [`inventory`](https://docs.rs/inventory) crate, which is not supported on all platforms.
|
||||
//
|
||||
//! - `num-bigint`: Enables conversions between Python objects and
|
||||
//! - [`num-bigint`](./num_bigint/index.html): Enables conversions between Python objects and
|
||||
//! [num-bigint](https://docs.rs/num-bigint)'s
|
||||
//! [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and
|
||||
//! [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types.
|
||||
//
|
||||
//! - `num-complex`: Enables conversions between Python objects and
|
||||
//! - [`num-complex`](crate::num_complex): Enables conversions between Python objects and
|
||||
//! [num-complex](https://docs.rs/num-complex)'s
|
||||
//! [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type.
|
||||
//
|
||||
|
@ -298,6 +298,10 @@ mod python;
|
|||
pub mod type_object;
|
||||
pub mod types;
|
||||
|
||||
pub mod num_bigint;
|
||||
|
||||
pub mod num_complex;
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
//
|
||||
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
|
||||
|
||||
#![cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))]
|
||||
#![cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy)))))
|
||||
)]
|
||||
//! Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types.
|
||||
//!
|
||||
//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types.
|
||||
//!
|
||||
//! # Setup
|
||||
//!
|
||||
//! To use this feature, add this to your **`Cargo.toml`**:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! # change * to the latest versions
|
||||
//! num-bigint = "*"
|
||||
//! pyo3 = { version = "*", features = ["num-bigint"] }
|
||||
//! ```
|
||||
//!
|
||||
//! Note that you must use compatible versions of num-bigint and PyO3.
|
||||
//! The required num-bigint version may vary based on the version of PyO3.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Using [`BigInt`] to correctly increment an arbitrary precision integer.
|
||||
//! This is not possible with Rust's native integers if the Python integer is too large,
|
||||
//! in which case it will fail its conversion and raise `OverflowError`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use num_bigint::BigInt;
|
||||
//! use pyo3::prelude::*;
|
||||
//! use pyo3::wrap_pyfunction;
|
||||
//!
|
||||
//! #[pyfunction]
|
||||
//! fn add_one(n: BigInt) -> BigInt {
|
||||
//! n + 1
|
||||
//! }
|
||||
//!
|
||||
//! #[pymodule]
|
||||
//! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
//! m.add_function(wrap_pyfunction!(add_one, m)?)?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Python code:
|
||||
//! ```python
|
||||
//! from my_module import add_one
|
||||
//!
|
||||
//! n = 1 << 1337
|
||||
//! value = add_one(n)
|
||||
//!
|
||||
//! assert n + 1 == value
|
||||
//! ```
|
||||
|
||||
use crate::{
|
||||
err, ffi, types::*, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyErr, PyNativeType,
|
||||
PyObject, PyResult, Python, ToPyObject,
|
||||
};
|
||||
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use std::os::raw::{c_int, c_uchar};
|
||||
|
||||
#[cfg(not(all(windows, PyPy)))]
|
||||
unsafe fn extract(ob: &PyLong, buffer: &mut [c_uchar], is_signed: c_int) -> PyResult<()> {
|
||||
err::error_on_minusone(
|
||||
ob.py(),
|
||||
ffi::_PyLong_AsByteArray(
|
||||
ob.as_ptr() as *mut ffi::PyLongObject,
|
||||
buffer.as_mut_ptr(),
|
||||
buffer.len(),
|
||||
1,
|
||||
is_signed,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! bigint_conversion {
|
||||
($rust_ty: ty, $is_signed: expr, $to_bytes: path, $from_bytes: path) => {
|
||||
impl ToPyObject for $rust_ty {
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
unsafe {
|
||||
let bytes = $to_bytes(self);
|
||||
let obj = ffi::_PyLong_FromByteArray(
|
||||
bytes.as_ptr() as *const c_uchar,
|
||||
bytes.len(),
|
||||
1,
|
||||
$is_signed,
|
||||
);
|
||||
PyObject::from_owned_ptr(py, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl IntoPy<PyObject> for $rust_ty {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
self.to_object(py)
|
||||
}
|
||||
}
|
||||
impl<'source> FromPyObject<'source> for $rust_ty {
|
||||
fn extract(ob: &'source PyAny) -> PyResult<$rust_ty> {
|
||||
let py = ob.py();
|
||||
unsafe {
|
||||
let num = ffi::PyNumber_Index(ob.as_ptr());
|
||||
if num.is_null() {
|
||||
return Err(PyErr::fetch(py));
|
||||
}
|
||||
let n_bits = ffi::_PyLong_NumBits(num);
|
||||
let n_bytes = if n_bits < 0 {
|
||||
return Err(PyErr::fetch(py));
|
||||
} else if n_bits == 0 {
|
||||
0
|
||||
} else {
|
||||
(n_bits as usize - 1 + $is_signed) / 8 + 1
|
||||
};
|
||||
let num: Py<PyLong> = Py::from_owned_ptr(py, num);
|
||||
if n_bytes <= 128 {
|
||||
let mut buffer = [0; 128];
|
||||
extract(num.as_ref(py), &mut buffer[..n_bytes], $is_signed)?;
|
||||
Ok($from_bytes(&buffer[..n_bytes]))
|
||||
} else {
|
||||
let mut buffer = vec![0; n_bytes];
|
||||
extract(num.as_ref(py), &mut buffer, $is_signed)?;
|
||||
Ok($from_bytes(&buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
bigint_conversion!(BigUint, 0, BigUint::to_bytes_le, BigUint::from_bytes_le);
|
||||
bigint_conversion!(
|
||||
BigInt,
|
||||
1,
|
||||
BigInt::to_signed_bytes_le,
|
||||
BigInt::from_signed_bytes_le
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::{PyDict, PyModule};
|
||||
use indoc::indoc;
|
||||
|
||||
fn python_fib(py: Python) -> &PyModule {
|
||||
let fib_code = indoc!(
|
||||
r#"
|
||||
def fib(n):
|
||||
f0, f1 = 0, 1
|
||||
for _ in range(n):
|
||||
f0, f1 = f1, f0 + f1
|
||||
return f0
|
||||
|
||||
def fib_neg(n):
|
||||
return -fib(n)
|
||||
"#
|
||||
);
|
||||
PyModule::from_code(py, fib_code, "fib.py", "fib").unwrap()
|
||||
}
|
||||
|
||||
fn rust_fib<T>(n: usize) -> T
|
||||
where
|
||||
T: From<u16>,
|
||||
for<'a> &'a T: std::ops::Add<Output = T>,
|
||||
{
|
||||
let mut f0: T = T::from(0);
|
||||
let mut f1: T = T::from(1);
|
||||
for _ in 0..n {
|
||||
let f2 = &f0 + &f1;
|
||||
f0 = std::mem::replace(&mut f1, f2);
|
||||
}
|
||||
f0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_biguint() {
|
||||
Python::with_gil(|py| {
|
||||
let rs_result: BigUint = rust_fib(400);
|
||||
let fib = python_fib(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("rs_result", &rs_result).unwrap();
|
||||
locals.set_item("fib", fib).unwrap();
|
||||
// Checks if Rust BigUint -> Python Long conversion is correct
|
||||
py.run("assert fib.fib(400) == rs_result", None, Some(locals))
|
||||
.unwrap();
|
||||
// Checks if Python Long -> Rust BigUint conversion is correct if N is small
|
||||
let py_result: BigUint =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
// Checks if Python Long -> Rust BigUint conversion is correct if N is large
|
||||
let rs_result: BigUint = rust_fib(2000);
|
||||
let py_result: BigUint =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_bigint() {
|
||||
Python::with_gil(|py| {
|
||||
let rs_result = rust_fib::<BigInt>(400) * -1;
|
||||
let fib = python_fib(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("rs_result", &rs_result).unwrap();
|
||||
locals.set_item("fib", fib).unwrap();
|
||||
// Checks if Rust BigInt -> Python Long conversion is correct
|
||||
py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals))
|
||||
.unwrap();
|
||||
// Checks if Python Long -> Rust BigInt conversion is correct if N is small
|
||||
let py_result: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
// Checks if Python Long -> Rust BigInt conversion is correct if N is large
|
||||
let rs_result = rust_fib::<BigInt>(2000) * -1;
|
||||
let py_result: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
})
|
||||
}
|
||||
|
||||
fn python_index_class(py: Python) -> &PyModule {
|
||||
let index_code = indoc!(
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
def __index__(self):
|
||||
return self.x
|
||||
"#
|
||||
);
|
||||
PyModule::from_code(py, index_code, "index.py", "index").unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_index_class() {
|
||||
Python::with_gil(|py| {
|
||||
let index = python_index_class(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("index", index).unwrap();
|
||||
let ob = py.eval("index.C(10)", None, Some(locals)).unwrap();
|
||||
let _: BigInt = FromPyObject::extract(ob).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_zero() {
|
||||
Python::with_gil(|py| {
|
||||
let fib = python_fib(py);
|
||||
let zero: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap();
|
||||
assert_eq!(zero, BigInt::from(0));
|
||||
})
|
||||
}
|
||||
|
||||
/// `OverflowError` on converting Python int to BigInt, see issue #629
|
||||
#[test]
|
||||
fn check_overflow() {
|
||||
Python::with_gil(|py| {
|
||||
macro_rules! test {
|
||||
($T:ty, $value:expr, $py:expr) => {
|
||||
let value = $value;
|
||||
println!("{}: {}", stringify!($T), value);
|
||||
let python_value = value.clone().to_object(py);
|
||||
let roundtrip_value = python_value.extract::<$T>(py).unwrap();
|
||||
assert_eq!(value, roundtrip_value);
|
||||
};
|
||||
}
|
||||
|
||||
for i in 0..=256usize {
|
||||
// test a lot of values to help catch other bugs too
|
||||
test!(BigInt, BigInt::from(i), py);
|
||||
test!(BigUint, BigUint::from(i), py);
|
||||
test!(BigInt, -BigInt::from(i), py);
|
||||
test!(BigInt, BigInt::from(1) << i, py);
|
||||
test!(BigUint, BigUint::from(1u32) << i, py);
|
||||
test!(BigInt, -BigInt::from(1) << i, py);
|
||||
test!(BigInt, (BigInt::from(1) << i) + 1u32, py);
|
||||
test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py);
|
||||
test!(BigInt, (-BigInt::from(1) << i) + 1u32, py);
|
||||
test!(BigInt, (BigInt::from(1) << i) - 1u32, py);
|
||||
test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py);
|
||||
test!(BigInt, (-BigInt::from(1) << i) - 1u32, py);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
#![cfg(feature = "num-complex")]
|
||||
#![cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
|
||||
//! Conversions to and from [num-complex](https://docs.rs/num-complex)’
|
||||
//! [`Complex`]`<`[`f32`]`>` and [`Complex`]`<`[`f64`]`>`.
|
||||
//!
|
||||
//! num-complex’ [`Complex`] supports more operations than PyO3's [`PyComplex`]
|
||||
//! and can be used with the rest of the Rust ecosystem.
|
||||
//!
|
||||
//! # Setup
|
||||
//!
|
||||
//! To use this feature, add this to your **`Cargo.toml`**:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! # change * to the latest versions
|
||||
//! num-complex = "*"
|
||||
//! pyo3 = { version = "*", features = ["num-complex"] }
|
||||
//! ```
|
||||
//!
|
||||
//! Note that you must use compatible versions of num-complex and PyO3.
|
||||
//! The required num-complex version may vary based on the version of PyO3.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Using [num-complex](https://docs.rs/num-complex) and [nalgebra](https://docs.rs/nalgebra)
|
||||
//! to create a pyfunction that calculates the eigenvalues of a 2x2 matrix.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! # // not tested because nalgebra isn't supported on msrv
|
||||
//! # // please file an issue if it breaks!
|
||||
//! use nalgebra::base::{dimension::Const, storage::Storage, Matrix};
|
||||
//! use num_complex::Complex;
|
||||
//! use pyo3::prelude::*;
|
||||
//! use pyo3::wrap_pyfunction;
|
||||
//!
|
||||
//! type T = Complex<f64>;
|
||||
//!
|
||||
//! #[pyfunction]
|
||||
//! fn get_eigenvalues(m11: T, m12: T, m21: T, m22: T) -> Vec<T> {
|
||||
//! let mat = Matrix::<T, Const<2>, Const<2>, _>::new(m11, m12, m21, m22);
|
||||
//!
|
||||
//! match mat.eigenvalues() {
|
||||
//! Some(e) => e.data.as_slice().to_vec(),
|
||||
//! None => vec![],
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[pymodule]
|
||||
//! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
//! m.add_function(wrap_pyfunction!(get_eigenvalues, m)?)?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! # // test
|
||||
//! # use assert_approx_eq::assert_approx_eq;
|
||||
//! # use nalgebra::ComplexField;
|
||||
//! # use pyo3::types::PyComplex;
|
||||
//! #
|
||||
//! # fn main() -> PyResult<()> {
|
||||
//! # Python::with_gil(|py| -> PyResult<()> {
|
||||
//! # let module = PyModule::new(py, "my_module")?;
|
||||
//! #
|
||||
//! # module.add_function(wrap_pyfunction!(get_eigenvalues, module)?)?;
|
||||
//! #
|
||||
//! # let m11 = PyComplex::from_doubles(py, 0_f64, -1_f64);
|
||||
//! # let m12 = PyComplex::from_doubles(py, 1_f64, 0_f64);
|
||||
//! # let m21 = PyComplex::from_doubles(py, 2_f64, -1_f64);
|
||||
//! # let m22 = PyComplex::from_doubles(py, -1_f64, 0_f64);
|
||||
//! #
|
||||
//! # let result = module
|
||||
//! # .getattr("get_eigenvalues")?
|
||||
//! # .call1((m11, m12, m21, m22))?;
|
||||
//! # println!("eigenvalues: {:?}", result);
|
||||
//! #
|
||||
//! # let result = result.extract::<Vec<T>>()?;
|
||||
//! # let e0 = result[0];
|
||||
//! # let e1 = result[1];
|
||||
//! #
|
||||
//! # assert_approx_eq!(e0, Complex::new(1_f64, -1_f64));
|
||||
//! # assert_approx_eq!(e1, Complex::new(-2_f64, 0_f64));
|
||||
//! #
|
||||
//! # Ok(())
|
||||
//! # })
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Python code:
|
||||
//! ```python
|
||||
//! from my_module import get_eigenvalues
|
||||
//!
|
||||
//! m11 = complex(0,-1)
|
||||
//! m12 = complex(1,0)
|
||||
//! m21 = complex(2,-1)
|
||||
//! m22 = complex(-1,0)
|
||||
//!
|
||||
//! result = get_eigenvalues(m11,m12,m21,m22)
|
||||
//! assert result == [complex(1,-1), complex(-2,0)]
|
||||
//! ```
|
||||
use crate::{
|
||||
ffi, types::PyComplex, AsPyPointer, FromPyObject, PyAny, PyErr, PyNativeType, PyObject,
|
||||
PyResult, Python, ToPyObject,
|
||||
};
|
||||
use num_complex::Complex;
|
||||
use std::os::raw::c_double;
|
||||
|
||||
impl PyComplex {
|
||||
/// Creates a new Python `PyComplex` object from `num_complex`'s [`Complex`].
|
||||
pub fn from_complex<F: Into<c_double>>(py: Python, complex: Complex<F>) -> &PyComplex {
|
||||
unsafe {
|
||||
let ptr = ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into());
|
||||
py.from_owned_ptr(ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! complex_conversion {
|
||||
($float: ty) => {
|
||||
impl ToPyObject for Complex<$float> {
|
||||
#[inline]
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
crate::IntoPy::<PyObject>::into_py(self.to_owned(), py)
|
||||
}
|
||||
}
|
||||
impl crate::IntoPy<PyObject> for Complex<$float> {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
unsafe {
|
||||
let raw_obj =
|
||||
ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double);
|
||||
PyObject::from_owned_ptr(py, raw_obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[allow(clippy::float_cmp)] // The comparison is for an error value
|
||||
impl<'source> FromPyObject<'source> for Complex<$float> {
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Complex<$float>> {
|
||||
unsafe {
|
||||
let val = ffi::PyComplex_AsCComplex(obj.as_ptr());
|
||||
if val.real == -1.0 && PyErr::occurred(obj.py()) {
|
||||
Err(PyErr::fetch(obj.py()))
|
||||
} else {
|
||||
Ok(Complex::new(val.real as $float, val.imag as $float))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(any(Py_LIMITED_API, PyPy))]
|
||||
#[allow(clippy::float_cmp)] // The comparison is for an error value
|
||||
impl<'source> FromPyObject<'source> for Complex<$float> {
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Complex<$float>> {
|
||||
unsafe {
|
||||
let ptr = obj.as_ptr();
|
||||
let real = ffi::PyComplex_RealAsDouble(ptr);
|
||||
if real == -1.0 && PyErr::occurred(obj.py()) {
|
||||
return Err(PyErr::fetch(obj.py()));
|
||||
}
|
||||
let imag = ffi::PyComplex_ImagAsDouble(ptr);
|
||||
Ok(Complex::new(real as $float, imag as $float))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
complex_conversion!(f32);
|
||||
complex_conversion!(f64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip
|
||||
#[test]
|
||||
fn from_complex() {
|
||||
Python::with_gil(|py| {
|
||||
let complex = Complex::new(3.0, 1.2);
|
||||
let py_c = PyComplex::from_complex(py, complex);
|
||||
assert_eq!(py_c.real(), 3.0);
|
||||
assert_eq!(py_c.imag(), 1.2);
|
||||
});
|
||||
}
|
||||
#[test]
|
||||
fn to_from_complex() {
|
||||
Python::with_gil(|py| {
|
||||
let val = Complex::new(3.0, 1.2);
|
||||
let obj = val.to_object(py);
|
||||
assert_eq!(obj.extract::<Complex<f64>>(py).unwrap(), val);
|
||||
});
|
||||
}
|
||||
#[test]
|
||||
fn from_complex_err() {
|
||||
Python::with_gil(|py| {
|
||||
let obj = vec![1].to_object(py);
|
||||
assert!(obj.extract::<Complex<f64>>(py).is_err());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
#[cfg(all(not(PyPy), not(Py_LIMITED_API)))]
|
||||
use crate::instance::PyNativeType;
|
||||
use crate::{ffi, AsPyPointer, PyAny, Python};
|
||||
#[cfg(all(not(PyPy), not(Py_LIMITED_API)))]
|
||||
use std::ops::*;
|
||||
use std::os::raw::c_double;
|
||||
|
||||
/// Represents a Python `complex`.
|
||||
/// Represents a Python [`complex`](https://docs.python.org/3/library/functions.html#complex) object.
|
||||
///
|
||||
/// Note that `PyComplex` supports only basic operations. For advanced operations
|
||||
/// consider using [num-bigint](https://docs.rs/num-bigint)'s [`Complex`] type instead.
|
||||
/// This requires the [`num-complex`](crate::num_complex) feature flag.
|
||||
///
|
||||
/// [`Complex`]: https://docs.rs/num-complex/latest/num_complex/struct.Complex.html
|
||||
#[repr(transparent)]
|
||||
pub struct PyComplex(PyAny);
|
||||
|
||||
|
@ -17,7 +19,7 @@ pyobject_native_type!(
|
|||
);
|
||||
|
||||
impl PyComplex {
|
||||
/// Creates a new Python `complex` object, from its real and imaginary values.
|
||||
/// Creates a new `PyComplex` from the given real and imaginary values.
|
||||
pub fn from_doubles(py: Python, real: c_double, imag: c_double) -> &PyComplex {
|
||||
unsafe {
|
||||
let ptr = ffi::PyComplex_FromDoubles(real, imag);
|
||||
|
@ -28,194 +30,176 @@ impl PyComplex {
|
|||
pub fn real(&self) -> c_double {
|
||||
unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) }
|
||||
}
|
||||
/// Returns the imaginary part the complex number.
|
||||
/// Returns the imaginary part of the complex number.
|
||||
pub fn imag(&self) -> c_double {
|
||||
unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) }
|
||||
}
|
||||
/// Returns `|self|`.
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
pub fn abs(&self) -> c_double {
|
||||
unsafe {
|
||||
let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
ffi::_Py_c_abs(val)
|
||||
}
|
||||
}
|
||||
/// Returns `self ** other`
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
pub fn pow(&self, other: &PyComplex) -> &PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_pow))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[inline(always)]
|
||||
unsafe fn complex_operation(
|
||||
l: &PyComplex,
|
||||
r: &PyComplex,
|
||||
operation: unsafe extern "C" fn(ffi::Py_complex, ffi::Py_complex) -> ffi::Py_complex,
|
||||
) -> *mut ffi::PyObject {
|
||||
let l_val = (*(l.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
let r_val = (*(r.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
ffi::PyComplex_FromCComplex(operation(l_val, r_val))
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
impl<'py> Add for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn add(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_sum))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
impl<'py> Sub for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn sub(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_diff))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
impl<'py> Mul for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn mul(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_prod))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
impl<'py> Div for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn div(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_quot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
|
||||
impl<'py> Neg for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn neg(self) -> &'py PyComplex {
|
||||
unsafe {
|
||||
let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
self.py()
|
||||
.from_owned_ptr(ffi::PyComplex_FromCComplex(ffi::_Py_c_neg(val)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "num-complex")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
|
||||
mod complex_conversion {
|
||||
mod not_limited_impls {
|
||||
use super::*;
|
||||
use crate::{FromPyObject, PyErr, PyNativeType, PyObject, PyResult, ToPyObject};
|
||||
use num_complex::Complex;
|
||||
use crate::instance::PyNativeType;
|
||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||
|
||||
impl PyComplex {
|
||||
/// Creates a new Python `PyComplex` object from `num_complex`'s [`Complex`].
|
||||
pub fn from_complex<F: Into<c_double>>(py: Python, complex: Complex<F>) -> &PyComplex {
|
||||
/// Returns `|self|`.
|
||||
pub fn abs(&self) -> c_double {
|
||||
unsafe {
|
||||
let ptr = ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into());
|
||||
py.from_owned_ptr(ptr)
|
||||
let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
ffi::_Py_c_abs(val)
|
||||
}
|
||||
}
|
||||
/// Returns `self` raised to the power of `other`.
|
||||
pub fn pow(&self, other: &PyComplex) -> &PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_pow))
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! complex_conversion {
|
||||
($float: ty) => {
|
||||
impl ToPyObject for Complex<$float> {
|
||||
#[inline]
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
crate::IntoPy::<PyObject>::into_py(self.to_owned(), py)
|
||||
}
|
||||
}
|
||||
impl crate::IntoPy<PyObject> for Complex<$float> {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
unsafe {
|
||||
let raw_obj =
|
||||
ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double);
|
||||
PyObject::from_owned_ptr(py, raw_obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[allow(clippy::float_cmp)] // The comparison is for an error value
|
||||
impl<'source> FromPyObject<'source> for Complex<$float> {
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Complex<$float>> {
|
||||
unsafe {
|
||||
let val = ffi::PyComplex_AsCComplex(obj.as_ptr());
|
||||
if val.real == -1.0 && PyErr::occurred(obj.py()) {
|
||||
Err(PyErr::fetch(obj.py()))
|
||||
} else {
|
||||
Ok(Complex::new(val.real as $float, val.imag as $float))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(any(Py_LIMITED_API, PyPy))]
|
||||
#[allow(clippy::float_cmp)] // The comparison is for an error value
|
||||
impl<'source> FromPyObject<'source> for Complex<$float> {
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Complex<$float>> {
|
||||
unsafe {
|
||||
let ptr = obj.as_ptr();
|
||||
let real = ffi::PyComplex_RealAsDouble(ptr);
|
||||
if real == -1.0 && PyErr::occurred(obj.py()) {
|
||||
return Err(PyErr::fetch(obj.py()));
|
||||
}
|
||||
let imag = ffi::PyComplex_ImagAsDouble(ptr);
|
||||
Ok(Complex::new(real as $float, imag as $float))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
complex_conversion!(f32);
|
||||
complex_conversion!(f64);
|
||||
|
||||
#[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip
|
||||
#[test]
|
||||
fn from_complex() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let complex = Complex::new(3.0, 1.2);
|
||||
let py_c = PyComplex::from_complex(py, complex);
|
||||
assert_eq!(py_c.real(), 3.0);
|
||||
assert_eq!(py_c.imag(), 1.2);
|
||||
#[inline(always)]
|
||||
unsafe fn complex_operation(
|
||||
l: &PyComplex,
|
||||
r: &PyComplex,
|
||||
operation: unsafe extern "C" fn(ffi::Py_complex, ffi::Py_complex) -> ffi::Py_complex,
|
||||
) -> *mut ffi::PyObject {
|
||||
let l_val = (*(l.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
let r_val = (*(r.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
ffi::PyComplex_FromCComplex(operation(l_val, r_val))
|
||||
}
|
||||
#[test]
|
||||
fn to_from_complex() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let val = Complex::new(3.0, 1.2);
|
||||
let obj = val.to_object(py);
|
||||
assert_eq!(obj.extract::<Complex<f64>>(py).unwrap(), val);
|
||||
|
||||
impl<'py> Add for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn add(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_sum))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn from_complex_err() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let obj = vec![1].to_object(py);
|
||||
assert!(obj.extract::<Complex<f64>>(py).is_err());
|
||||
|
||||
impl<'py> Sub for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn sub(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_diff))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'py> Mul for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn mul(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_prod))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'py> Div for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn div(self, other: &'py PyComplex) -> &'py PyComplex {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr(complex_operation(self, other, ffi::_Py_c_quot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'py> Neg for &'py PyComplex {
|
||||
type Output = &'py PyComplex;
|
||||
fn neg(self) -> &'py PyComplex {
|
||||
unsafe {
|
||||
let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval;
|
||||
self.py()
|
||||
.from_owned_ptr(ffi::PyComplex_FromCComplex(ffi::_Py_c_neg(val)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::PyComplex;
|
||||
use crate::Python;
|
||||
use assert_approx_eq::assert_approx_eq;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
Python::with_gil(|py| {
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l + r;
|
||||
assert_approx_eq!(res.real(), 4.0);
|
||||
assert_approx_eq!(res.imag(), 3.8);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub() {
|
||||
Python::with_gil(|py| {
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l - r;
|
||||
assert_approx_eq!(res.real(), 2.0);
|
||||
assert_approx_eq!(res.imag(), -1.4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul() {
|
||||
Python::with_gil(|py| {
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l * r;
|
||||
assert_approx_eq!(res.real(), -0.12);
|
||||
assert_approx_eq!(res.imag(), 9.0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_div() {
|
||||
Python::with_gil(|py| {
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l / r;
|
||||
assert_approx_eq!(res.real(), 0.788_659_793_814_432_9);
|
||||
assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg() {
|
||||
Python::with_gil(|py| {
|
||||
let val = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let res = -val;
|
||||
assert_approx_eq!(res.real(), -3.0);
|
||||
assert_approx_eq!(res.imag(), -1.2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_abs() {
|
||||
Python::with_gil(|py| {
|
||||
let val = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pow() {
|
||||
Python::with_gil(|py| {
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.2, 2.6);
|
||||
let val = l.pow(r);
|
||||
assert_approx_eq!(val.real(), -1.419_309_997_016_603_7);
|
||||
assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,90 +213,10 @@ mod test {
|
|||
fn test_from_double() {
|
||||
use assert_approx_eq::assert_approx_eq;
|
||||
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let complex = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
assert_approx_eq!(complex.real(), 3.0);
|
||||
assert_approx_eq!(complex.imag(), 1.2);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l + r;
|
||||
assert_approx_eq!(res.real(), 4.0);
|
||||
assert_approx_eq!(res.imag(), 3.8);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_sub() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l - r;
|
||||
assert_approx_eq!(res.real(), 2.0);
|
||||
assert_approx_eq!(res.imag(), -1.4);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_mul() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l * r;
|
||||
assert_approx_eq!(res.real(), -0.12);
|
||||
assert_approx_eq!(res.imag(), 9.0);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_div() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.0, 2.6);
|
||||
let res = l / r;
|
||||
assert_approx_eq!(res.real(), 0.788_659_793_814_432_9);
|
||||
assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_neg() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let val = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let res = -val;
|
||||
assert_approx_eq!(res.real(), -3.0);
|
||||
assert_approx_eq!(res.imag(), -1.2);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_abs() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let val = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2);
|
||||
}
|
||||
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
#[test]
|
||||
fn test_pow() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let l = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
let r = PyComplex::from_doubles(py, 1.2, 2.6);
|
||||
let val = l.pow(r);
|
||||
assert_approx_eq!(val.real(), -1.419_309_997_016_603_7);
|
||||
assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6);
|
||||
Python::with_gil(|py| {
|
||||
let complex = PyComplex::from_doubles(py, 3.0, 1.2);
|
||||
assert_approx_eq!(complex.real(), 3.0);
|
||||
assert_approx_eq!(complex.imag(), 1.2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
236
src/types/num.rs
236
src/types/num.rs
|
@ -357,242 +357,6 @@ mod test_128bit_intergers {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy)))))
|
||||
)]
|
||||
mod bigint_conversion {
|
||||
use super::*;
|
||||
use crate::{err, Py};
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use std::os::raw::{c_int, c_uchar};
|
||||
|
||||
#[cfg(not(all(windows, PyPy)))]
|
||||
unsafe fn extract(ob: &PyLong, buffer: &mut [c_uchar], is_signed: c_int) -> PyResult<()> {
|
||||
err::error_on_minusone(
|
||||
ob.py(),
|
||||
ffi::_PyLong_AsByteArray(
|
||||
ob.as_ptr() as *mut ffi::PyLongObject,
|
||||
buffer.as_mut_ptr(),
|
||||
buffer.len(),
|
||||
1,
|
||||
is_signed,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! bigint_conversion {
|
||||
($rust_ty: ty, $is_signed: expr, $to_bytes: path, $from_bytes: path) => {
|
||||
impl ToPyObject for $rust_ty {
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
unsafe {
|
||||
let bytes = $to_bytes(self);
|
||||
let obj = ffi::_PyLong_FromByteArray(
|
||||
bytes.as_ptr() as *const c_uchar,
|
||||
bytes.len(),
|
||||
1,
|
||||
$is_signed,
|
||||
);
|
||||
PyObject::from_owned_ptr(py, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl IntoPy<PyObject> for $rust_ty {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
self.to_object(py)
|
||||
}
|
||||
}
|
||||
impl<'source> FromPyObject<'source> for $rust_ty {
|
||||
fn extract(ob: &'source PyAny) -> PyResult<$rust_ty> {
|
||||
let py = ob.py();
|
||||
unsafe {
|
||||
let num = ffi::PyNumber_Index(ob.as_ptr());
|
||||
if num.is_null() {
|
||||
return Err(PyErr::fetch(py));
|
||||
}
|
||||
let n_bits = ffi::_PyLong_NumBits(num);
|
||||
let n_bytes = if n_bits < 0 {
|
||||
return Err(PyErr::fetch(py));
|
||||
} else if n_bits == 0 {
|
||||
0
|
||||
} else {
|
||||
(n_bits as usize - 1 + $is_signed) / 8 + 1
|
||||
};
|
||||
let num: Py<PyLong> = Py::from_owned_ptr(py, num);
|
||||
if n_bytes <= 128 {
|
||||
let mut buffer = [0; 128];
|
||||
extract(num.as_ref(py), &mut buffer[..n_bytes], $is_signed)?;
|
||||
Ok($from_bytes(&buffer[..n_bytes]))
|
||||
} else {
|
||||
let mut buffer = vec![0; n_bytes];
|
||||
extract(num.as_ref(py), &mut buffer, $is_signed)?;
|
||||
Ok($from_bytes(&buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
bigint_conversion!(BigUint, 0, BigUint::to_bytes_le, BigUint::from_bytes_le);
|
||||
bigint_conversion!(
|
||||
BigInt,
|
||||
1,
|
||||
BigInt::to_signed_bytes_le,
|
||||
BigInt::from_signed_bytes_le
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::{PyDict, PyModule};
|
||||
use indoc::indoc;
|
||||
|
||||
fn python_fib(py: Python) -> &PyModule {
|
||||
let fib_code = indoc!(
|
||||
r#"
|
||||
def fib(n):
|
||||
f0, f1 = 0, 1
|
||||
for _ in range(n):
|
||||
f0, f1 = f1, f0 + f1
|
||||
return f0
|
||||
|
||||
def fib_neg(n):
|
||||
return -fib(n)
|
||||
"#
|
||||
);
|
||||
PyModule::from_code(py, fib_code, "fib.py", "fib").unwrap()
|
||||
}
|
||||
|
||||
fn rust_fib<T>(n: usize) -> T
|
||||
where
|
||||
T: From<u16>,
|
||||
for<'a> &'a T: std::ops::Add<Output = T>,
|
||||
{
|
||||
let mut f0: T = T::from(0);
|
||||
let mut f1: T = T::from(1);
|
||||
for _ in 0..n {
|
||||
let f2 = &f0 + &f1;
|
||||
f0 = std::mem::replace(&mut f1, f2);
|
||||
}
|
||||
f0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_biguint() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let rs_result: BigUint = rust_fib(400);
|
||||
let fib = python_fib(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("rs_result", &rs_result).unwrap();
|
||||
locals.set_item("fib", fib).unwrap();
|
||||
// Checks if Rust BigUint -> Python Long conversion is correct
|
||||
py.run("assert fib.fib(400) == rs_result", None, Some(locals))
|
||||
.unwrap();
|
||||
// Checks if Python Long -> Rust BigUint conversion is correct if N is small
|
||||
let py_result: BigUint =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
// Checks if Python Long -> Rust BigUint conversion is correct if N is large
|
||||
let rs_result: BigUint = rust_fib(2000);
|
||||
let py_result: BigUint =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_bigint() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let rs_result = rust_fib::<BigInt>(400) * -1;
|
||||
let fib = python_fib(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("rs_result", &rs_result).unwrap();
|
||||
locals.set_item("fib", fib).unwrap();
|
||||
// Checks if Rust BigInt -> Python Long conversion is correct
|
||||
py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals))
|
||||
.unwrap();
|
||||
// Checks if Python Long -> Rust BigInt conversion is correct if N is small
|
||||
let py_result: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
// Checks if Python Long -> Rust BigInt conversion is correct if N is large
|
||||
let rs_result = rust_fib::<BigInt>(2000) * -1;
|
||||
let py_result: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
}
|
||||
|
||||
fn python_index_class(py: Python) -> &PyModule {
|
||||
let index_code = indoc!(
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
def __index__(self):
|
||||
return self.x
|
||||
"#
|
||||
);
|
||||
PyModule::from_code(py, index_code, "index.py", "index").unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_index_class() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let index = python_index_class(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("index", index).unwrap();
|
||||
let ob = py.eval("index.C(10)", None, Some(locals)).unwrap();
|
||||
let _: BigInt = FromPyObject::extract(ob).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_zero() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let fib = python_fib(py);
|
||||
let zero: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap();
|
||||
assert_eq!(zero, BigInt::from(0));
|
||||
}
|
||||
|
||||
/// `OverflowError` on converting Python int to BigInt, see issue #629
|
||||
#[test]
|
||||
fn check_overflow() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
macro_rules! test {
|
||||
($T:ty, $value:expr, $py:expr) => {
|
||||
let value = $value;
|
||||
println!("{}: {}", stringify!($T), value);
|
||||
let python_value = value.clone().to_object(py);
|
||||
let roundtrip_value = python_value.extract::<$T>(py).unwrap();
|
||||
assert_eq!(value, roundtrip_value);
|
||||
};
|
||||
}
|
||||
for i in 0..=256usize {
|
||||
// test a lot of values to help catch other bugs too
|
||||
test!(BigInt, BigInt::from(i), py);
|
||||
test!(BigUint, BigUint::from(i), py);
|
||||
test!(BigInt, -BigInt::from(i), py);
|
||||
test!(BigInt, BigInt::from(1) << i, py);
|
||||
test!(BigUint, BigUint::from(1u32) << i, py);
|
||||
test!(BigInt, -BigInt::from(1) << i, py);
|
||||
test!(BigInt, (BigInt::from(1) << i) + 1u32, py);
|
||||
test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py);
|
||||
test!(BigInt, (-BigInt::from(1) << i) + 1u32, py);
|
||||
test!(BigInt, (BigInt::from(1) << i) - 1u32, py);
|
||||
test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py);
|
||||
test!(BigInt, (-BigInt::from(1) << i) - 1u32, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Python;
|
||||
|
|
Loading…
Reference in New Issue