diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index e3fd001c599c80..c07ab0eefd5f8b 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -34,31 +34,31 @@ pointers. This is consistent throughout the API. } Py_complex; -.. c:function:: Py_complex _Py_c_sum(Py_complex left, Py_complex right) +.. c:function:: Py_complex Py_complex_sum(Py_complex left, Py_complex right) Return the sum of two complex numbers, using the C :c:type:`Py_complex` representation. -.. c:function:: Py_complex _Py_c_diff(Py_complex left, Py_complex right) +.. c:function:: Py_complex Py_complex_diff(Py_complex left, Py_complex right) Return the difference between two complex numbers, using the C :c:type:`Py_complex` representation. -.. c:function:: Py_complex _Py_c_neg(Py_complex num) +.. c:function:: Py_complex Py_complex_neg(Py_complex num) Return the negation of the complex number *num*, using the C :c:type:`Py_complex` representation. -.. c:function:: Py_complex _Py_c_prod(Py_complex left, Py_complex right) +.. c:function:: Py_complex Py_complex_prod(Py_complex left, Py_complex right) Return the product of two complex numbers, using the C :c:type:`Py_complex` representation. -.. c:function:: Py_complex _Py_c_quot(Py_complex dividend, Py_complex divisor) +.. c:function:: Py_complex Py_complex_quot(Py_complex dividend, Py_complex divisor) Return the quotient of two complex numbers, using the C :c:type:`Py_complex` representation. @@ -67,7 +67,7 @@ pointers. This is consistent throughout the API. :c:data:`errno` to :c:macro:`!EDOM`. -.. c:function:: Py_complex _Py_c_pow(Py_complex num, Py_complex exp) +.. c:function:: Py_complex Py_complex_pow(Py_complex num, Py_complex exp) Return the exponentiation of *num* by *exp*, using the C :c:type:`Py_complex` representation. @@ -76,6 +76,28 @@ pointers. This is consistent throughout the API. this method returns zero and sets :c:data:`errno` to :c:macro:`!EDOM`. +.. c:function:: double Py_complex_abs(Py_complex num) + + Return the absolute value (or modulus or magnitude) of *num*, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: 3.5 + + +In Python 3.13, these functions have been made public. Previously, they were +known as these private functions: + +* ``Py_complex_sum()``: ``_Py_c_sum()`` +* ``Py_complex_diff()``: ``_Py_c_diff()`` +* ``Py_complex_neg()``: ``_Py_c_neg()`` +* ``Py_complex_prod()``: ``_Py_c_prod()`` +* ``Py_complex_quot()``: ``_Py_c_quot()`` +* ``Py_complex_pow()``: ``_Py_c_pow()`` +* ``Py_complex_abs()``: ``_Py_c_abs()`` + +Old names are kept for backward compatibility. + + Complex Numbers as Python Objects ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 136fe901ce39fb..0fe77a63c2de4d 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1181,6 +1181,20 @@ New Features :exc:`KeyError` if the key missing. (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.) +* Add public functions operating on ``Py_complex`` numbers: + + * :c:func:`Py_complex_sum`: previously known as ``_Py_c_sum()`` + * :c:func:`Py_complex_diff`: previously known as ``_Py_c_diff()`` + * :c:func:`Py_complex_neg`: previously known as ``_Py_c_neg()`` + * :c:func:`Py_complex_prod`: previously known as ``_Py_c_prod()`` + * :c:func:`Py_complex_quot`: previously known as ``_Py_c_quot()`` + * :c:func:`Py_complex_pow`: previously known as ``_Py_c_pow()`` + * :c:func:`Py_complex_abs`: previously known as ``_Py_c_abs()`` + + The old names with the ``_Py_c`` prefix are kept for backward compatibility. + + (Contributed by Victor Stinner in :gh:`111481`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h index b524ec42c24371..533f2d0acfa4e9 100644 --- a/Include/cpython/complexobject.h +++ b/Include/cpython/complexobject.h @@ -7,6 +7,25 @@ typedef struct { double imag; } Py_complex; +// Operations on complex numbers. +PyAPI_FUNC(Py_complex) Py_complex_sum(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) Py_complex_diff(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) Py_complex_neg(Py_complex); +PyAPI_FUNC(Py_complex) Py_complex_prod(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) Py_complex_quot(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) Py_complex_pow(Py_complex, Py_complex); +PyAPI_FUNC(double) Py_complex_abs(Py_complex); + +// Keep old Python 3.12 names as aliases to new functions +#define _Py_c_sum Py_complex_sum +#define _Py_c_diff Py_complex_diff +#define _Py_c_neg Py_complex_neg +#define _Py_c_prod Py_complex_prod +#define _Py_c_quot Py_complex_quot +#define _Py_c_pow Py_complex_pow +#define _Py_c_abs Py_complex_abs + + /* Complex object interface */ /* diff --git a/Include/internal/pycore_complexobject.h b/Include/internal/pycore_complexobject.h index a6fee9d23f3a9f..54713536eedc46 100644 --- a/Include/internal/pycore_complexobject.h +++ b/Include/internal/pycore_complexobject.h @@ -10,16 +10,6 @@ extern "C" { #include "pycore_unicodeobject.h" // _PyUnicodeWriter -// Operations on complex numbers. -// Export functions for 'cmath' shared extension. -PyAPI_FUNC(Py_complex) _Py_c_sum(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_c_neg(Py_complex); -PyAPI_FUNC(Py_complex) _Py_c_prod(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_c_quot(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex); -PyAPI_FUNC(double) _Py_c_abs(Py_complex); - /* Format the object based on the format_spec, as defined in PEP 3101 (Advanced String Formatting). */ extern int _PyComplex_FormatAdvancedWriter( diff --git a/Misc/NEWS.d/next/C API/2023-11-15-05-45-59.gh-issue-111481.BhE88s.rst b/Misc/NEWS.d/next/C API/2023-11-15-05-45-59.gh-issue-111481.BhE88s.rst new file mode 100644 index 00000000000000..eb5bd79ba46543 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-15-05-45-59.gh-issue-111481.BhE88s.rst @@ -0,0 +1,11 @@ +Add public functions operating on ``Py_complex`` numbers: + +* :c:func:`Py_complex_sum`: previously known as ``_Py_c_sum()`` +* :c:func:`Py_complex_diff`: previously known as ``_Py_c_diff()`` +* :c:func:`Py_complex_neg`: previously known as ``_Py_c_neg()`` +* :c:func:`Py_complex_prod`: previously known as ``_Py_c_prod()`` +* :c:func:`Py_complex_quot`: previously known as ``_Py_c_quot()`` +* :c:func:`Py_complex_pow`: previously known as ``_Py_c_pow()`` +* :c:func:`Py_complex_abs`: previously known as ``_Py_c_abs()`` + +The old names with the ``_Py_c`` prefix are kept for backward compatibility. diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index 400f4054c613ee..8bdfe521dfee0d 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -86,6 +86,52 @@ complex_asccomplex(PyObject *Py_UNUSED(module), PyObject *obj) } +static PyObject * +test_py_complex(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Basic tests on Py_complex functions: + // + // - Py_complex_sum() + // - Py_complex_diff() + // - Py_complex_neg() + // - Py_complex_prod() + // - Py_complex_quot() + // - Py_complex_pow() + // - Py_complex_abs() + + Py_complex a = {1.0, 5.0}; + Py_complex b = {1.5, 0.5}; + Py_complex x = Py_complex_sum(a, b); + assert(x.real == 2.5 && x.imag == 5.5); + + x = Py_complex_diff(a, b); + assert(x.real == -0.5 && x.imag == 4.5); + + x = Py_complex_neg(a); + assert(x.real == -1.0 && x.imag == -5.0); + + a = (Py_complex){1.0, -2.0}; + b = (Py_complex){3.0, 4.0}; + x = Py_complex_prod(a, b); + assert(x.real == 11.0 && x.imag == -2.0); + + a = (Py_complex){6.9, -3.2}; + x = Py_complex_quot(a, a); + assert(x.real == 1.0 && x.imag == 0.0); + + a = (Py_complex){1.3, 2.7}; + Py_complex zero = {0.0, 0.0}; + x = Py_complex_pow(a, zero); + assert(x.real == 1.0 && x.imag == 0.0); + + x = (Py_complex){3.0, 4.0}; + double mod = Py_complex_abs(x); + assert(mod == hypot(3.0, 4.0)); + + Py_RETURN_NONE; +} + + static PyMethodDef test_methods[] = { {"complex_check", complex_check, METH_O}, {"complex_checkexact", complex_checkexact, METH_O}, @@ -94,6 +140,7 @@ static PyMethodDef test_methods[] = { {"complex_realasdouble", complex_realasdouble, METH_O}, {"complex_imagasdouble", complex_imagasdouble, METH_O}, {"complex_asccomplex", complex_asccomplex, METH_O}, + {"test_py_complex", test_py_complex, METH_NOARGS}, {NULL}, }; diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index 57bc55632be485..3971ea00bd8754 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -7,7 +7,6 @@ #endif #include "Python.h" -#include "pycore_complexobject.h" // _Py_c_neg() #include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR /* we need DBL_MAX, DBL_MIN, DBL_EPSILON, DBL_MANT_DIG and FLT_RADIX from float.h. We assume that FLT_RADIX is either 2 or 16. */ @@ -374,7 +373,7 @@ cmath_atanh_impl(PyObject *module, Py_complex z) /* Reduce to case where z.real >= 0., using atanh(z) = -atanh(-z). */ if (z.real < 0.) { - return _Py_c_neg(cmath_atanh_impl(module, _Py_c_neg(z))); + return Py_complex_neg(cmath_atanh_impl(module, Py_complex_neg(z))); } ay = fabs(z.imag); @@ -927,7 +926,7 @@ cmath_log_impl(PyObject *module, Py_complex x, PyObject *y_obj) return NULL; } y = c_log(y); - x = _Py_c_quot(x, y); + x = Py_complex_quot(x, y); } if (errno != 0) return math_error(); @@ -992,7 +991,7 @@ cmath_polar_impl(PyObject *module, Py_complex z) errno = 0; phi = c_atan2(z); /* should not cause any exception */ - r = _Py_c_abs(z); /* sets errno to ERANGE on overflow */ + r = Py_complex_abs(z); /* sets errno to ERANGE on overflow */ if (errno != 0) return math_error(); else @@ -1176,10 +1175,10 @@ cmath_isclose_impl(PyObject *module, Py_complex a, Py_complex b, this is essentially the "weak" test from the Boost library */ - diff = _Py_c_abs(_Py_c_diff(a, b)); + diff = Py_complex_abs(Py_complex_diff(a, b)); - return (((diff <= rel_tol * _Py_c_abs(b)) || - (diff <= rel_tol * _Py_c_abs(a))) || + return (((diff <= rel_tol * Py_complex_abs(b)) || + (diff <= rel_tol * Py_complex_abs(a))) || (diff <= abs_tol)); } diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 0e96f54584677c..fb2bb82e10751d 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -26,7 +26,7 @@ class complex "PyComplexObject *" "&PyComplex_Type" static Py_complex c_1 = {1., 0.}; Py_complex -_Py_c_sum(Py_complex a, Py_complex b) +Py_complex_sum(Py_complex a, Py_complex b) { Py_complex r; r.real = a.real + b.real; @@ -35,7 +35,7 @@ _Py_c_sum(Py_complex a, Py_complex b) } Py_complex -_Py_c_diff(Py_complex a, Py_complex b) +Py_complex_diff(Py_complex a, Py_complex b) { Py_complex r; r.real = a.real - b.real; @@ -44,7 +44,7 @@ _Py_c_diff(Py_complex a, Py_complex b) } Py_complex -_Py_c_neg(Py_complex a) +Py_complex_neg(Py_complex a) { Py_complex r; r.real = -a.real; @@ -53,7 +53,7 @@ _Py_c_neg(Py_complex a) } Py_complex -_Py_c_prod(Py_complex a, Py_complex b) +Py_complex_prod(Py_complex a, Py_complex b) { Py_complex r; r.real = a.real*b.real - a.imag*b.imag; @@ -66,7 +66,7 @@ _Py_c_prod(Py_complex a, Py_complex b) #pragma optimize("", off) #endif Py_complex -_Py_c_quot(Py_complex a, Py_complex b) +Py_complex_quot(Py_complex a, Py_complex b) { /****************************************************************** This was the original algorithm. It's grossly prone to spurious @@ -127,7 +127,7 @@ _Py_c_quot(Py_complex a, Py_complex b) #endif Py_complex -_Py_c_pow(Py_complex a, Py_complex b) +Py_complex_pow(Py_complex a, Py_complex b) { Py_complex r; double vabs,len,at,phase; @@ -139,7 +139,7 @@ _Py_c_pow(Py_complex a, Py_complex b) if (b.imag != 0. || b.real < 0.) errno = EDOM; r.real = 0.; - r.imag = 0.; + r.imag = -1.; } else { vabs = hypot(a.real,a.imag); @@ -165,9 +165,9 @@ c_powu(Py_complex x, long n) p = x; while (mask > 0 && n >= mask) { if (n & mask) - r = _Py_c_prod(r,p); + r = Py_complex_prod(r,p); mask <<= 1; - p = _Py_c_prod(p,p); + p = Py_complex_prod(p,p); } return r; } @@ -178,12 +178,12 @@ c_powi(Py_complex x, long n) if (n > 0) return c_powu(x,n); else - return _Py_c_quot(c_1, c_powu(x,-n)); + return Py_complex_quot(c_1, c_powu(x,-n)); } double -_Py_c_abs(Py_complex z) +Py_complex_abs(Py_complex z) { /* sets errno = ERANGE on overflow; otherwise errno = 0 */ double result; @@ -462,7 +462,7 @@ complex_add(PyObject *v, PyObject *w) Py_complex a, b; TO_COMPLEX(v, a); TO_COMPLEX(w, b); - result = _Py_c_sum(a, b); + result = Py_complex_sum(a, b); return PyComplex_FromCComplex(result); } @@ -473,7 +473,7 @@ complex_sub(PyObject *v, PyObject *w) Py_complex a, b; TO_COMPLEX(v, a); TO_COMPLEX(w, b); - result = _Py_c_diff(a, b); + result = Py_complex_diff(a, b); return PyComplex_FromCComplex(result); } @@ -484,7 +484,7 @@ complex_mul(PyObject *v, PyObject *w) Py_complex a, b; TO_COMPLEX(v, a); TO_COMPLEX(w, b); - result = _Py_c_prod(a, b); + result = Py_complex_prod(a, b); return PyComplex_FromCComplex(result); } @@ -496,7 +496,7 @@ complex_div(PyObject *v, PyObject *w) TO_COMPLEX(v, a); TO_COMPLEX(w, b); errno = 0; - quot = _Py_c_quot(a, b); + quot = Py_complex_quot(a, b); if (errno == EDOM) { PyErr_SetString(PyExc_ZeroDivisionError, "complex division by zero"); return NULL; @@ -523,7 +523,7 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) p = c_powi(a, (long)b.real); } else { - p = _Py_c_pow(a, b); + p = Py_complex_pow(a, b); } _Py_ADJUST_ERANGE2(p.real, p.imag); @@ -564,7 +564,7 @@ complex_abs(PyComplexObject *v) { double result; - result = _Py_c_abs(v->cval); + result = Py_complex_abs(v->cval); if (errno == ERANGE) { PyErr_SetString(PyExc_OverflowError,