]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-69639: Add mixed-mode rules for complex arithmetic (C-like) (GH-124829)
authorSergey B Kirpichev <skirpichev@gmail.com>
Tue, 26 Nov 2024 15:57:39 +0000 (18:57 +0300)
committerGitHub <noreply@github.com>
Tue, 26 Nov 2024 15:57:39 +0000 (17:57 +0200)
"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).

15 files changed:
Doc/c-api/complex.rst
Doc/library/cmath.rst
Doc/library/stdtypes.rst
Doc/reference/expressions.rst
Doc/whatsnew/3.14.rst
Include/cpython/complexobject.h
Include/internal/pycore_floatobject.h
Lib/test/test_builtin.py
Lib/test/test_capi/test_complex.py
Lib/test/test_complex.py
Misc/NEWS.d/next/Core_and_Builtins/2024-08-03-14-02-27.gh-issue-69639.mW3iKq.rst [new file with mode: 0644]
Modules/_testcapi/complex.c
Objects/complexobject.c
Objects/floatobject.c
Python/bltinmodule.c

index 16bd79475dc1e66ea6fffd3ef3844dd3f1ce1f43..d1f5d8eda676ef52da1a789ae849edf705b13d2c 100644 (file)
@@ -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`
index f122e3644ece5607b87b76382fdb26d681f51e61..e7c027dd4d0c22daa23b415129ebab0ce384835d 100644 (file)
@@ -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
 
 
index 2347437d7273d96a972e95792e1596a0f2da1ca3..4f4fc9fba63120ebc525c0f10b2b4c5396093ab3 100644 (file)
@@ -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`):
index 3eaceae41f7eaf157bc45294b50eb698469b7010..7c95b207b1aed22c92962f2d997a9a245c5e2095 100644 (file)
@@ -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:
 
index 0e4b9eb0cf0b9c39173b0dd40013d2b92f85698d..869a47c12612936f9110854b56150e89a8b8d5dc 100644 (file)
@@ -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`.)
 
index fbdc6a91fe895c0f7af0bff6a3836a1991709c20..28576afad0b6b5b3f01c22bafa6db8ea839446e8 100644 (file)
@@ -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);
 
index be1c6cc97720d26c3b22fcbcd1fa26ac7a05e211..f44b081b06cea55b52db17703c04774dfc921f9f 100644 (file)
@@ -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
 }
index f8e6f05cd607c847e5c95ff78b5a67572ad7c731..e51711d9b4f1a4d464dcfa83104134a1503a7801 100644 (file)
@@ -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")
index 368edfbf2ce97ee4cc19e42e9a64e36c801bc80f..97e0eb3f0430809e2017ad71d6939d3ab149960b 100644 (file)
@@ -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
index c51327c7f33a0a20cffd9de465574ceb93e9a1f8..179556f57e884fc212e3ce2f3593a4a0821c3239 100644 (file)
@@ -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 (file)
index 0000000..72596b0
--- /dev/null
@@ -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.
index eceb1310bfe874a6895f60eefd6ae380e2b69228..b726cd3236f1792f7510e88b0481c71795e84239 100644 (file)
@@ -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},
index 9faa57519a424b129b40f79ecb825833fe431763..8fbca3cb02d80a5611ffdd14b9774fbe15f771af 100644 (file)
@@ -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)
index f00b6a6b4b2bdcd1748af405bf741f6df6b8c4c7..bcc77287454768e4a5b35930b3f18061777ac9c1 100644 (file)
@@ -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;
 
index 85ebd5b00cc18bf8c876d0b8aec8c5bfe823055e..17df9208f224f4a265a4520588b95c842ac65912 100644 (file)
@@ -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;
             }