diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 16bd79475dc1e6..d1f5d8eda676ef 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_cr_sum(Py_complex left, double right) + + Return the sum of a complex number and a 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_cr_diff(Py_complex left, double right) + + Return the difference between a complex number and a real number, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: 3.14 + + +.. c:function:: Py_complex _Py_rc_diff(double left, Py_complex right) + + Return the difference between a real number and a complex 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_cr_prod(Py_complex left, double right) + + Return the product of a complex number and a 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_cr_quot(Py_complex dividend, double divisor) + + 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 + :c:data:`errno` to :c:macro:`!EDOM`. + + .. versionadded:: 3.14 + + +.. c:function:: Py_complex _Py_rc_quot(double dividend, Py_complex divisor) + + 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 + :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/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 a9b7662dcb212b..5df6216cd6e382 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/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index decde0d297cf59..63b8dd73127984 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -28,13 +28,12 @@ Arithmetic conversions .. index:: pair: arithmetic; conversion When a description of an arithmetic operator below uses the phrase "the numeric -arguments are converted to a common type", this means that the operator +arguments are converted to a common real 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 both arguments are complex numbers, no conversion is performed; -* otherwise, if either argument is a floating-point number, the other is - converted to floating point; +* 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,12 +1321,16 @@ 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 -common type and then multiplied together. In the latter case, sequence +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 :meth:`~object.__rmul__` methods. +.. versionchanged:: 3.14 + If only one operand is a complex number, the other operand is converted + to a floating-point number. + .. index:: single: matrix multiplication pair: operator; @ (at) @@ -1395,23 +1398,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 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 :meth:`~object.__radd__` methods. +.. versionchanged:: 3.14 + If only one operand is a complex number, the other operand is 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. +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. +.. versionchanged:: 3.14 + If only one operand is a complex number, the other operand is converted + to a floating-point number. + .. _shifting: diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 7f9e3107a6e1a0..e04cc77c16a820 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 numbers as + specified by C standards since C99. + (Contributed by Sergey B Kirpichev in :gh:`69639`.) + * :class:`super` objects are now :mod:`pickleable ` and :mod:`copyable `. (Contributed by Serhiy Storchaka in :gh:`125767`.) diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h index fbdc6a91fe895c..28576afad0b6b5 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_cr_sum(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, 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_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); diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index be1c6cc97720d2..f44b081b06cea5 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_int_to_double(PyObject **v, double *dbl); + #ifdef __cplusplus } 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/Lib/test/test_complex.py b/Lib/test/test_complex.py index ecc97315e50d31..5aeb91aa5718e1 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -126,6 +126,12 @@ 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)) self.assertComplexesAreIdentical(complex(INF, 1)/(0.0+1j), complex(NAN, -INF)) @@ -154,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): @@ -224,6 +263,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 +274,14 @@ 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.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) @@ -238,6 +289,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)) + 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/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..72596b0302aa45 --- /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 numbers +as specified by C standards since C99. Patch by Sergey B Kirpichev. 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}, diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 787235c63a6be1..108e5d0b085e65 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_int_to_double() #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_object.h" // _PyObject_Init() #include "pycore_pymath.h" // _Py_ADJUST_ERANGE2() @@ -34,6 +35,20 @@ _Py_c_sum(Py_complex a, Py_complex b) return r; } +Py_complex +_Py_cr_sum(Py_complex a, double b) +{ + Py_complex r = a; + r.real += b; + return r; +} + +static inline Py_complex +_Py_rc_sum(double a, Py_complex b) +{ + return _Py_cr_sum(b, a); +} + Py_complex _Py_c_diff(Py_complex a, Py_complex b) { @@ -43,6 +58,23 @@ _Py_c_diff(Py_complex a, Py_complex b) return r; } +Py_complex +_Py_cr_diff(Py_complex a, double b) +{ + Py_complex r = a; + r.real -= b; + return r; +} + +Py_complex +_Py_rc_diff(double a, Py_complex b) +{ + Py_complex r; + r.real = a - b.real; + r.imag = -b.imag; + return r; +} + Py_complex _Py_c_neg(Py_complex a) { @@ -61,6 +93,21 @@ _Py_c_prod(Py_complex a, Py_complex b) return r; } +Py_complex +_Py_cr_prod(Py_complex a, double b) +{ + Py_complex r = a; + r.real *= b; + r.imag *= b; + return r; +} + +static inline Py_complex +_Py_rc_prod(double a, Py_complex b) +{ + return _Py_cr_prod(b, a); +} + /* Avoid bad optimization on Windows ARM64 until the compiler is fixed */ #ifdef _M_ARM64 #pragma optimize("", off) @@ -143,6 +190,64 @@ _Py_c_quot(Py_complex a, Py_complex b) return r; } + +Py_complex +_Py_cr_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_rc_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 @@ -474,83 +579,90 @@ 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_complex(PyObject **pobj, Py_complex *pc) +real_to_double(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_int_to_double(pobj, dbl) < 0) { + return -1; } - *pobj = Py_NewRef(Py_NotImplemented); - return -1; + return 0; } - -static PyObject * -complex_add(PyObject *v, PyObject *w) +static int +real_to_complex(PyObject **pobj, Py_complex *pc) { - 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); + pc->imag = 0.0; + return real_to_double(pobj, &(pc->real)); } -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); -} +/* 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. -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); -} + 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): -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); - if (errno == EDOM) { - PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); - return NULL; - } - return PyComplex_FromCComplex(quot); -} + 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. + */ + +#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) +COMPLEX_BINOP(mul, prod) +COMPLEX_BINOP(sub, diff) +COMPLEX_BINOP(div, quot) static PyObject * complex_pow(PyObject *v, PyObject *w, PyObject *z) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index d66863febe8c86..5eb50a65d9c969 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -341,16 +341,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 (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 */ -static int -convert_to_double(PyObject **v, double *dbl) +int +_Py_convert_int_to_double(PyObject **v, double *dbl) { PyObject *obj = *v; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 12f065d4b4f138..8fd3ce29d6b3b1 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2750,7 +2750,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; } @@ -2763,7 +2762,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; }