mirror of
https://github.com/python/cpython.git
synced 2025-12-15 21:44:50 +00:00
gh-69639: Add mixed-mode rules for complex arithmetic (C-like) (GH-124829)
"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).
This commit is contained in:
parent
dcf629213b
commit
987311d42e
15 changed files with 449 additions and 98 deletions
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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`):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.)
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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,84 +579,91 @@ 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);
|
||||
}
|
||||
*pobj = Py_NewRef(Py_NotImplemented);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
complex_add(PyObject *v, PyObject *w)
|
||||
{
|
||||
Py_complex result;
|
||||
Py_complex a, b;
|
||||
TO_COMPLEX(v, a);
|
||||
TO_COMPLEX(w, b);
|
||||
result = _Py_c_sum(a, b);
|
||||
return PyComplex_FromCComplex(result);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
else if (_Py_convert_int_to_double(pobj, dbl) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return PyComplex_FromCComplex(quot);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
real_to_complex(PyObject **pobj, Py_complex *pc)
|
||||
{
|
||||
pc->imag = 0.0;
|
||||
return real_to_double(pobj, &(pc->real));
|
||||
}
|
||||
|
||||
/* Complex arithmetic rules implement special mixed-mode case where combining
|
||||
a pure-real (float or int) value and a complex value is performed directly
|
||||
without first coercing the real value to a complex value.
|
||||
|
||||
Let us consider the addition as an example, assuming that ints are implicitly
|
||||
converted to floats. We have the following rules (up to variants with changed
|
||||
order of operands):
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue