summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/complex.rst54
-rw-r--r--Doc/library/cmath.rst12
-rw-r--r--Doc/library/stdtypes.rst16
-rw-r--r--Doc/reference/expressions.rst25
-rw-r--r--Doc/whatsnew/3.14.rst4
-rw-r--r--Include/cpython/complexobject.h6
-rw-r--r--Include/internal/pycore_floatobject.h2
-rw-r--r--Lib/test/test_builtin.py14
-rw-r--r--Lib/test/test_capi/test_complex.py45
-rw-r--r--Lib/test/test_complex.py57
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst2
-rw-r--r--Modules/_testcapi/complex.c50
-rw-r--r--Objects/complexobject.c236
-rw-r--r--Objects/floatobject.c12
-rw-r--r--Python/bltinmodule.c2
15 files changed, 444 insertions, 93 deletions
diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst
index 16bd794..d1f5d8e 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 f122e36..e7c027d 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 2347437..4f4fc9f 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 3eaceae..7c95b20 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.
@@ -1323,12 +1322,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)
@@ -1396,23 +1399,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 0e4b9eb..869a47c 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -230,6 +230,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`.)
+
* All Windows code pages are now supported as "cpXXX" codecs on Windows.
(Contributed by Serhiy Storchaka in :gh:`123803`.)
diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h
index fbdc6a9..28576af 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 be1c6cc..f44b081 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_builtin.py b/Lib/test/test_builtin.py
index f8e6f05..e51711d 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -35,6 +35,7 @@ from test.support import (cpython_only, swap_attr, maybe_get_event_loop_policy)
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,17 @@ class BuiltinTest(unittest.TestCase):
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([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,
"sum accuracy not guaranteed on machines with double rounding")
diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py
index 368edfb..97e0eb3 100644
--- a/Lib/test/test_capi/test_complex.py
+++ b/Lib/test/test_capi/test_complex.py
@@ -7,6 +7,7 @@ from test.test_capi.test_getargs import (BadComplex, BadComplex2, Complex,
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 @@ class BadComplex3:
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 @@ class CAPIComplexTest(unittest.TestCase):
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 @@ class CAPIComplexTest(unittest.TestCase):
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 @@ class CAPIComplexTest(unittest.TestCase):
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 c51327c..179556f 100644
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -126,6 +126,12 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
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 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
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 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
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 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
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 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
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 0000000..72596b0
--- /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 eceb131..b726cd3 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 9faa575..8fbca3c 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()
@@ -35,6 +36,20 @@ _Py_c_sum(Py_complex a, Py_complex b)
}
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)
{
Py_complex r;
@@ -44,6 +59,23 @@ _Py_c_diff(Py_complex a, Py_complex b)
}
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)
{
Py_complex r;
@@ -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 f00b6a6..bcc7728 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 85ebd5b..17df920 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -2829,7 +2829,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;
}
@@ -2842,7 +2841,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;
}