From 3980363b80941ec839f0fee9d4577a97dcc22966 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 23 Apr 2024 18:00:55 +0300 Subject: [PATCH 01/24] gh-69639: add mixed-mode rules for complex arithmetic (C-like) "Generally, mixed-mode arithmetic combining real and complex variables should be performed directly, not by first coercing the real to complex, lest the sign of zero be rendered uninformative; the same goes for combinations of pure imaginary quantities with complex variables." (c) Kahan, W: Branch cuts for complex elementary functions. This patch implements mixed-mode arithmetic rules, combining real and complex variables as specified by C standards since C99 (in particular, there is no special version for the true division with real lhs operand). Most C compilers implementing C99+ Annex G have only these special rules (without support for imaginary type, which is going to be deprecated in C2y). --- Doc/library/cmath.rst | 12 +- Doc/library/stdtypes.rst | 16 +- Include/internal/pycore_floatobject.h | 2 + Lib/test/test_complex.py | 15 ++ ...4-08-03-14-02-27.gh-issue-69639.mW3iKq.rst | 2 + Objects/complexobject.c | 167 ++++++++++++++---- Objects/floatobject.c | 6 +- Python/bltinmodule.c | 2 - 8 files changed, 167 insertions(+), 55 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst diff --git a/Doc/library/cmath.rst b/Doc/library/cmath.rst index 381a8332f4b187..98519a11c95eb6 100644 --- a/Doc/library/cmath.rst +++ b/Doc/library/cmath.rst @@ -24,17 +24,17 @@ the function is then applied to the result of the conversion. imaginary axis we look at the sign of the real part. For example, the :func:`cmath.sqrt` function has a branch cut along the - negative real axis. An argument of ``complex(-2.0, -0.0)`` is treated as + negative real axis. An argument of ``-2-0j`` is treated as though it lies *below* the branch cut, and so gives a result on the negative imaginary axis:: - >>> cmath.sqrt(complex(-2.0, -0.0)) + >>> cmath.sqrt(-2-0j) -1.4142135623730951j - But an argument of ``complex(-2.0, 0.0)`` is treated as though it lies above + But an argument of ``-2+0j`` is treated as though it lies above the branch cut:: - >>> cmath.sqrt(complex(-2.0, 0.0)) + >>> cmath.sqrt(-2+0j) 1.4142135623730951j @@ -63,9 +63,9 @@ rectangular coordinates to polar coordinates and back. along the negative real axis. The sign of the result is the same as the sign of ``x.imag``, even when ``x.imag`` is zero:: - >>> phase(complex(-1.0, 0.0)) + >>> phase(-1+0j) 3.141592653589793 - >>> phase(complex(-1.0, -0.0)) + >>> phase(-1-0j) -3.141592653589793 diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 833c71c4ce4b9a..db315907623dc3 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -243,6 +243,9 @@ numeric literal yields an imaginary number (a complex number with a zero real part) which you can add to an integer or float to get a complex number with real and imaginary parts. +The constructors :func:`int`, :func:`float`, and +:func:`complex` can be used to produce numbers of a specific type. + .. index:: single: arithmetic pair: built-in function; int @@ -262,12 +265,15 @@ and imaginary parts. Python fully supports mixed arithmetic: when a binary arithmetic operator has operands of different numeric types, the operand with the "narrower" type is -widened to that of the other, where integer is narrower than floating point, -which is narrower than complex. A comparison between numbers of different types -behaves as though the exact values of those numbers were being compared. [2]_ +widened to that of the other, where integer is narrower than floating point. +Arithmetic with complex and real operands is defined by the usual mathematical +formula, for example:: -The constructors :func:`int`, :func:`float`, and -:func:`complex` can be used to produce numbers of a specific type. + x + complex(u, v) = complex(x + u, v) + x * complex(u, v) = complex(x * u, x * v) + +A comparison between numbers of different types behaves as though the exact +values of those numbers were being compared. [2]_ All numeric types (except complex) support the following operations (for priorities of the operations, see :ref:`operator-summary`): diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index be1c6cc97720d2..fd52fe006392b2 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -54,6 +54,8 @@ extern PyObject* _Py_string_to_number_with_underscores( extern double _Py_parse_inf_or_nan(const char *p, char **endptr); +extern int _Py_convert_to_double(PyObject **v, double *dbl); + #ifdef __cplusplus } diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index ecc97315e50d31..aac967867ae923 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -127,6 +127,9 @@ def test_truediv(self): self.assertTrue(isnan(z.real)) self.assertTrue(isnan(z.imag)) + self.assertComplexesAreIdentical(complex(INF, NAN) / 2, + complex(INF, NAN)) + self.assertComplexesAreIdentical(complex(INF, 1)/(0.0+1j), complex(NAN, -INF)) @@ -224,6 +227,10 @@ def check(n, deltas, is_equal, imag = 0.0): def test_add(self): self.assertEqual(1j + int(+1), complex(+1, 1)) self.assertEqual(1j + int(-1), complex(-1, 1)) + self.assertComplexesAreIdentical(complex(-0.0, -0.0) + (-0.0), + complex(-0.0, -0.0)) + self.assertComplexesAreIdentical((-0.0) + complex(-0.0, -0.0), + complex(-0.0, -0.0)) self.assertRaises(OverflowError, operator.add, 1j, 10**1000) self.assertRaises(TypeError, operator.add, 1j, None) self.assertRaises(TypeError, operator.add, None, 1j) @@ -231,6 +238,10 @@ def test_add(self): def test_sub(self): self.assertEqual(1j - int(+1), complex(-1, 1)) self.assertEqual(1j - int(-1), complex(1, 1)) + self.assertComplexesAreIdentical(complex(-0.0, -0.0) - 0.0, + complex(-0.0, -0.0)) + self.assertComplexesAreIdentical(-0.0 - complex(0.0, 0.0), + complex(-0.0, -0.0)) self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) self.assertRaises(TypeError, operator.sub, 1j, None) self.assertRaises(TypeError, operator.sub, None, 1j) @@ -238,6 +249,10 @@ def test_sub(self): def test_mul(self): self.assertEqual(1j * int(20), complex(0, 20)) self.assertEqual(1j * int(-1), complex(0, -1)) + self.assertComplexesAreIdentical(complex(INF, NAN) * 2, + complex(INF, NAN)) + self.assertComplexesAreIdentical(2 * complex(INF, NAN), + complex(INF, NAN)) self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) self.assertRaises(TypeError, operator.mul, 1j, None) self.assertRaises(TypeError, operator.mul, None, 1j) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst b/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst new file mode 100644 index 00000000000000..0581bcd8c13681 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst @@ -0,0 +1,2 @@ +Implement mixed-mode arithmetic rules combining real and complex variables +as specified by C standards since C99. Patch by Sergey B Kirpichev. diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 787235c63a6be1..64cc500ea782ea 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -8,6 +8,7 @@ #include "Python.h" #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_complexobject.h" // _PyComplex_FormatAdvancedWriter() +#include "pycore_floatobject.h" // _Py_convert_to_double() #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_object.h" // _PyObject_Init() #include "pycore_pymath.h" // _Py_ADJUST_ERANGE2() @@ -481,75 +482,163 @@ complex_hash(PyComplexObject *v) return (obj) static int -to_complex(PyObject **pobj, Py_complex *pc) +to_float(PyObject **pobj, double *dbl) { PyObject *obj = *pobj; - pc->real = pc->imag = 0.0; - if (PyLong_Check(obj)) { - pc->real = PyLong_AsDouble(obj); - if (pc->real == -1.0 && PyErr_Occurred()) { - *pobj = NULL; - return -1; - } - return 0; - } if (PyFloat_Check(obj)) { - pc->real = PyFloat_AsDouble(obj); - return 0; + *dbl = PyFloat_AS_DOUBLE(obj); + } + else if (_Py_convert_to_double(pobj, dbl) < 0) { + return -1; } - *pobj = Py_NewRef(Py_NotImplemented); - return -1; + return 0; +} + +static int +to_complex(PyObject **pobj, Py_complex *pc) +{ + pc->imag = 0.0; + return to_float(pobj, &(pc->real)); } +/* Complex arithmetic rules implement special mixed-mode case: combining + pure-real (float's or int's) value and complex value performed directly, not + by first coercing the real value to complex. + + Lets consider the addition as an example, assuming that int's are implicitly + converted to float's. We have following rules (up to variants with changed + order of operands): + + complex(x, y) + complex(u, v) = complex(x + u, y + v) + float(x) + complex(u, v) = complex(x + u, v) + + Similar rules are implemented for subtraction and multiplication. See C11's + Annex G, sections G.5.1 and G.5.2. The true division is special: + + complex(x, y) / float(u) = complex(x/u, y/u) + float(x) / complex(u, v) = complex(x, 0) / complex(u, v) + */ static PyObject * complex_add(PyObject *v, PyObject *w) { - Py_complex result; - Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); - result = _Py_c_sum(a, b); - return PyComplex_FromCComplex(result); + if (PyComplex_Check(w)) { + PyObject *tmp = v; + v = w; + w = tmp; + } + + Py_complex a = ((PyComplexObject *)(v))->cval; + double b; + + if (PyComplex_Check(w)) { + Py_complex b = ((PyComplexObject *)(w))->cval; + a = _Py_c_sum(a, b); + } + else if (to_float(&w, &b) < 0) { + return w; + } + else { + a.real += b; + } + + return PyComplex_FromCComplex(a); } static PyObject * complex_sub(PyObject *v, PyObject *w) { - Py_complex result; - Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); - result = _Py_c_diff(a, b); - return PyComplex_FromCComplex(result); + Py_complex a; + + if (PyComplex_Check(w)) { + Py_complex b = ((PyComplexObject *)(w))->cval; + + if (PyComplex_Check(v)) { + a = ((PyComplexObject *)(v))->cval; + errno = 0; + a = _Py_c_diff(a, b); + } + else if (to_float(&v, &a.real) < 0) { + return v; + } + else { + a = (Py_complex) {a.real, -b.imag}; + a.real -= b.real; + } + } + else { + a = ((PyComplexObject *)(v))->cval; + double b; + + if (to_float(&w, &b) < 0) { + return w; + } + a.real -= b; + } + + return PyComplex_FromCComplex(a); } static PyObject * complex_mul(PyObject *v, PyObject *w) { - Py_complex result; - Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); - result = _Py_c_prod(a, b); - return PyComplex_FromCComplex(result); + if (PyComplex_Check(w)) { + PyObject *tmp = v; + v = w; + w = tmp; + } + + Py_complex a = ((PyComplexObject *)(v))->cval; + double b; + + if (PyComplex_Check(w)) { + Py_complex b = ((PyComplexObject *)(w))->cval; + a = _Py_c_prod(a, b); + } + else if (to_float(&w, &b) < 0) { + return w; + } + else { + a.real *= b; + a.imag *= b; + } + + return PyComplex_FromCComplex(a); } static PyObject * complex_div(PyObject *v, PyObject *w) { - Py_complex quot; - Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); - errno = 0; - quot = _Py_c_quot(a, b); + Py_complex a; + + if (PyComplex_Check(w)) { + Py_complex b = ((PyComplexObject *)(w))->cval; + TO_COMPLEX(v, a); + errno = 0; + a = _Py_c_quot(a, b); + } + else { + double b; + + if (to_float(&w, &b) < 0) { + return w; + } + if (b) { + a = ((PyComplexObject *)(v))->cval; + a.real /= b; + a.imag /= b; + } + else { + errno = EDOM; + } + } + if (errno == EDOM) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - return PyComplex_FromCComplex(quot); + return PyComplex_FromCComplex(a); } static PyObject * diff --git a/Objects/floatobject.c b/Objects/floatobject.c index a48a210adee3b9..74903a0049f9e4 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -309,13 +309,13 @@ PyFloat_AsDouble(PyObject *op) #define CONVERT_TO_DOUBLE(obj, dbl) \ if (PyFloat_Check(obj)) \ dbl = PyFloat_AS_DOUBLE(obj); \ - else if (convert_to_double(&(obj), &(dbl)) < 0) \ + else if (_Py_convert_to_double(&(obj), &(dbl)) < 0) \ return obj; /* Methods */ -static int -convert_to_double(PyObject **v, double *dbl) +int +_Py_convert_to_double(PyObject **v, double *dbl) { PyObject *obj = *v; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index f87f942cc76258..b2a18855d04d7f 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2742,7 +2742,6 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) double value = PyLong_AsDouble(item); if (value != -1.0 || !PyErr_Occurred()) { re_sum = cs_add(re_sum, value); - im_sum.hi += 0.0; Py_DECREF(item); continue; } @@ -2755,7 +2754,6 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) if (PyFloat_Check(item)) { double value = PyFloat_AS_DOUBLE(item); re_sum = cs_add(re_sum, value); - im_sum.hi += 0.0; _Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc); continue; } From 46521c30be0207ff40ba380001bcd7dc871ab0f0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Oct 2024 05:27:51 +0300 Subject: [PATCH 02/24] address review: * more tests for multiplication * rename to _Py_convert_int_to_double * rename to real_to_float/complex * slightly optimize code --- Include/internal/pycore_floatobject.h | 2 +- Lib/test/test_complex.py | 10 ++++---- Objects/complexobject.c | 34 +++++++++++++-------------- Objects/floatobject.c | 10 ++++---- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index fd52fe006392b2..f44b081b06cea5 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -54,7 +54,7 @@ extern PyObject* _Py_string_to_number_with_underscores( extern double _Py_parse_inf_or_nan(const char *p, char **endptr); -extern int _Py_convert_to_double(PyObject **v, double *dbl); +extern int _Py_convert_int_to_double(PyObject **v, double *dbl); #ifdef __cplusplus diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index aac967867ae923..a6dedfd00d7a54 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -249,10 +249,12 @@ def test_sub(self): def test_mul(self): self.assertEqual(1j * int(20), complex(0, 20)) self.assertEqual(1j * int(-1), complex(0, -1)) - self.assertComplexesAreIdentical(complex(INF, NAN) * 2, - complex(INF, NAN)) - self.assertComplexesAreIdentical(2 * complex(INF, NAN), - complex(INF, NAN)) + for c, r in [(2, complex(INF, 2)), (INF, complex(INF, INF)), + (0, complex(NAN, 0)), (-0.0, complex(NAN, -0.0)), + (NAN, complex(NAN, NAN))]: + with self.subTest(c=c, r=r): + self.assertComplexesAreIdentical(complex(INF, 1) * c, r) + self.assertComplexesAreIdentical(c * complex(INF, 1), r) self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) self.assertRaises(TypeError, operator.mul, 1j, None) self.assertRaises(TypeError, operator.mul, None, 1j) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 64cc500ea782ea..edf334ab5f1aeb 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -8,7 +8,7 @@ #include "Python.h" #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_complexobject.h" // _PyComplex_FormatAdvancedWriter() -#include "pycore_floatobject.h" // _Py_convert_to_double() +#include "pycore_floatobject.h" // _Py_convert_int_to_double() #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_object.h" // _PyObject_Init() #include "pycore_pymath.h" // _Py_ADJUST_ERANGE2() @@ -475,31 +475,31 @@ complex_hash(PyComplexObject *v) } /* This macro may return! */ -#define TO_COMPLEX(obj, c) \ - if (PyComplex_Check(obj)) \ - c = ((PyComplexObject *)(obj))->cval; \ - else if (to_complex(&(obj), &(c)) < 0) \ +#define TO_COMPLEX(obj, c) \ + if (PyComplex_Check(obj)) \ + c = ((PyComplexObject *)(obj))->cval; \ + else if (real_to_complex(&(obj), &(c)) < 0) \ return (obj) static int -to_float(PyObject **pobj, double *dbl) +real_to_float(PyObject **pobj, double *dbl) { PyObject *obj = *pobj; if (PyFloat_Check(obj)) { *dbl = PyFloat_AS_DOUBLE(obj); } - else if (_Py_convert_to_double(pobj, dbl) < 0) { + else if (_Py_convert_int_to_double(pobj, dbl) < 0) { return -1; } return 0; } static int -to_complex(PyObject **pobj, Py_complex *pc) +real_to_complex(PyObject **pobj, Py_complex *pc) { pc->imag = 0.0; - return to_float(pobj, &(pc->real)); + return real_to_float(pobj, &(pc->real)); } /* Complex arithmetic rules implement special mixed-mode case: combining @@ -523,7 +523,7 @@ to_complex(PyObject **pobj, Py_complex *pc) static PyObject * complex_add(PyObject *v, PyObject *w) { - if (PyComplex_Check(w)) { + if (!PyComplex_Check(v)) { PyObject *tmp = v; v = w; w = tmp; @@ -536,7 +536,7 @@ complex_add(PyObject *v, PyObject *w) Py_complex b = ((PyComplexObject *)(w))->cval; a = _Py_c_sum(a, b); } - else if (to_float(&w, &b) < 0) { + else if (real_to_float(&w, &b) < 0) { return w; } else { @@ -559,19 +559,19 @@ complex_sub(PyObject *v, PyObject *w) errno = 0; a = _Py_c_diff(a, b); } - else if (to_float(&v, &a.real) < 0) { + else if (real_to_float(&v, &a.real) < 0) { return v; } else { - a = (Py_complex) {a.real, -b.imag}; a.real -= b.real; + a.imag = -b.imag; } } else { a = ((PyComplexObject *)(v))->cval; double b; - if (to_float(&w, &b) < 0) { + if (real_to_float(&w, &b) < 0) { return w; } a.real -= b; @@ -583,7 +583,7 @@ complex_sub(PyObject *v, PyObject *w) static PyObject * complex_mul(PyObject *v, PyObject *w) { - if (PyComplex_Check(w)) { + if (!PyComplex_Check(v)) { PyObject *tmp = v; v = w; w = tmp; @@ -596,7 +596,7 @@ complex_mul(PyObject *v, PyObject *w) Py_complex b = ((PyComplexObject *)(w))->cval; a = _Py_c_prod(a, b); } - else if (to_float(&w, &b) < 0) { + else if (real_to_float(&w, &b) < 0) { return w; } else { @@ -621,7 +621,7 @@ complex_div(PyObject *v, PyObject *w) else { double b; - if (to_float(&w, &b) < 0) { + if (real_to_float(&w, &b) < 0) { return w; } if (b) { diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 74903a0049f9e4..6859bd542dcb10 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -306,16 +306,16 @@ PyFloat_AsDouble(PyObject *op) obj is not of float or int type, Py_NotImplemented is incref'ed, stored in obj, and returned from the function invoking this macro. */ -#define CONVERT_TO_DOUBLE(obj, dbl) \ - if (PyFloat_Check(obj)) \ - dbl = PyFloat_AS_DOUBLE(obj); \ - else if (_Py_convert_to_double(&(obj), &(dbl)) < 0) \ +#define CONVERT_TO_DOUBLE(obj, dbl) \ + if (PyFloat_Check(obj)) \ + dbl = PyFloat_AS_DOUBLE(obj); \ + else if (_Py_convert_int_to_double(&(obj), &(dbl)) < 0) \ return obj; /* Methods */ int -_Py_convert_to_double(PyObject **v, double *dbl) +_Py_convert_int_to_double(PyObject **v, double *dbl) { PyObject *obj = *v; From 1323f4d45ae4cb8808dcce33b86a06621ed1dac7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Oct 2024 08:14:37 +0300 Subject: [PATCH 03/24] Update Objects/complexobject.c --- Objects/complexobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index edf334ab5f1aeb..85a3fc890bfb55 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -556,7 +556,6 @@ complex_sub(PyObject *v, PyObject *w) if (PyComplex_Check(v)) { a = ((PyComplexObject *)(v))->cval; - errno = 0; a = _Py_c_diff(a, b); } else if (real_to_float(&v, &a.real) < 0) { From 5021a9b6ac1d262356e8684882f8f50ab63253ad Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Oct 2024 10:27:17 +0300 Subject: [PATCH 04/24] address review: -> real_to_double --- Objects/complexobject.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 85a3fc890bfb55..484684d76a9670 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -482,7 +482,7 @@ complex_hash(PyComplexObject *v) return (obj) static int -real_to_float(PyObject **pobj, double *dbl) +real_to_double(PyObject **pobj, double *dbl) { PyObject *obj = *pobj; @@ -499,7 +499,7 @@ static int real_to_complex(PyObject **pobj, Py_complex *pc) { pc->imag = 0.0; - return real_to_float(pobj, &(pc->real)); + return real_to_double(pobj, &(pc->real)); } /* Complex arithmetic rules implement special mixed-mode case: combining @@ -536,7 +536,7 @@ complex_add(PyObject *v, PyObject *w) Py_complex b = ((PyComplexObject *)(w))->cval; a = _Py_c_sum(a, b); } - else if (real_to_float(&w, &b) < 0) { + else if (real_to_double(&w, &b) < 0) { return w; } else { @@ -558,7 +558,7 @@ complex_sub(PyObject *v, PyObject *w) a = ((PyComplexObject *)(v))->cval; a = _Py_c_diff(a, b); } - else if (real_to_float(&v, &a.real) < 0) { + else if (real_to_double(&v, &a.real) < 0) { return v; } else { @@ -570,7 +570,7 @@ complex_sub(PyObject *v, PyObject *w) a = ((PyComplexObject *)(v))->cval; double b; - if (real_to_float(&w, &b) < 0) { + if (real_to_double(&w, &b) < 0) { return w; } a.real -= b; @@ -595,7 +595,7 @@ complex_mul(PyObject *v, PyObject *w) Py_complex b = ((PyComplexObject *)(w))->cval; a = _Py_c_prod(a, b); } - else if (real_to_float(&w, &b) < 0) { + else if (real_to_double(&w, &b) < 0) { return w; } else { @@ -620,7 +620,7 @@ complex_div(PyObject *v, PyObject *w) else { double b; - if (real_to_float(&w, &b) < 0) { + if (real_to_double(&w, &b) < 0) { return w; } if (b) { From c7308efca22497599bdee4382fc13946342b5e46 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Oct 2024 09:22:48 +0300 Subject: [PATCH 05/24] Add _Py_cd_* and _Py_dc_* functions --- Doc/c-api/complex.rst | 54 ++++++ Include/cpython/complexobject.h | 6 + Lib/test/test_complex.py | 40 +++++ Objects/complexobject.c | 281 ++++++++++++++++++-------------- 4 files changed, 257 insertions(+), 124 deletions(-) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 16bd79475dc1e6..4ac3d9d5fb2d69 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -44,12 +44,36 @@ pointers. This is consistent throughout the API. representation. +.. c:function:: Py_complex _Py_cd_sum(Py_complex left, double right) + + Return the sum of complex and real number, using the C :c:type:`Py_complex` + representation. + + .. versionadded:: 3.14 + + .. c:function:: Py_complex _Py_c_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_dc_diff(double left, Py_complex right) + + Return the difference of real and complex number, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: 3.14 + + +.. c:function:: Py_complex _Py_cd_diff(Py_complex left, double right) + + Return the difference of complex and real number, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: 3.14 + + .. c:function:: Py_complex _Py_c_neg(Py_complex num) Return the negation of the complex number *num*, using the C @@ -62,6 +86,14 @@ pointers. This is consistent throughout the API. representation. +.. c:function:: Py_complex _Py_cd_prod(Py_complex left, double right) + + Return the product of complex and real number, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: 3.14 + + .. c:function:: Py_complex _Py_c_quot(Py_complex dividend, Py_complex divisor) Return the quotient of two complex numbers, using the C :c:type:`Py_complex` @@ -71,6 +103,28 @@ pointers. This is consistent throughout the API. :c:data:`errno` to :c:macro:`!EDOM`. +.. c:function:: Py_complex _Py_dc_quot(double left, Py_complex right) + + Return the quotent of real and complex number, using the C + :c:type:`Py_complex` representation. + + If *divisor* is zero, this method returns zero and sets + :c:data:`errno` to :c:macro:`!EDOM`. + + .. versionadded:: 3.14 + + +.. c:function:: Py_complex _Py_cd_quot(Py_complex left, double right) + + Return the quotient of complex and real number, using the C + :c:type:`Py_complex` representation. + + If *divisor* is zero, this method returns zero and sets + :c:data:`errno` to :c:macro:`!EDOM`. + + .. versionadded:: 3.14 + + .. c:function:: Py_complex _Py_c_pow(Py_complex num, Py_complex exp) Return the exponentiation of *num* by *exp*, using the C :c:type:`Py_complex` diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h index fbdc6a91fe895c..6ee064527d3356 100644 --- a/Include/cpython/complexobject.h +++ b/Include/cpython/complexobject.h @@ -9,10 +9,16 @@ typedef struct { // Operations on complex numbers. PyAPI_FUNC(Py_complex) _Py_c_sum(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) _Py_cd_sum(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) _Py_dc_diff(double, Py_complex); +PyAPI_FUNC(Py_complex) _Py_cd_diff(Py_complex, double); 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_cd_prod(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_quot(Py_complex, Py_complex); +PyAPI_FUNC(Py_complex) _Py_dc_quot(double, Py_complex); +PyAPI_FUNC(Py_complex) _Py_cd_quot(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex); PyAPI_FUNC(double) _Py_c_abs(Py_complex); diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index a6dedfd00d7a54..5aeb91aa5718e1 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -126,6 +126,9 @@ def test_truediv(self): z = complex(0, 0) / complex(denom_real, denom_imag) self.assertTrue(isnan(z.real)) self.assertTrue(isnan(z.imag)) + z = float(0) / complex(denom_real, denom_imag) + self.assertTrue(isnan(z.real)) + self.assertTrue(isnan(z.imag)) self.assertComplexesAreIdentical(complex(INF, NAN) / 2, complex(INF, NAN)) @@ -157,6 +160,39 @@ def test_truediv(self): self.assertComplexesAreIdentical(complex(INF, 1)/complex(1, INF), complex(NAN, NAN)) + # mixed types + self.assertEqual((1+1j)/float(2), 0.5+0.5j) + self.assertEqual(float(1)/(1+2j), 0.2-0.4j) + self.assertEqual(float(1)/(-1+2j), -0.2-0.4j) + self.assertEqual(float(1)/(1-2j), 0.2+0.4j) + self.assertEqual(float(1)/(2+1j), 0.4-0.2j) + self.assertEqual(float(1)/(-2+1j), -0.4-0.2j) + self.assertEqual(float(1)/(2-1j), 0.4+0.2j) + + self.assertComplexesAreIdentical(INF/(1+0j), + complex(INF, NAN)) + self.assertComplexesAreIdentical(INF/(0.0+1j), + complex(NAN, -INF)) + self.assertComplexesAreIdentical(INF/complex(2**1000, 2**-1000), + complex(INF, NAN)) + self.assertComplexesAreIdentical(INF/complex(NAN, NAN), + complex(NAN, NAN)) + + self.assertComplexesAreIdentical(float(1)/complex(INF, INF), (0.0-0j)) + self.assertComplexesAreIdentical(float(1)/complex(INF, -INF), (0.0+0j)) + self.assertComplexesAreIdentical(float(1)/complex(-INF, INF), + complex(-0.0, -0.0)) + self.assertComplexesAreIdentical(float(1)/complex(-INF, -INF), + complex(-0.0, 0)) + self.assertComplexesAreIdentical(float(1)/complex(INF, NAN), + complex(0.0, -0.0)) + self.assertComplexesAreIdentical(float(1)/complex(-INF, NAN), + complex(-0.0, -0.0)) + self.assertComplexesAreIdentical(float(1)/complex(NAN, INF), + complex(0.0, -0.0)) + self.assertComplexesAreIdentical(float(INF)/complex(NAN, INF), + complex(NAN, NAN)) + def test_truediv_zero_division(self): for a, b in ZERO_DIVISION: with self.assertRaises(ZeroDivisionError): @@ -242,6 +278,10 @@ def test_sub(self): complex(-0.0, -0.0)) self.assertComplexesAreIdentical(-0.0 - complex(0.0, 0.0), complex(-0.0, -0.0)) + self.assertComplexesAreIdentical(complex(1, 2) - complex(2, 1), + complex(-1, 1)) + self.assertComplexesAreIdentical(complex(2, 1) - complex(1, 2), + complex(1, -1)) self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) self.assertRaises(TypeError, operator.sub, 1j, None) self.assertRaises(TypeError, operator.sub, None, 1j) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 484684d76a9670..e0f6204cef1af8 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -35,6 +35,14 @@ _Py_c_sum(Py_complex a, Py_complex b) return r; } +Py_complex +_Py_cd_sum(Py_complex a, double b) +{ + Py_complex r = a; + r.real += b; + return r; +} + Py_complex _Py_c_diff(Py_complex a, Py_complex b) { @@ -44,6 +52,22 @@ _Py_c_diff(Py_complex a, Py_complex b) return r; } +Py_complex +_Py_dc_diff(double a, Py_complex b) +{ + Py_complex r = {a, -b.imag}; + r.real -= b.real; + return r; +} + +Py_complex +_Py_cd_diff(Py_complex a, double b) +{ + Py_complex r = a; + r.real -= b; + return r; +} + Py_complex _Py_c_neg(Py_complex a) { @@ -62,6 +86,15 @@ _Py_c_prod(Py_complex a, Py_complex b) return r; } +Py_complex +_Py_cd_prod(Py_complex a, double b) +{ + Py_complex r = a; + r.real *= b; + r.imag *= b; + return r; +} + /* Avoid bad optimization on Windows ARM64 until the compiler is fixed */ #ifdef _M_ARM64 #pragma optimize("", off) @@ -144,10 +177,68 @@ _Py_c_quot(Py_complex a, Py_complex b) return r; } + +/* an equivalent of above function, when 1st argument is real */ +Py_complex +_Py_dc_quot(double a, Py_complex b) +{ + Py_complex r; + const double abs_breal = b.real < 0 ? -b.real : b.real; + const double abs_bimag = b.imag < 0 ? -b.imag : b.imag; + + if (abs_breal >= abs_bimag) { + if (abs_breal == 0.0) { + errno = EDOM; + r.real = r.imag = 0.0; + } + else { + const double ratio = b.imag / b.real; + const double denom = b.real + b.imag * ratio; + r.real = a / denom; + r.imag = (-a * ratio) / denom; + } + } + else if (abs_bimag >= abs_breal) { + const double ratio = b.real / b.imag; + const double denom = b.real * ratio + b.imag; + assert(b.imag != 0.0); + r.real = (a * ratio) / denom; + r.imag = (-a) / denom; + } + else { + r.real = r.imag = Py_NAN; + } + + if (isnan(r.real) && isnan(r.imag) && isfinite(a) + && (isinf(abs_breal) || isinf(abs_bimag))) + { + const double x = copysign(isinf(b.real) ? 1.0 : 0.0, b.real); + const double y = copysign(isinf(b.imag) ? 1.0 : 0.0, b.imag); + r.real = 0.0 * (a*x); + r.imag = 0.0 * (-a*y); + } + + return r; +} #ifdef _M_ARM64 #pragma optimize("", on) #endif +Py_complex +_Py_cd_quot(Py_complex a, double b) +{ + Py_complex r = a; + if (b) { + r.real /= b; + r.imag /= b; + } + else { + errno = EDOM; + r.real = r.imag = 0.0; + } + return r; +} + Py_complex _Py_c_pow(Py_complex a, Py_complex b) { @@ -513,132 +604,74 @@ real_to_complex(PyObject **pobj, Py_complex *pc) complex(x, y) + complex(u, v) = complex(x + u, y + v) float(x) + complex(u, v) = complex(x + u, v) - Similar rules are implemented for subtraction and multiplication. See C11's - Annex G, sections G.5.1 and G.5.2. The true division is special: - - complex(x, y) / float(u) = complex(x/u, y/u) - float(x) / complex(u, v) = complex(x, 0) / complex(u, v) + Similar rules are implemented for subtraction, multiplication and division. + See C11's Annex G, sections G.5.1 and G.5.2. */ -static PyObject * -complex_add(PyObject *v, PyObject *w) -{ - if (!PyComplex_Check(v)) { - PyObject *tmp = v; - v = w; - w = tmp; - } - - Py_complex a = ((PyComplexObject *)(v))->cval; - double b; - - if (PyComplex_Check(w)) { - Py_complex b = ((PyComplexObject *)(w))->cval; - a = _Py_c_sum(a, b); - } - else if (real_to_double(&w, &b) < 0) { - return w; - } - else { - a.real += b; - } - - return PyComplex_FromCComplex(a); -} - -static PyObject * -complex_sub(PyObject *v, PyObject *w) -{ - Py_complex a; - - if (PyComplex_Check(w)) { - Py_complex b = ((PyComplexObject *)(w))->cval; - - if (PyComplex_Check(v)) { - a = ((PyComplexObject *)(v))->cval; - a = _Py_c_diff(a, b); - } - else if (real_to_double(&v, &a.real) < 0) { - return v; - } - else { - a.real -= b.real; - a.imag = -b.imag; - } - } - else { - a = ((PyComplexObject *)(v))->cval; - double b; - - if (real_to_double(&w, &b) < 0) { - return w; - } - a.real -= b; - } - - return PyComplex_FromCComplex(a); -} - -static PyObject * -complex_mul(PyObject *v, PyObject *w) -{ - if (!PyComplex_Check(v)) { - PyObject *tmp = v; - v = w; - w = tmp; - } - - Py_complex a = ((PyComplexObject *)(v))->cval; - double b; - - if (PyComplex_Check(w)) { - Py_complex b = ((PyComplexObject *)(w))->cval; - a = _Py_c_prod(a, b); - } - else if (real_to_double(&w, &b) < 0) { - return w; - } - else { - a.real *= b; - a.imag *= b; - } - - return PyComplex_FromCComplex(a); -} - -static PyObject * -complex_div(PyObject *v, PyObject *w) -{ - Py_complex a; - - if (PyComplex_Check(w)) { - Py_complex b = ((PyComplexObject *)(w))->cval; - TO_COMPLEX(v, a); - errno = 0; - a = _Py_c_quot(a, b); - } - else { - double b; - - if (real_to_double(&w, &b) < 0) { - return w; - } - if (b) { - a = ((PyComplexObject *)(v))->cval; - a.real /= b; - a.imag /= b; - } - else { - errno = EDOM; - } - } - - if (errno == EDOM) { - PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); - return NULL; - } - return PyComplex_FromCComplex(a); -} +#define COMM_BINOP(name, func) \ + static PyObject * \ + complex_##name(PyObject *v, PyObject *w) \ + { \ + if (!PyComplex_Check(v)) { \ + PyObject *tmp = v; \ + v = w; \ + w = tmp; \ + } \ + Py_complex a = ((PyComplexObject *)(v))->cval; \ + double b; \ + if (PyComplex_Check(w)) { \ + Py_complex b = ((PyComplexObject *)(w))->cval; \ + a = _Py_c_##func(a, b); \ + } \ + else if (real_to_double(&w, &b) < 0) { \ + return w; \ + } \ + else { \ + a = _Py_cd_##func(a, b); \ + } \ + return PyComplex_FromCComplex(a); \ + } + +COMM_BINOP(add, sum) +COMM_BINOP(mul, prod) + +#define GEN_BINOP(name, func) \ + static PyObject * \ + complex_##name(PyObject *v, PyObject *w) \ + { \ + Py_complex a; \ + errno = 0; \ + if (PyComplex_Check(w)) { \ + Py_complex b = ((PyComplexObject *)(w))->cval; \ + if (PyComplex_Check(v)) { \ + a = ((PyComplexObject *)(v))->cval; \ + a = _Py_c_##func(a, b); \ + } \ + else if (real_to_double(&v, &a.real) < 0) { \ + return v; \ + } \ + else { \ + a = _Py_dc_##func(a.real, b); \ + } \ + } \ + else { \ + a = ((PyComplexObject *)(v))->cval; \ + double b; \ + if (real_to_double(&w, &b) < 0) { \ + return w; \ + } \ + a = _Py_cd_##func(a, b); \ + } \ + if (errno == EDOM) { \ + PyErr_SetString(PyExc_ZeroDivisionError, \ + "division by zero"); \ + return NULL; \ + } \ + return PyComplex_FromCComplex(a); \ + } + +GEN_BINOP(sub, diff) +GEN_BINOP(div, quot) static PyObject * complex_pow(PyObject *v, PyObject *w, PyObject *z) From ee1aa017be0532032208ac11660f63c50b9ed1e0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Oct 2024 14:37:59 +0300 Subject: [PATCH 06/24] + what's new --- Doc/whatsnew/3.14.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ffc001241ac5ec..b071dad8c16f3b 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -190,6 +190,10 @@ Other Language Changes They raise an error if the argument is a string. (Contributed by Serhiy Storchaka in :gh:`84978`.) +* Implement mixed-mode arithmetic rules combining real and complex variables as + specified by C standards since C99. (Contributed by Sergey B Kirpichev in + :gh:`69639`.) + New Modules =========== From da566dd16e9c51919ec7343a71356f8c49a15676 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Oct 2024 14:44:45 +0300 Subject: [PATCH 07/24] + trying to document new coersion rules in the reference --- Doc/reference/expressions.rst | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index ab72ad49d041e1..d8a6536834c1b8 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -27,11 +27,12 @@ Arithmetic conversions .. index:: pair: arithmetic; conversion -When a description of an arithmetic operator below uses the phrase "the numeric +When a description of an arithmetic operator below uses the phrase "the real numeric arguments are converted to a common type", this means that the operator implementation for built-in types works as follows: -* If either argument is a complex number, the other is converted to complex; +* If only one argument is a complex number, the other is converted to a + floating-point number; * otherwise, if either argument is a floating-point number, the other is converted to floating point; @@ -1321,13 +1322,17 @@ operators and one for additive operators: The ``*`` (multiplication) operator yields the product of its arguments. The arguments must either both be numbers, or one argument must be an integer and -the other must be a sequence. In the former case, the numbers are converted to a +the other must be a sequence. In the former case, the real numbers are converted to a common type and then multiplied together. In the latter case, sequence repetition is performed; a negative repetition factor yields an empty sequence. This operation can be customized using the special :meth:`~object.__mul__` and :meth:`~object.__rmul__` methods. +.. versionchanged:: 3.14 + If only one operand is a complex number, the other operand converted + to a floating-point number. + .. index:: single: matrix multiplication pair: operator; @ (at) @@ -1395,23 +1400,31 @@ floating-point number using the :func:`abs` function if appropriate. The ``+`` (addition) operator yields the sum of its arguments. The arguments must either both be numbers or both be sequences of the same type. In the -former case, the numbers are converted to a common type and then added together. +former case, the real numbers are converted to a common type and then added together. In the latter case, the sequences are concatenated. This operation can be customized using the special :meth:`~object.__add__` and :meth:`~object.__radd__` methods. +.. versionchanged:: 3.14 + If only one operand is a complex number, the other operand converted + to a floating-point number. + .. index:: single: subtraction single: operator; - (minus) single: - (minus); binary operator The ``-`` (subtraction) operator yields the difference of its arguments. The -numeric arguments are first converted to a common type. +real numeric arguments are first converted to a common type. This operation can be customized using the special :meth:`~object.__sub__` and :meth:`~object.__rsub__` methods. +.. versionchanged:: 3.14 + If only one operand is a complex number, the other operand converted + to a floating-point number. + .. _shifting: From 714b731e93157a502d654a2b3f0bfb43d999cf41 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 3 Oct 2024 05:23:11 +0300 Subject: [PATCH 08/24] address review: expressions.rst --- Doc/reference/expressions.rst | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index d8a6536834c1b8..41930b17c6dbe6 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -27,15 +27,14 @@ Arithmetic conversions .. index:: pair: arithmetic; conversion -When a description of an arithmetic operator below uses the phrase "the real numeric -arguments are converted to a common type", this means that the operator +When a description of an arithmetic operator below uses the phrase "the numeric +arguments are converted to a common real type", this means that the operator implementation for built-in types works as follows: -* If only one argument is a complex number, the other is converted to a - floating-point number; +* If both arguments are complex numbers, no conversion is necessary; -* otherwise, if either argument is a floating-point number, the other is - converted to floating point; +* otherwise, if either argument is a complex or a floating-point number, the + other is converted to a floating-point number; * otherwise, both must be integers and no conversion is necessary. @@ -1322,8 +1321,8 @@ operators and one for additive operators: The ``*`` (multiplication) operator yields the product of its arguments. The arguments must either both be numbers, or one argument must be an integer and -the other must be a sequence. In the former case, the real numbers are converted to a -common type and then multiplied together. In the latter case, sequence +the other must be a sequence. In the former case, the numbers are converted to a +common real type and then multiplied together. In the latter case, sequence repetition is performed; a negative repetition factor yields an empty sequence. This operation can be customized using the special :meth:`~object.__mul__` and @@ -1400,7 +1399,7 @@ floating-point number using the :func:`abs` function if appropriate. The ``+`` (addition) operator yields the sum of its arguments. The arguments must either both be numbers or both be sequences of the same type. In the -former case, the real numbers are converted to a common type and then added together. +former case, the numbers are converted to a common real type and then added together. In the latter case, the sequences are concatenated. This operation can be customized using the special :meth:`~object.__add__` and @@ -1416,7 +1415,7 @@ This operation can be customized using the special :meth:`~object.__add__` and single: - (minus); binary operator The ``-`` (subtraction) operator yields the difference of its arguments. The -real numeric arguments are first converted to a common type. +numeric arguments are first converted to a common real type. This operation can be customized using the special :meth:`~object.__sub__` and :meth:`~object.__rsub__` methods. From 7a04eb00b236cd41fbf16fba3355d7366726da10 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 3 Oct 2024 05:26:48 +0300 Subject: [PATCH 09/24] address review: reword what's new --- Doc/whatsnew/3.14.rst | 2 +- .../2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b071dad8c16f3b..82d01ce784f8f9 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -190,7 +190,7 @@ Other Language Changes They raise an error if the argument is a string. (Contributed by Serhiy Storchaka in :gh:`84978`.) -* Implement mixed-mode arithmetic rules combining real and complex variables as +* Implement mixed-mode arithmetic rules combining real and complex numbers as specified by C standards since C99. (Contributed by Sergey B Kirpichev in :gh:`69639`.) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst b/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst index 0581bcd8c13681..72596b0302aa45 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst @@ -1,2 +1,2 @@ -Implement mixed-mode arithmetic rules combining real and complex variables +Implement mixed-mode arithmetic rules combining real and complex numbers as specified by C standards since C99. Patch by Sergey B Kirpichev. From 1d42c10202c275e95ea4b0166051f9dfa9b2c9e1 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 4 Oct 2024 07:59:41 +0300 Subject: [PATCH 10/24] cleanup: just one macro --- Objects/complexobject.c | 91 ++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index e0f6204cef1af8..268d058ed24e45 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -43,6 +43,12 @@ _Py_cd_sum(Py_complex a, double b) return r; } +static inline Py_complex +_Py_dc_sum(double a, Py_complex b) +{ + return _Py_cd_sum(b, a); +} + Py_complex _Py_c_diff(Py_complex a, Py_complex b) { @@ -53,18 +59,18 @@ _Py_c_diff(Py_complex a, Py_complex b) } Py_complex -_Py_dc_diff(double a, Py_complex b) +_Py_cd_diff(Py_complex a, double b) { - Py_complex r = {a, -b.imag}; - r.real -= b.real; + Py_complex r = a; + r.real -= b; return r; } Py_complex -_Py_cd_diff(Py_complex a, double b) +_Py_dc_diff(double a, Py_complex b) { - Py_complex r = a; - r.real -= b; + Py_complex r = {a, -b.imag}; + r.real -= b.real; return r; } @@ -95,6 +101,12 @@ _Py_cd_prod(Py_complex a, double b) return r; } +static inline Py_complex +_Py_dc_prod(double a, Py_complex b) +{ + return _Py_cd_prod(b, a); +} + /* Avoid bad optimization on Windows ARM64 until the compiler is fixed */ #ifdef _M_ARM64 #pragma optimize("", off) @@ -178,7 +190,22 @@ _Py_c_quot(Py_complex a, Py_complex b) return r; } -/* an equivalent of above function, when 1st argument is real */ +Py_complex +_Py_cd_quot(Py_complex a, double b) +{ + Py_complex r = a; + if (b) { + r.real /= b; + r.imag /= b; + } + else { + errno = EDOM; + r.real = r.imag = 0.0; + } + return r; +} + +/* an equivalent of _Py_c_quot() function, when 1st argument is real */ Py_complex _Py_dc_quot(double a, Py_complex b) { @@ -224,21 +251,6 @@ _Py_dc_quot(double a, Py_complex b) #pragma optimize("", on) #endif -Py_complex -_Py_cd_quot(Py_complex a, double b) -{ - Py_complex r = a; - if (b) { - r.real /= b; - r.imag /= b; - } - else { - errno = EDOM; - r.real = r.imag = 0.0; - } - return r; -} - Py_complex _Py_c_pow(Py_complex a, Py_complex b) { @@ -608,34 +620,7 @@ real_to_complex(PyObject **pobj, Py_complex *pc) See C11's Annex G, sections G.5.1 and G.5.2. */ -#define COMM_BINOP(name, func) \ - static PyObject * \ - complex_##name(PyObject *v, PyObject *w) \ - { \ - if (!PyComplex_Check(v)) { \ - PyObject *tmp = v; \ - v = w; \ - w = tmp; \ - } \ - Py_complex a = ((PyComplexObject *)(v))->cval; \ - double b; \ - if (PyComplex_Check(w)) { \ - Py_complex b = ((PyComplexObject *)(w))->cval; \ - a = _Py_c_##func(a, b); \ - } \ - else if (real_to_double(&w, &b) < 0) { \ - return w; \ - } \ - else { \ - a = _Py_cd_##func(a, b); \ - } \ - return PyComplex_FromCComplex(a); \ - } - -COMM_BINOP(add, sum) -COMM_BINOP(mul, prod) - -#define GEN_BINOP(name, func) \ +#define COMPLEX_BINOP(name, func) \ static PyObject * \ complex_##name(PyObject *v, PyObject *w) \ { \ @@ -670,8 +655,10 @@ COMM_BINOP(mul, prod) return PyComplex_FromCComplex(a); \ } -GEN_BINOP(sub, diff) -GEN_BINOP(div, quot) +COMPLEX_BINOP(add, sum) +COMPLEX_BINOP(mul, prod) +COMPLEX_BINOP(sub, diff) +COMPLEX_BINOP(div, quot) static PyObject * complex_pow(PyObject *v, PyObject *w, PyObject *z) From d6e9d14e1c54cb9d3db07fe1386bfe62b4fb619a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Oct 2024 08:52:17 +0300 Subject: [PATCH 11/24] renamed c-api helpers: _dc_ -> _rc_ and _cd_ -> _cr_ --- Doc/c-api/complex.rst | 12 ++++++------ Include/cpython/complexobject.h | 12 ++++++------ Objects/complexobject.c | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 4ac3d9d5fb2d69..df6fca752b36f5 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -44,7 +44,7 @@ pointers. This is consistent throughout the API. representation. -.. c:function:: Py_complex _Py_cd_sum(Py_complex left, double right) +.. c:function:: Py_complex _Py_cr_sum(Py_complex left, double right) Return the sum of complex and real number, using the C :c:type:`Py_complex` representation. @@ -58,7 +58,7 @@ pointers. This is consistent throughout the API. :c:type:`Py_complex` representation. -.. c:function:: Py_complex _Py_dc_diff(double left, Py_complex right) +.. c:function:: Py_complex _Py_rc_diff(double left, Py_complex right) Return the difference of real and complex number, using the C :c:type:`Py_complex` representation. @@ -66,7 +66,7 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 -.. c:function:: Py_complex _Py_cd_diff(Py_complex left, double right) +.. c:function:: Py_complex _Py_cr_diff(Py_complex left, double right) Return the difference of complex and real number, using the C :c:type:`Py_complex` representation. @@ -86,7 +86,7 @@ pointers. This is consistent throughout the API. representation. -.. c:function:: Py_complex _Py_cd_prod(Py_complex left, double right) +.. c:function:: Py_complex _Py_cr_prod(Py_complex left, double right) Return the product of complex and real number, using the C :c:type:`Py_complex` representation. @@ -103,7 +103,7 @@ pointers. This is consistent throughout the API. :c:data:`errno` to :c:macro:`!EDOM`. -.. c:function:: Py_complex _Py_dc_quot(double left, Py_complex right) +.. c:function:: Py_complex _Py_rc_quot(double left, Py_complex right) Return the quotent of real and complex number, using the C :c:type:`Py_complex` representation. @@ -114,7 +114,7 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 -.. c:function:: Py_complex _Py_cd_quot(Py_complex left, double right) +.. c:function:: Py_complex _Py_cr_quot(Py_complex left, double right) Return the quotient of complex and real number, using the C :c:type:`Py_complex` representation. diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h index 6ee064527d3356..e408e8bcc0aff0 100644 --- a/Include/cpython/complexobject.h +++ b/Include/cpython/complexobject.h @@ -9,16 +9,16 @@ typedef struct { // Operations on complex numbers. PyAPI_FUNC(Py_complex) _Py_c_sum(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_cd_sum(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_cr_sum(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_dc_diff(double, Py_complex); -PyAPI_FUNC(Py_complex) _Py_cd_diff(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_rc_diff(double, Py_complex); +PyAPI_FUNC(Py_complex) _Py_cr_diff(Py_complex, double); 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_cd_prod(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_cr_prod(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_quot(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_dc_quot(double, Py_complex); -PyAPI_FUNC(Py_complex) _Py_cd_quot(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_rc_quot(double, Py_complex); +PyAPI_FUNC(Py_complex) _Py_cr_quot(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex); PyAPI_FUNC(double) _Py_c_abs(Py_complex); diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 268d058ed24e45..9ba0ae329b7a2a 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -36,7 +36,7 @@ _Py_c_sum(Py_complex a, Py_complex b) } Py_complex -_Py_cd_sum(Py_complex a, double b) +_Py_cr_sum(Py_complex a, double b) { Py_complex r = a; r.real += b; @@ -44,9 +44,9 @@ _Py_cd_sum(Py_complex a, double b) } static inline Py_complex -_Py_dc_sum(double a, Py_complex b) +_Py_rc_sum(double a, Py_complex b) { - return _Py_cd_sum(b, a); + return _Py_cr_sum(b, a); } Py_complex @@ -59,7 +59,7 @@ _Py_c_diff(Py_complex a, Py_complex b) } Py_complex -_Py_cd_diff(Py_complex a, double b) +_Py_cr_diff(Py_complex a, double b) { Py_complex r = a; r.real -= b; @@ -67,7 +67,7 @@ _Py_cd_diff(Py_complex a, double b) } Py_complex -_Py_dc_diff(double a, Py_complex b) +_Py_rc_diff(double a, Py_complex b) { Py_complex r = {a, -b.imag}; r.real -= b.real; @@ -93,7 +93,7 @@ _Py_c_prod(Py_complex a, Py_complex b) } Py_complex -_Py_cd_prod(Py_complex a, double b) +_Py_cr_prod(Py_complex a, double b) { Py_complex r = a; r.real *= b; @@ -102,9 +102,9 @@ _Py_cd_prod(Py_complex a, double b) } static inline Py_complex -_Py_dc_prod(double a, Py_complex b) +_Py_rc_prod(double a, Py_complex b) { - return _Py_cd_prod(b, a); + return _Py_cr_prod(b, a); } /* Avoid bad optimization on Windows ARM64 until the compiler is fixed */ @@ -191,7 +191,7 @@ _Py_c_quot(Py_complex a, Py_complex b) } Py_complex -_Py_cd_quot(Py_complex a, double b) +_Py_cr_quot(Py_complex a, double b) { Py_complex r = a; if (b) { @@ -207,7 +207,7 @@ _Py_cd_quot(Py_complex a, double b) /* an equivalent of _Py_c_quot() function, when 1st argument is real */ Py_complex -_Py_dc_quot(double a, Py_complex b) +_Py_rc_quot(double a, Py_complex b) { Py_complex r; const double abs_breal = b.real < 0 ? -b.real : b.real; @@ -636,7 +636,7 @@ real_to_complex(PyObject **pobj, Py_complex *pc) return v; \ } \ else { \ - a = _Py_dc_##func(a.real, b); \ + a = _Py_rc_##func(a.real, b); \ } \ } \ else { \ @@ -645,7 +645,7 @@ real_to_complex(PyObject **pobj, Py_complex *pc) if (real_to_double(&w, &b) < 0) { \ return w; \ } \ - a = _Py_cd_##func(a, b); \ + a = _Py_cr_##func(a, b); \ } \ if (errno == EDOM) { \ PyErr_SetString(PyExc_ZeroDivisionError, \ From 2db007212da2a1b7c9592677a8e78360faebbff0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Oct 2024 09:50:20 +0300 Subject: [PATCH 12/24] 1d42c10202 +1 --- Doc/c-api/complex.rst | 16 ++++++++-------- Include/cpython/complexobject.h | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index df6fca752b36f5..c1ed59812e8934 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -58,17 +58,17 @@ pointers. This is consistent throughout the API. :c:type:`Py_complex` representation. -.. c:function:: Py_complex _Py_rc_diff(double left, Py_complex right) +.. c:function:: Py_complex _Py_cr_diff(Py_complex left, double right) - Return the difference of real and complex number, using the C + Return the difference of complex and real number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 -.. c:function:: Py_complex _Py_cr_diff(Py_complex left, double right) +.. c:function:: Py_complex _Py_rc_diff(double left, Py_complex right) - Return the difference of complex and real number, using the C + Return the difference of real and complex number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 @@ -103,9 +103,9 @@ pointers. This is consistent throughout the API. :c:data:`errno` to :c:macro:`!EDOM`. -.. c:function:: Py_complex _Py_rc_quot(double left, Py_complex right) +.. c:function:: Py_complex _Py_cr_quot(Py_complex left, double right) - Return the quotent of real and complex number, using the C + Return the quotient of complex and real number, using the C :c:type:`Py_complex` representation. If *divisor* is zero, this method returns zero and sets @@ -114,9 +114,9 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 -.. c:function:: Py_complex _Py_cr_quot(Py_complex left, double right) +.. c:function:: Py_complex _Py_rc_quot(double left, Py_complex right) - Return the quotient of complex and real number, using the C + Return the quotent of real and complex number, using the C :c:type:`Py_complex` representation. If *divisor* is zero, this method returns zero and sets diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h index e408e8bcc0aff0..28576afad0b6b5 100644 --- a/Include/cpython/complexobject.h +++ b/Include/cpython/complexobject.h @@ -11,14 +11,14 @@ typedef struct { PyAPI_FUNC(Py_complex) _Py_c_sum(Py_complex, Py_complex); PyAPI_FUNC(Py_complex) _Py_cr_sum(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_rc_diff(double, Py_complex); PyAPI_FUNC(Py_complex) _Py_cr_diff(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_rc_diff(double, 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_cr_prod(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_quot(Py_complex, Py_complex); -PyAPI_FUNC(Py_complex) _Py_rc_quot(double, Py_complex); PyAPI_FUNC(Py_complex) _Py_cr_quot(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_rc_quot(double, Py_complex); PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex); PyAPI_FUNC(double) _Py_c_abs(Py_complex); From 46bed69dd27d2ee75f3007dfb4c5646d5bff9f52 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Oct 2024 09:51:36 +0300 Subject: [PATCH 13/24] + tests for semi-private C-API --- Lib/test/test_capi/test_complex.py | 45 ++++++++++++++++++++++++++- Modules/_testcapi/complex.c | 50 ++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 368edfbf2ce97e..97e0eb3f043080 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -7,6 +7,7 @@ FloatSubclass, Float, BadFloat, BadFloat2, ComplexSubclass) from test.support import import_helper +from test.support.testcase import ComplexesAreIdenticalMixin _testcapi = import_helper.import_module('_testcapi') @@ -23,7 +24,7 @@ def __complex__(self): raise RuntimeError -class CAPIComplexTest(unittest.TestCase): +class CAPIComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase): def test_check(self): # Test PyComplex_Check() check = _testlimitedcapi.complex_check @@ -171,12 +172,33 @@ def test_py_c_sum(self): self.assertEqual(_py_c_sum(1, 1j), (1+1j, 0)) + def test_py_cr_sum(self): + # Test _Py_cr_sum() + _py_cr_sum = _testcapi._py_cr_sum + + self.assertComplexesAreIdentical(_py_cr_sum(-0j, -0.0)[0], + complex(-0.0, -0.0)) + def test_py_c_diff(self): # Test _Py_c_diff() _py_c_diff = _testcapi._py_c_diff self.assertEqual(_py_c_diff(1, 1j), (1-1j, 0)) + def test_py_cr_diff(self): + # Test _Py_cr_diff() + _py_cr_diff = _testcapi._py_cr_diff + + self.assertComplexesAreIdentical(_py_cr_diff(-0j, 0.0)[0], + complex(-0.0, -0.0)) + + def test_py_rc_diff(self): + # Test _Py_rc_diff() + _py_rc_diff = _testcapi._py_rc_diff + + self.assertComplexesAreIdentical(_py_rc_diff(-0.0, 0j)[0], + complex(-0.0, -0.0)) + def test_py_c_neg(self): # Test _Py_c_neg() _py_c_neg = _testcapi._py_c_neg @@ -189,6 +211,13 @@ def test_py_c_prod(self): self.assertEqual(_py_c_prod(2, 1j), (2j, 0)) + def test_py_cr_prod(self): + # Test _Py_cr_prod() + _py_cr_prod = _testcapi._py_cr_prod + + self.assertComplexesAreIdentical(_py_cr_prod(complex('inf+1j'), INF)[0], + complex('inf+infj')) + def test_py_c_quot(self): # Test _Py_c_quot() _py_c_quot = _testcapi._py_c_quot @@ -211,6 +240,20 @@ def test_py_c_quot(self): self.assertEqual(_py_c_quot(1, 0j)[1], errno.EDOM) + def test_py_cr_quot(self): + # Test _Py_cr_quot() + _py_cr_quot = _testcapi._py_cr_quot + + self.assertComplexesAreIdentical(_py_cr_quot(complex('inf+1j'), 2**1000)[0], + INF + 2**-1000*1j) + + def test_py_rc_quot(self): + # Test _Py_rc_quot() + _py_rc_quot = _testcapi._py_rc_quot + + self.assertComplexesAreIdentical(_py_rc_quot(1.0, complex('nan-infj'))[0], + 0j) + def test_py_c_pow(self): # Test _Py_c_pow() _py_c_pow = _testcapi._py_c_pow diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index eceb1310bfe874..b726cd3236f179 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -46,21 +46,59 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) static PyObject * \ _py_c_##suffix(PyObject *Py_UNUSED(module), PyObject *args) \ { \ - Py_complex num, exp, res; \ + Py_complex a, b, res; \ \ - if (!PyArg_ParseTuple(args, "DD", &num, &exp)) { \ + if (!PyArg_ParseTuple(args, "DD", &a, &b)) { \ return NULL; \ } \ \ errno = 0; \ - res = _Py_c_##suffix(num, exp); \ + res = _Py_c_##suffix(a, b); \ + return Py_BuildValue("Di", &res, errno); \ + }; + +#define _PY_CR_FUNC2(suffix) \ + static PyObject * \ + _py_cr_##suffix(PyObject *Py_UNUSED(module), PyObject *args) \ + { \ + Py_complex a, res; \ + double b; \ + \ + if (!PyArg_ParseTuple(args, "Dd", &a, &b)) { \ + return NULL; \ + } \ + \ + errno = 0; \ + res = _Py_cr_##suffix(a, b); \ + return Py_BuildValue("Di", &res, errno); \ + }; + +#define _PY_RC_FUNC2(suffix) \ + static PyObject * \ + _py_rc_##suffix(PyObject *Py_UNUSED(module), PyObject *args) \ + { \ + Py_complex b, res; \ + double a; \ + \ + if (!PyArg_ParseTuple(args, "dD", &a, &b)) { \ + return NULL; \ + } \ + \ + errno = 0; \ + res = _Py_rc_##suffix(a, b); \ return Py_BuildValue("Di", &res, errno); \ }; _PY_C_FUNC2(sum) +_PY_CR_FUNC2(sum) _PY_C_FUNC2(diff) +_PY_CR_FUNC2(diff) +_PY_RC_FUNC2(diff) _PY_C_FUNC2(prod) +_PY_CR_FUNC2(prod) _PY_C_FUNC2(quot) +_PY_CR_FUNC2(quot) +_PY_RC_FUNC2(quot) _PY_C_FUNC2(pow) static PyObject* @@ -86,10 +124,16 @@ static PyMethodDef test_methods[] = { {"complex_fromccomplex", complex_fromccomplex, METH_O}, {"complex_asccomplex", complex_asccomplex, METH_O}, {"_py_c_sum", _py_c_sum, METH_VARARGS}, + {"_py_cr_sum", _py_cr_sum, METH_VARARGS}, {"_py_c_diff", _py_c_diff, METH_VARARGS}, + {"_py_cr_diff", _py_cr_diff, METH_VARARGS}, + {"_py_rc_diff", _py_rc_diff, METH_VARARGS}, {"_py_c_neg", _py_c_neg, METH_O}, {"_py_c_prod", _py_c_prod, METH_VARARGS}, + {"_py_cr_prod", _py_cr_prod, METH_VARARGS}, {"_py_c_quot", _py_c_quot, METH_VARARGS}, + {"_py_cr_quot", _py_cr_quot, METH_VARARGS}, + {"_py_rc_quot", _py_rc_quot, METH_VARARGS}, {"_py_c_pow", _py_c_pow, METH_VARARGS}, {"_py_c_abs", _py_c_abs, METH_O}, {NULL}, From cc298e555873845b4ab00bea734bfb85d81892cd Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 8 Oct 2024 07:01:57 +0300 Subject: [PATCH 14/24] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/c-api/complex.rst | 16 ++++++++-------- Doc/reference/expressions.rst | 8 ++++---- Doc/whatsnew/3.14.rst | 4 ++-- Objects/complexobject.c | 11 ++++++----- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index c1ed59812e8934..46529f95c85aea 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -46,7 +46,7 @@ pointers. This is consistent throughout the API. .. c:function:: Py_complex _Py_cr_sum(Py_complex left, double right) - Return the sum of complex and real number, using the C :c:type:`Py_complex` + Return the sum of a complex and a real number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 @@ -60,7 +60,7 @@ pointers. This is consistent throughout the API. .. c:function:: Py_complex _Py_cr_diff(Py_complex left, double right) - Return the difference of complex and real number, using the C + Return the difference between a complex number and a real number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 @@ -68,7 +68,7 @@ pointers. This is consistent throughout the API. .. c:function:: Py_complex _Py_rc_diff(double left, Py_complex right) - Return the difference of real and complex number, using the C + Return the difference between a real number and a complex number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 @@ -88,7 +88,7 @@ pointers. This is consistent throughout the API. .. c:function:: Py_complex _Py_cr_prod(Py_complex left, double right) - Return the product of complex and real number, using the C + Return the product of a complex number and a real number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 @@ -103,9 +103,9 @@ pointers. This is consistent throughout the API. :c:data:`errno` to :c:macro:`!EDOM`. -.. c:function:: Py_complex _Py_cr_quot(Py_complex left, double right) +.. c:function:: Py_complex _Py_cr_quot(Py_complex dividend, double divisor) - Return the quotient of complex and real number, using the C + Return the quotient of a complex number and a real number, using the C :c:type:`Py_complex` representation. If *divisor* is zero, this method returns zero and sets @@ -114,9 +114,9 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 -.. c:function:: Py_complex _Py_rc_quot(double left, Py_complex right) +.. c:function:: Py_complex _Py_rc_quot(double dividend, Py_complex divisor) - Return the quotent of real and complex number, using the C + Return the quotient of a real number and a complex number, using the C :c:type:`Py_complex` representation. If *divisor* is zero, this method returns zero and sets diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 41930b17c6dbe6..605557822a92cb 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -31,7 +31,7 @@ When a description of an arithmetic operator below uses the phrase "the numeric arguments are converted to a common real type", this means that the operator implementation for built-in types works as follows: -* If both arguments are complex numbers, no conversion is necessary; +* If both arguments are complex numbers, no conversion is performed; * otherwise, if either argument is a complex or a floating-point number, the other is converted to a floating-point number; @@ -1329,7 +1329,7 @@ This operation can be customized using the special :meth:`~object.__mul__` and :meth:`~object.__rmul__` methods. .. versionchanged:: 3.14 - If only one operand is a complex number, the other operand converted + If only one operand is a complex number, the other operand is converted to a floating-point number. .. index:: @@ -1406,7 +1406,7 @@ This operation can be customized using the special :meth:`~object.__add__` and :meth:`~object.__radd__` methods. .. versionchanged:: 3.14 - If only one operand is a complex number, the other operand converted + If only one operand is a complex number, the other operand is converted to a floating-point number. .. index:: @@ -1421,7 +1421,7 @@ This operation can be customized using the special :meth:`~object.__sub__` and :meth:`~object.__rsub__` methods. .. versionchanged:: 3.14 - If only one operand is a complex number, the other operand converted + If only one operand is a complex number, the other operand is converted to a floating-point number. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index e30ae3f6672c94..09f7a4ff7fc2f5 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -191,8 +191,8 @@ Other Language Changes (Contributed by Serhiy Storchaka in :gh:`84978`.) * Implement mixed-mode arithmetic rules combining real and complex numbers as - specified by C standards since C99. (Contributed by Sergey B Kirpichev in - :gh:`69639`.) + specified by C standards since C99. + (Contributed by Sergey B Kirpichev in :gh:`69639`.) New Modules diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 9ba0ae329b7a2a..02050abdee28c4 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -69,8 +69,9 @@ _Py_cr_diff(Py_complex a, double b) Py_complex _Py_rc_diff(double a, Py_complex b) { - Py_complex r = {a, -b.imag}; - r.real -= b.real; + Py_complex r; + r.real = a - b.real; + r.imag = -b.imag; return r; } @@ -606,11 +607,11 @@ real_to_complex(PyObject **pobj, Py_complex *pc) } /* Complex arithmetic rules implement special mixed-mode case: combining - pure-real (float's or int's) value and complex value performed directly, not + a pure-real (float's or int's) value and a complex value is performed directly, not by first coercing the real value to complex. - Lets consider the addition as an example, assuming that int's are implicitly - converted to float's. We have following rules (up to variants with changed + Let us consider the addition as an example, assuming that int's are implicitly + converted to float's. We have the following rules (up to variants with changed order of operands): complex(x, y) + complex(u, v) = complex(x + u, y + v) From ee22926a720479660a69f1d0abe9c941cadef00a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 13 Oct 2024 18:28:27 +0300 Subject: [PATCH 15/24] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/c-api/complex.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 46529f95c85aea..d1f5d8eda676ef 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -46,7 +46,7 @@ pointers. This is consistent throughout the API. .. c:function:: Py_complex _Py_cr_sum(Py_complex left, double right) - Return the sum of a complex and a real number, using the C :c:type:`Py_complex` + Return the sum of a complex number and a real number, using the C :c:type:`Py_complex` representation. .. versionadded:: 3.14 From 220c5516962a034efa233318bed1b4457bc2f5bc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 14 Oct 2024 06:43:17 +0300 Subject: [PATCH 16/24] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/reference/expressions.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 605557822a92cb..5a8e5b86029ff3 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -33,8 +33,7 @@ implementation for built-in types works as follows: * If both arguments are complex numbers, no conversion is performed; -* otherwise, if either argument is a complex or a floating-point number, the - other is converted to a floating-point number; +* if either argument is a complex or a floating-point number, the other is converted to a floating-point number; * otherwise, both must be integers and no conversion is necessary. From f06838471aeafd197fc753d559cf19806835da1a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 14 Oct 2024 08:05:57 +0300 Subject: [PATCH 17/24] Apply suggestions from code review Co-authored-by: Carol Willing --- Objects/complexobject.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 02050abdee28c4..18c63eaab15b30 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -606,12 +606,12 @@ real_to_complex(PyObject **pobj, Py_complex *pc) return real_to_double(pobj, &(pc->real)); } -/* Complex arithmetic rules implement special mixed-mode case: combining - a pure-real (float's or int's) value and a complex value is performed directly, not - by first coercing the real value to complex. +/* Complex arithmetic rules implement special mixed-mode case where combining + a pure-real (float or int) value and a complex value is performed directly + without first coercing the real value to a complex value. - Let us consider the addition as an example, assuming that int's are implicitly - converted to float's. We have the following rules (up to variants with changed + Let us consider the addition as an example, assuming that ints are implicitly + converted to floats. We have the following rules (up to variants with changed order of operands): complex(x, y) + complex(u, v) = complex(x + u, y + v) From 7fb1be19cc8f54846b10ca6a17cc82d800068b8f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 30 Oct 2024 06:12:24 +0300 Subject: [PATCH 18/24] + move news --- .../2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core and Builtins => Core_and_Builtins}/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst From badf4920791aa26c326bd52b4585d7b4da066124 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 31 Oct 2024 15:52:50 +0300 Subject: [PATCH 19/24] address review: * macro formatting * macro params * check if some argument of complex_##NAME is a complex --- Objects/complexobject.c | 69 +++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 18c63eaab15b30..c472f35086db1b 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -621,39 +621,42 @@ real_to_complex(PyObject **pobj, Py_complex *pc) See C11's Annex G, sections G.5.1 and G.5.2. */ -#define COMPLEX_BINOP(name, func) \ - static PyObject * \ - complex_##name(PyObject *v, PyObject *w) \ - { \ - Py_complex a; \ - errno = 0; \ - if (PyComplex_Check(w)) { \ - Py_complex b = ((PyComplexObject *)(w))->cval; \ - if (PyComplex_Check(v)) { \ - a = ((PyComplexObject *)(v))->cval; \ - a = _Py_c_##func(a, b); \ - } \ - else if (real_to_double(&v, &a.real) < 0) { \ - return v; \ - } \ - else { \ - a = _Py_rc_##func(a.real, b); \ - } \ - } \ - else { \ - a = ((PyComplexObject *)(v))->cval; \ - double b; \ - if (real_to_double(&w, &b) < 0) { \ - return w; \ - } \ - a = _Py_cr_##func(a, b); \ - } \ - if (errno == EDOM) { \ - PyErr_SetString(PyExc_ZeroDivisionError, \ - "division by zero"); \ - return NULL; \ - } \ - return PyComplex_FromCComplex(a); \ +#define COMPLEX_BINOP(NAME, FUNC) \ + static PyObject * \ + complex_##NAME(PyObject *v, PyObject *w) \ + { \ + Py_complex a; \ + errno = 0; \ + if (PyComplex_Check(w)) { \ + Py_complex b = ((PyComplexObject *)w)->cval; \ + if (PyComplex_Check(v)) { \ + a = ((PyComplexObject *)(v))->cval; \ + a = _Py_c_##FUNC(a, b); \ + } \ + else if (real_to_double(&v, &a.real) < 0) { \ + return v; \ + } \ + else { \ + a = _Py_rc_##FUNC(a.real, b); \ + } \ + } \ + else if (!PyComplex_Check(v)) { \ + Py_RETURN_NOTIMPLEMENTED; \ + } \ + else { \ + a = ((PyComplexObject *)v)->cval; \ + double b; \ + if (real_to_double(&w, &b) < 0) { \ + return w; \ + } \ + a = _Py_cr_##FUNC(a, b); \ + } \ + if (errno == EDOM) { \ + PyErr_SetString(PyExc_ZeroDivisionError, \ + "division by zero"); \ + return NULL; \ + } \ + return PyComplex_FromCComplex(a); \ } COMPLEX_BINOP(add, sum) From 38762b3ffbc4e5e5248c184cdcf74b7a454b399c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 31 Oct 2024 16:15:49 +0300 Subject: [PATCH 20/24] address review: rename symbols in commentary to avoid clash with macro --- Objects/complexobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index c472f35086db1b..16d64108a6ac35 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -614,8 +614,8 @@ real_to_complex(PyObject **pobj, Py_complex *pc) converted to floats. We have the following rules (up to variants with changed order of operands): - complex(x, y) + complex(u, v) = complex(x + u, y + v) - float(x) + complex(u, v) = complex(x + u, v) + complex(a, b) + complex(c, d) = complex(a + c, b + d) + float(a) + complex(b, c) = complex(a + b, c) Similar rules are implemented for subtraction, multiplication and division. See C11's Annex G, sections G.5.1 and G.5.2. From e685bd98d21ebb0854bf8479c9b6ff228f8607a2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 31 Oct 2024 17:07:37 +0300 Subject: [PATCH 21/24] Update Objects/complexobject.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Objects/complexobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 16d64108a6ac35..108e5d0b085e65 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -630,7 +630,7 @@ real_to_complex(PyObject **pobj, Py_complex *pc) if (PyComplex_Check(w)) { \ Py_complex b = ((PyComplexObject *)w)->cval; \ if (PyComplex_Check(v)) { \ - a = ((PyComplexObject *)(v))->cval; \ + a = ((PyComplexObject *)v)->cval; \ a = _Py_c_##FUNC(a, b); \ } \ else if (real_to_double(&v, &a.real) < 0) { \ From 2b46a960ec049a806be8a6bb1d00e4cec45bc2ce Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 26 Nov 2024 04:10:53 +0300 Subject: [PATCH 22/24] added sum() test --- Lib/test/test_builtin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index f8e6f05cd607c8..b4f59488ed69a5 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -35,6 +35,7 @@ from test.support.import_helper import import_module from test.support.os_helper import (EnvironmentVarGuard, TESTFN, unlink) from test.support.script_helper import assert_python_ok +from test.support.testcase import ComplexesAreIdenticalMixin from test.support.warnings_helper import check_warnings from test.support import requires_IEEE_754 from unittest.mock import MagicMock, patch @@ -151,7 +152,7 @@ def map_char(arg): def pack(*args): return args -class BuiltinTest(unittest.TestCase): +class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase): # Helper to check picklability def check_iter_pickle(self, it, seq, proto): itorg = it @@ -1902,6 +1903,13 @@ def __getitem__(self, index): self.assertEqual(sum(xs), complex(sum(z.real for z in xs), sum(z.imag for z in xs))) + # test that sum() of complex and real numbers doesn't + # smash sign of imaginary 0 + self.assertComplexesAreIdentical(sum([complex(1, -0.0), 1]), + complex(2, -0.0)) + self.assertComplexesAreIdentical(sum([complex(1, -0.0), 1.0]), + complex(2, -0.0)) + @requires_IEEE_754 @unittest.skipIf(HAVE_DOUBLE_ROUNDING, "sum accuracy not guaranteed on machines with double rounding") From 759a6d03cdf3d600388848ebf8848fd8ee0adb0c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 26 Nov 2024 11:44:06 +0200 Subject: [PATCH 23/24] Update Lib/test/test_builtin.py Co-authored-by: Sergey B Kirpichev --- Lib/test/test_builtin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index b4f59488ed69a5..d97e65c6b0153e 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1907,8 +1907,12 @@ def __getitem__(self, index): # smash sign of imaginary 0 self.assertComplexesAreIdentical(sum([complex(1, -0.0), 1]), complex(2, -0.0)) + self.assertComplexesAreIdentical(sum([1, complex(1, -0.0)]), + complex(2, -0.0)) self.assertComplexesAreIdentical(sum([complex(1, -0.0), 1.0]), complex(2, -0.0)) + self.assertComplexesAreIdentical(sum([1.0, complex(1, -0.0)]), + complex(2, -0.0)) @requires_IEEE_754 @unittest.skipIf(HAVE_DOUBLE_ROUNDING, From ed67dc2d0e846e29e13a9e522cae8a49b4424cc8 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 26 Nov 2024 14:40:51 +0300 Subject: [PATCH 24/24] I'LL NEVER USE WEB-EDITOR... I'LL NEVER USE WEB-EDITOR... I'LL NEVER USE WEB-EDITOR... I'LL NEVER USE WEB-EDITOR... --- Lib/test/test_builtin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index d97e65c6b0153e..e51711d9b4f1a4 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1908,11 +1908,11 @@ def __getitem__(self, index): self.assertComplexesAreIdentical(sum([complex(1, -0.0), 1]), complex(2, -0.0)) self.assertComplexesAreIdentical(sum([1, complex(1, -0.0)]), - complex(2, -0.0)) + complex(2, -0.0)) self.assertComplexesAreIdentical(sum([complex(1, -0.0), 1.0]), complex(2, -0.0)) self.assertComplexesAreIdentical(sum([1.0, complex(1, -0.0)]), - complex(2, -0.0)) + complex(2, -0.0)) @requires_IEEE_754 @unittest.skipIf(HAVE_DOUBLE_ROUNDING,