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:
Sergey B Kirpichev 2024-11-26 18:57:39 +03:00 committed by GitHub
parent dcf629213b
commit 987311d42e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 449 additions and 98 deletions

View 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`

View 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

View 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`):

View 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:

View 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`.)

View 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);

View 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
}

View 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")

View 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

View 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)

View file

@ -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.

View 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},

View 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,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)
{

View 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;

View 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;
}