diff --git a/Cargo.toml b/Cargo.toml index 132119c9..0b8df253 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ proptest = { version = "0.10.1", default-features = false, features = ["std"] } # features needed to run the PyO3 test suite pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] } serde_json = "1.0.61" +# needed for num-complex doctest +nalgebra = "0.27.1" [build-dependencies] pyo3-build-config = { path = "pyo3-build-config", version = "=0.14.0-alpha.0" } diff --git a/guide/src/features.md b/guide/src/features.md index 9e21a40e..bb7c3a42 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -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 objects via [serde](https://serde.rs/). diff --git a/src/lib.rs b/src/lib.rs index 48f47c95..21b5ea4a 100644 --- a/src/lib.rs +++ b/src/lib.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`](crate::num_bigint): 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. // @@ -300,6 +300,8 @@ pub mod types; pub mod num_bigint; +pub mod num_complex; + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[cfg(feature = "serde")] pub mod serde; diff --git a/src/num_bigint.rs b/src/num_bigint.rs index d3be678c..be985848 100644 --- a/src/num_bigint.rs +++ b/src/num_bigint.rs @@ -1,3 +1,7 @@ +// 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, @@ -5,8 +9,7 @@ )] //! Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types. //! -//! This is useful for converting Python integers, which have arbitrary precision, -//! when they may not fit in Rust's built-in integer types. +//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types. //! //! # Setup //! @@ -15,7 +18,7 @@ //! ```toml //! [dependencies] //! num-bigint = "0.4" -//! pyo3 = { version = "0.14.0", features = ["num-bigint"] } +//! pyo3 = { version = "0.14", features = ["num-bigint"] } //! ``` //! //! Note that you must use compatible versions of num-bigint and PyO3. @@ -23,18 +26,20 @@ //! //! ## Examples //! -//! [`BigInt`] and [`BigUint`] can be used represent arbitrary precision integers: +//! 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)?)?; @@ -45,14 +50,13 @@ //! 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, diff --git a/src/num_complex.rs b/src/num_complex.rs new file mode 100644 index 00000000..801e7aca --- /dev/null +++ b/src/num_complex.rs @@ -0,0 +1,192 @@ +#![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] +//! num-complex = "0.4" +//! pyo3 = { version = "0.14", features = ["num-complex"] } +//! ``` +//! +//! Note that you must use compatible versions of num-complex and PyO3. +//! The required num-complex version may vary with older versions 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. +//! +//! ```rust +//! use nalgebra::base::{dimension::Const, storage::Storage, Matrix}; +//! use num_complex::Complex; +//! use pyo3::prelude::*; +//! use pyo3::wrap_pyfunction; +//! +//! type T = Complex; +//! +//! #[pyfunction] +//! fn get_eigenvalues(m11: T, m12: T, m21: T, m22: T) -> Vec { +//! let mat = Matrix::, 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::>()?; +//! # 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>(py: Python, complex: Complex) -> &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::::into_py(self.to_owned(), py) + } + } + impl crate::IntoPy 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> { + 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> { + 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() { + 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); + } + #[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::>(py).unwrap(), val); + } + #[test] + fn from_complex_err() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = vec![1].to_object(py); + assert!(obj.extract::>(py).is_err()); + } +} diff --git a/src/types/complex.rs b/src/types/complex.rs index 70b782e4..cb8f7bbb 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -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); @@ -25,197 +27,183 @@ impl PyComplex { } } /// Returns the real part of the complex number. + #[must_use = "method returns a new number and does not mutate the original value"] 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. + #[must_use = "method returns a new number and does not mutate the original value"] 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>(py: Python, complex: Complex) -> &PyComplex { + /// Returns `|self|`. + #[must_use = "method returns a new number and does not mutate the original value"] + 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`. + #[must_use = "method returns a new PyComplex and does not mutate the original value"] + 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::::into_py(self.to_owned(), py) - } - } - impl crate::IntoPy 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> { - 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> { - 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::>(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::>(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() { + 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); + } + + #[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); + } + + #[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); + } + + #[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); + } + + #[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); + } + + #[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); + } + + #[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); + } } } @@ -235,84 +223,4 @@ mod test { 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); - } }