diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index d1f5d8eda676ef..3d4b3bbe99c069 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -52,6 +52,14 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 +.. c:function:: Py_complex _Py_ci_sum(Py_complex left, double right) + + Return the sum of a complex number and an imaginary number, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: next + + .. c:function:: Py_complex _Py_c_diff(Py_complex left, Py_complex right) Return the difference between two complex numbers, using the C @@ -74,6 +82,22 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 +.. c:function:: Py_complex _Py_ci_diff(Py_complex left, double right) + + Return the difference between a complex number and an imaginary number, + using the C :c:type:`Py_complex` representation. + + .. versionadded:: next + + +.. c:function:: Py_complex _Py_ic_diff(double left, Py_complex right) + + Return the difference between an imaginary number and a complex number, + using the C :c:type:`Py_complex` representation. + + .. versionadded:: next + + .. c:function:: Py_complex _Py_c_neg(Py_complex num) Return the negation of the complex number *num*, using the C @@ -94,6 +118,14 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 +.. c:function:: Py_complex _Py_ci_prod(Py_complex left, double right) + + Return the product of a complex number and an imaginary number, using the C + :c:type:`Py_complex` representation. + + .. versionadded:: next + + .. 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` @@ -125,6 +157,28 @@ pointers. This is consistent throughout the API. .. versionadded:: 3.14 +.. c:function:: Py_complex _Py_ci_quot(Py_complex dividend, double divisor) + + Return the quotient of a complex number and an imaginary 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:: next + + +.. c:function:: Py_complex _Py_ic_quot(double dividend, Py_complex divisor) + + Return the quotient of an imaginary 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:: next + + .. c:function:: Py_complex _Py_c_pow(Py_complex num, Py_complex exp) Return the exponentiation of *num* by *exp*, using the C :c:type:`Py_complex` diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 59e7a31bc2ef06..74433a0aeac973 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -304,6 +304,8 @@ func,PyGILState_Release,3.2,, type,PyGILState_STATE,3.2,, type,PyGetSetDef,3.2,,full-abi data,PyGetSetDescr_Type,3.2,, +func,PyImaginary_FromDouble,3.14,, +data,PyImaginary_Type,3.14,, func,PyImport_AddModule,3.2,, func,PyImport_AddModuleObject,3.7,, func,PyImport_AddModuleRef,3.13,, diff --git a/Doc/library/cmath.rst b/Doc/library/cmath.rst index e7c027dd4d0c22..ef613863e34496 100644 --- a/Doc/library/cmath.rst +++ b/Doc/library/cmath.rst @@ -279,7 +279,7 @@ Constants .. data:: infj Complex number with zero real part and positive infinity imaginary - part. Equivalent to ``complex(0.0, float('inf'))``. + part. Equivalent to ``float('inf')*1j``. .. versionadded:: 3.6 @@ -295,7 +295,7 @@ Constants .. data:: nanj Complex number with zero real part and NaN imaginary part. Equivalent to - ``complex(0.0, float('nan'))``. + ``float('nan')*1j``. .. versionadded:: 3.6 diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index a7549b9bce76e2..03e9c86b51795c 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -33,10 +33,11 @@ are always available. They are listed here in alphabetical order. | | :func:`complex` | | | | **P** | | **V** | | | | | **I** | | :func:`pow` | | :func:`vars` | | | **D** | | :func:`id` | | :func:`print` | | | -| | :func:`delattr` | | :func:`input` | | :func:`property` | | **Z** | -| | |func-dict|_ | | :func:`int` | | | | :func:`zip` | -| | :func:`dir` | | :func:`isinstance` | | | | | -| | :func:`divmod` | | :func:`issubclass` | | | | **_** | +| | :func:`delattr` | | :func:`imaginary` | | :func:`property` | | | +| | |func-dict|_ | | :func:`input` | | | | **Z** | +| | :func:`dir` | | :func:`int` | | | | :func:`zip` | +| | :func:`divmod` | | :func:`isinstance` | | | | | +| | | | :func:`issubclass` | | | | **_** | | | | | :func:`iter` | | | | :func:`__import__` | +-------------------------+-----------------------+-----------------------+-------------------------+ @@ -388,7 +389,7 @@ are always available. They are listed here in alphabetical order. >>> complex('+1.23') (1.23+0j) >>> complex('-4.5j') - -4.5j + (0.0-4.5j) >>> complex('-1.23+4.5j') (-1.23+4.5j) >>> complex('\t( -1.23+4.5J )\n') @@ -398,7 +399,7 @@ are always available. They are listed here in alphabetical order. >>> complex(1.23) (1.23+0j) >>> complex(imag=-4.5) - -4.5j + (0.0-4.5j) >>> complex(-1.23, 4.5) (-1.23+4.5j) @@ -442,7 +443,7 @@ are always available. They are listed here in alphabetical order. See also :meth:`complex.from_number` which only accepts a single numeric argument. - If all arguments are omitted, returns ``0j``. + If all arguments are omitted, returns ``0.0+0j``. The complex type is described in :ref:`typesnumeric`. @@ -970,6 +971,14 @@ are always available. They are listed here in alphabetical order. .. audit-event:: builtins.id id id +.. class:: imaginary(x=0.0) + + Return an imaginary number with the value ``float(x)*1j``. If argument is + omitted, returns ``0j``. + + .. versionadded:: next + + .. function:: input() input(prompt) diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst index f7167032ad7df9..6279a6c8a85e8b 100644 --- a/Doc/reference/lexical_analysis.rst +++ b/Doc/reference/lexical_analysis.rst @@ -979,11 +979,12 @@ Imaginary literals are described by the following lexical definitions: .. productionlist:: python-grammar imagnumber: (`floatnumber` | `digitpart`) ("j" | "J") -An imaginary literal yields a complex number with a real part of 0.0. Complex -numbers are represented as a pair of floating-point numbers and have the same -restrictions on their range. To create a complex number with a nonzero real -part, add a floating-point number to it, e.g., ``(3+4j)``. Some examples of -imaginary literals:: +An imaginary literal yields a complex number without a real part, an instance +of :class:`imaginary`. Complex numbers are represented as a pair of +floating-point numbers and have the same restrictions on their range. To +create a complex number with a nonzero real part, add an integer or +floating-point number to imaginary, e.g., ``3+4j``. Some examples of imaginary +literals:: 3.14j 10.j 10j .001j 1e100j 3.14e-10j 3.14_15_93j diff --git a/Include/complexobject.h b/Include/complexobject.h index ebe49a832f7414..2ff40ecfe52128 100644 --- a/Include/complexobject.h +++ b/Include/complexobject.h @@ -9,15 +9,21 @@ extern "C" { /* Complex object interface */ PyAPI_DATA(PyTypeObject) PyComplex_Type; +PyAPI_DATA(PyTypeObject) PyImaginary_Type; #define PyComplex_Check(op) PyObject_TypeCheck((op), &PyComplex_Type) #define PyComplex_CheckExact(op) Py_IS_TYPE((op), &PyComplex_Type) +#define PyImaginary_Check(op) PyObject_TypeCheck((op), &PyImaginary_Type) +#define PyImaginary_CheckExact(op) Py_IS_TYPE((op), &PyImaginary_Type) + PyAPI_FUNC(PyObject *) PyComplex_FromDoubles(double real, double imag); PyAPI_FUNC(double) PyComplex_RealAsDouble(PyObject *op); PyAPI_FUNC(double) PyComplex_ImagAsDouble(PyObject *op); +PyAPI_FUNC(PyObject *) PyImaginary_FromDouble(double imag); + #ifndef Py_LIMITED_API # define Py_CPYTHON_COMPLEXOBJECT_H # include "cpython/complexobject.h" diff --git a/Include/cpython/complexobject.h b/Include/cpython/complexobject.h index 28576afad0b6b5..492bd8dfa7c165 100644 --- a/Include/cpython/complexobject.h +++ b/Include/cpython/complexobject.h @@ -10,15 +10,21 @@ 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_ci_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_ci_diff(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_ic_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_ci_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_ci_quot(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_ic_quot(double, Py_complex); PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex); PyAPI_FUNC(double) _Py_c_abs(Py_complex); diff --git a/Lib/ast.py b/Lib/ast.py index 0937c27bdf8a11..2bcc48325344fa 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -73,7 +73,7 @@ def _raise_malformed_node(node): msg += f' on line {lno}' raise ValueError(msg + f': {node!r}') def _convert_num(node): - if not isinstance(node, Constant) or type(node.value) not in (int, float, complex): + if not isinstance(node, Constant) or type(node.value) not in (int, float, imaginary, complex): _raise_malformed_node(node) return node.value def _convert_signed_num(node): diff --git a/Lib/copy.py b/Lib/copy.py index c64fc0761793f5..39045bd40e3d53 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -100,7 +100,7 @@ def copy(x): return _reconstruct(x, None, *rv) -_copy_atomic_types = {types.NoneType, int, float, bool, complex, str, tuple, +_copy_atomic_types = {types.NoneType, int, float, bool, complex, imaginary, str, tuple, bytes, frozenset, type, range, slice, property, types.BuiltinFunctionType, types.EllipsisType, types.NotImplementedType, types.FunctionType, types.CodeType, @@ -164,7 +164,8 @@ def deepcopy(x, memo=None, _nil=[]): _atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType, int, float, bool, complex, bytes, str, types.CodeType, type, range, - types.BuiltinFunctionType, types.FunctionType, weakref.ref, property} + types.BuiltinFunctionType, types.FunctionType, weakref.ref, + property, imaginary} _deepcopy_dispatch = d = {} diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 97e0eb3f043080..3fa59f109f4ee5 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -23,6 +23,9 @@ class BadComplex3: def __complex__(self): raise RuntimeError +class ImaginarySubclass(imaginary): + pass + class CAPIComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase): def test_check(self): @@ -176,7 +179,14 @@ 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], + self.assertComplexesAreIdentical(_py_cr_sum(-0.0 - 0j, -0.0)[0], + complex(-0.0, -0.0)) + + def test_py_ci_sum(self): + # Test _Py_cr_sum() + _py_ci_sum = _testcapi._py_ci_sum + + self.assertComplexesAreIdentical(_py_ci_sum(-0.0 - 0j, -0.0)[0], complex(-0.0, -0.0)) def test_py_c_diff(self): @@ -189,7 +199,14 @@ 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], + self.assertComplexesAreIdentical(_py_cr_diff(-0.0 - 0j, 0.0)[0], + complex(-0.0, -0.0)) + + def test_py_ci_diff(self): + # Test _Py_ci_diff() + _py_ci_diff = _testcapi._py_ci_diff + + self.assertComplexesAreIdentical(_py_ci_diff(-0.0 - 0j, 0.0)[0], complex(-0.0, -0.0)) def test_py_rc_diff(self): @@ -199,6 +216,13 @@ def test_py_rc_diff(self): self.assertComplexesAreIdentical(_py_rc_diff(-0.0, 0j)[0], complex(-0.0, -0.0)) + def test_py_ic_diff(self): + # Test _Py_ic_diff() + _py_ic_diff = _testcapi._py_ic_diff + + self.assertComplexesAreIdentical(_py_ic_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 @@ -218,6 +242,13 @@ def test_py_cr_prod(self): self.assertComplexesAreIdentical(_py_cr_prod(complex('inf+1j'), INF)[0], complex('inf+infj')) + def test_py_ci_prod(self): + # Test _Py_ci_prod() + _py_ci_prod = _testcapi._py_ci_prod + + self.assertComplexesAreIdentical(_py_ci_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 @@ -247,6 +278,13 @@ def test_py_cr_quot(self): self.assertComplexesAreIdentical(_py_cr_quot(complex('inf+1j'), 2**1000)[0], INF + 2**-1000*1j) + def test_py_ci_quot(self): + # Test _Py_ci_quot() + _py_ci_quot = _testcapi._py_ci_quot + + self.assertComplexesAreIdentical(_py_ci_quot(complex('1+infj'), 2**1000)[0], + INF - 2**-1000*1j) + def test_py_rc_quot(self): # Test _Py_rc_quot() _py_rc_quot = _testcapi._py_rc_quot @@ -254,6 +292,13 @@ def test_py_rc_quot(self): self.assertComplexesAreIdentical(_py_rc_quot(1.0, complex('nan-infj'))[0], 0j) + def test_py_ic_quot(self): + # Test _Py_ic_quot() + _py_ic_quot = _testcapi._py_ic_quot + + self.assertComplexesAreIdentical(_py_ic_quot(1.0, complex('inf-nanj'))[0], + -0.0) + def test_py_c_pow(self): # Test _Py_c_pow() _py_c_pow = _testcapi._py_c_pow @@ -275,7 +320,6 @@ def test_py_c_pow(self): self.assertEqual(_py_c_pow(max_num, 2), (complex(INF, INF), errno.ERANGE)) - def test_py_c_abs(self): # Test _Py_c_abs() _py_c_abs = _testcapi._py_c_abs @@ -294,5 +338,41 @@ def test_py_c_abs(self): self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) +class CAPIImaginaryTest(unittest.TestCase): + def test_check(self): + # Test PyImaginary_Check() + check = _testlimitedcapi.imaginary_check + + self.assertTrue(check(2j)) + self.assertTrue(check(ImaginarySubclass(2))) + self.assertFalse(check(ComplexSubclass(1+2j))) + self.assertFalse(check(Complex())) + self.assertFalse(check(3)) + self.assertFalse(check(3.0)) + self.assertFalse(check(object())) + + # CRASHES check(NULL) + + def test_checkexact(self): + # PyImaginary_CheckExact() + checkexact = _testlimitedcapi.imaginary_checkexact + + self.assertTrue(checkexact(2j)) + self.assertFalse(checkexact(ImaginarySubclass(2))) + self.assertFalse(checkexact(ComplexSubclass(1+2j))) + self.assertFalse(checkexact(Complex())) + self.assertFalse(checkexact(3)) + self.assertFalse(checkexact(3.0)) + self.assertFalse(checkexact(object())) + + # CRASHES checkexact(NULL) + + def test_fromdouble(self): + # Test PyImaginary_FromDouble() + fromdouble = _testlimitedcapi.imaginary_fromdouble + + self.assertEqual(fromdouble(2.0), 2.0j) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index fd002fb00ac338..5f2f3da6b23f9b 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -37,6 +37,9 @@ def __float__(self): class ComplexSubclass(complex): pass +class ImaginarySubclass(imaginary): + pass + class OtherComplexSubclass(complex): pass @@ -193,6 +196,26 @@ def test_truediv(self): self.assertComplexesAreIdentical(float(INF)/complex(NAN, INF), complex(NAN, NAN)) + self.assertEqual((1+1j)/float(2), 0.5+0.5j) + self.assertRaises(TypeError, operator.truediv, None, 1+1j) + self.assertRaises(TypeError, operator.truediv, 1+1j, None) + + self.assertEqual(1j/imaginary(2), 0.5) + self.assertEqual((1+1j)/imaginary(2), 0.5-0.5j) + self.assertEqual(1j/complex(1, 1), 0.5+0.5j) + self.assertEqual(1j/float(2), 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) + + z = float(1)/(NAN+2j) + self.assertTrue(isnan(z.real)) + self.assertTrue(isnan(z.imag)) + + self.assertRaises(ZeroDivisionError, operator.truediv, 1j, 0j) + self.assertRaises(ZeroDivisionError, operator.truediv, 1j, complex(0, 0)) + self.assertRaises(ZeroDivisionError, operator.truediv, 1j, 0.0) + def test_truediv_zero_division(self): for a, b in ZERO_DIVISION: with self.assertRaises(ZeroDivisionError): @@ -267,10 +290,28 @@ def test_add(self): complex(-0.0, -0.0)) self.assertComplexesAreIdentical((-0.0) + complex(-0.0, -0.0), complex(-0.0, -0.0)) + self.assertEqual(1j + imaginary(1), imaginary(2)) self.assertRaises(OverflowError, operator.add, 1j, 10**1000) self.assertRaises(TypeError, operator.add, 1j, None) + self.assertRaises(TypeError, operator.add, 1+1j, None) self.assertRaises(TypeError, operator.add, None, 1j) + self.assertComplexesAreIdentical(float(0.0) + 0j, complex(0, 0)) + self.assertComplexesAreIdentical(0j + float(0.0), complex(0, 0)) + self.assertComplexesAreIdentical(float(-0.0) + 0j, complex(-0.0, 0)) + self.assertComplexesAreIdentical(0j + float(-0.0), complex(-0.0, 0)) + self.assertComplexesAreIdentical((-0.0+0j) + float(0.0), complex(0, 0)) + self.assertComplexesAreIdentical(float(0.0) + (-0.0+0j), complex(0, 0)) + self.assertComplexesAreIdentical((1+0j) + complex(1-0j), complex(2, 0)) + self.assertComplexesAreIdentical(0j + complex(-0.0-0j), complex(-0.0, 0)) + self.assertComplexesAreIdentical(0j + complex(-0j), complex(0, 0)) + self.assertComplexesAreIdentical((1+0j) + complex(-0.0-0j), complex(1, 0)) + self.assertComplexesAreIdentical(complex(-0.0+0j) + (-0j), complex(-0.0, 0)) + self.assertComplexesAreIdentical((1+0j) + float(1.0), complex(2, 0)) + self.assertComplexesAreIdentical(float(1.0) + (1+0j), complex(2, 0)) + self.assertComplexesAreIdentical((1-0j) + float(1.0), complex(2, -0.0)) + self.assertComplexesAreIdentical(float(1.0) + (1-0j), complex(2, -0.0)) + def test_sub(self): self.assertEqual(1j - int(+1), complex(-1, 1)) self.assertEqual(1j - int(-1), complex(1, 1)) @@ -282,13 +323,34 @@ def test_sub(self): complex(-1, 1)) self.assertComplexesAreIdentical(complex(2, 1) - complex(1, 2), complex(1, -1)) + self.assertEqual(1j - imaginary(2), imaginary(-1)) self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) self.assertRaises(TypeError, operator.sub, 1j, None) + self.assertRaises(TypeError, operator.sub, 1+1j, None) + self.assertRaises(TypeError, operator.sub, None, 1+1j) self.assertRaises(TypeError, operator.sub, None, 1j) + self.assertRaises(TypeError, operator.sub, 1j, None) + + self.assertComplexesAreIdentical(float(0.0) - 0j, complex(0, -0.0)) + self.assertComplexesAreIdentical(0j - float(0.0), complex(-0.0, 0)) + self.assertComplexesAreIdentical(float(-0.0) - 0j, complex(-0.0, -0.0)) + self.assertComplexesAreIdentical(0j - float(-0.0), complex(0, 0)) + self.assertComplexesAreIdentical((-0.0+0j) - float(0.0), complex(-0.0, 0)) + self.assertComplexesAreIdentical(float(0.0) - (-0.0+0j), complex(0, -0.0)) + self.assertComplexesAreIdentical((1+0j) - complex(1-0j), complex(0, 0)) + self.assertComplexesAreIdentical(0j - complex(-0.0-0j), complex(0, 0)) + self.assertComplexesAreIdentical(0j - complex(-0j), complex(-0.0, 0)) + self.assertComplexesAreIdentical((1+0j) - complex(-0.0-0j), complex(1, 0)) + self.assertComplexesAreIdentical(complex(-0.0+0j) - (-0j), complex(-0.0, 0)) + self.assertComplexesAreIdentical((1+0j) - float(1.0), complex(0, 0)) + self.assertComplexesAreIdentical(float(1.0) - (1+0j), complex(0, -0.0)) + self.assertComplexesAreIdentical((1-0j) - float(1.0), complex(0, -0.0)) + self.assertComplexesAreIdentical(float(1.0) - (1-0j), complex(0, 0)) def test_mul(self): self.assertEqual(1j * int(20), complex(0, 20)) self.assertEqual(1j * int(-1), complex(0, -1)) + self.assertEqual(2j * imaginary(3), -6.0) 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))]: @@ -297,8 +359,27 @@ def test_mul(self): 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, 1+1j, None) self.assertRaises(TypeError, operator.mul, None, 1j) + self.assertComplexesAreIdentical(float(0.0) * 0j, complex(0, 0)) + self.assertComplexesAreIdentical(0j * float(0.0), complex(0.0, 0)) + self.assertComplexesAreIdentical(float(-0.0) * 0j, complex(0.0, -0.0)) + self.assertComplexesAreIdentical(0j * float(-0.0), complex(0, -0.0)) + self.assertComplexesAreIdentical((-0.0+0j) * float(0.0), complex(-0.0, 0)) + self.assertComplexesAreIdentical(float(0.0) * (-0.0+0j), complex(-0.0, 0)) + self.assertComplexesAreIdentical((-0.0+0j) * float(-0.0), complex(0, -0.0)) + self.assertComplexesAreIdentical(float(-0.0) * (-0.0+0j), complex(0, -0.0)) + self.assertComplexesAreIdentical((-0.0-0j) * float(-0.0), complex(0, 0)) + self.assertComplexesAreIdentical(float(-0.0) * (-0.0-0j), complex(0, 0)) + self.assertComplexesAreIdentical((1+0j) * complex(1-0j), complex(1, 0)) + self.assertComplexesAreIdentical(0j * complex(-0.0-0j), complex(0.0, -0.0)) + self.assertComplexesAreIdentical(0j * complex(-0j), complex(0, 0)) + self.assertComplexesAreIdentical((1+0j) * complex(-0.0-0j), complex(0, -0.0)) + self.assertComplexesAreIdentical((-0.0+0j) * complex(-0j), complex(0, 0)) + self.assertComplexesAreIdentical((1+0j) * float(1.0), complex(1, 0)) + self.assertComplexesAreIdentical(float(1.0) * (1+0j), complex(1, 0)) + for z, w, r in [(1e300+1j, complex(INF, INF), complex(NAN, INF)), (1e300+1j, complex(NAN, INF), complex(-INF, INF)), (1e300+1j, complex(INF, NAN), complex(INF, INF)), @@ -453,6 +534,7 @@ def test_boolcontext(self): def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) + self.assertEqual(1j.conjugate(), -1j) def test_constructor(self): def check(z, x, y): @@ -486,10 +568,10 @@ def check(z, x, y): "argument 'real' must be a real number, not .*WithComplex"): check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0) with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not complex"): + "argument 'real' must be a real number, not imaginary"): check(complex(4.25j, 0), 0.0, 4.25) with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not complex"): + "argument 'real' must be a real number, not imaginary"): check(complex(0j, 4.25), 0.0, 4.25) with self.assertWarnsRegex(DeprecationWarning, "argument 'imag' must be a real number, not complex"): @@ -501,19 +583,19 @@ def check(z, x, y): "argument 'imag' must be a real number, not .*WithComplex"): complex(0, WithComplex(4.25+0j)) with self.assertWarnsRegex(DeprecationWarning, - "argument 'imag' must be a real number, not complex"): + "argument 'imag' must be a real number, not imaginary"): check(complex(0.0, 4.25j), -4.25, 0.0) with self.assertWarnsRegex(DeprecationWarning, "argument 'real' must be a real number, not complex"): check(complex(4.25+0j, 0j), 4.25, 0.0) with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not complex"): + "argument 'real' must be a real number, not imaginary"): check(complex(4.25j, 0j), 0.0, 4.25) with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not complex"): + "argument 'real' must be a real number, not imaginary"): check(complex(0j, 4.25+0j), 0.0, 4.25) with self.assertWarnsRegex(DeprecationWarning, - "argument 'real' must be a real number, not complex"): + "argument 'real' must be a real number, not imaginary"): check(complex(0j, 4.25j), -4.25, 0.0) check(complex(real=4.25), 4.25, 0.0) @@ -597,18 +679,14 @@ def __complex__(self): self.assertRaises(TypeError, complex, WithIndex(None), 1.5) self.assertRaises(TypeError, complex, 1.5, WithIndex(None)) - class MyInt: - def __int__(self): - return 42 - - self.assertRaises(TypeError, complex, MyInt()) - self.assertRaises(TypeError, complex, MyInt(), 1.5) - self.assertRaises(TypeError, complex, 1.5, MyInt()) + self.assertRaises(TypeError, complex, MyInt(42)) + self.assertRaises(TypeError, complex, MyInt(42), 1.5) + self.assertRaises(TypeError, complex, 1.5, MyInt(42)) class complex0(complex): """Test usage of __complex__() when inheriting from 'complex'""" def __complex__(self): - return 42j + return 1+42j class complex1(complex): """Test usage of __complex__() with a __new__() method""" @@ -623,11 +701,30 @@ class complex2(complex): def __complex__(self): return None - check(complex(complex0(1j)), 0.0, 42.0) + check(complex(complex0(1j)), 1.0, 42.0) with self.assertWarns(DeprecationWarning): check(complex(complex1(1j)), 0.0, 2.0) self.assertRaises(TypeError, complex, complex2(1j)) + def test_imaginary_constructor(self): + self.assertEqual(imaginary(), 0j) + self.assertEqual(imaginary(-2), -2j) + self.assertEqual(imaginary(1.25), 1.25j) + self.assertEqual(imaginary("1.25"), 1.25j) + + self.assertEqual(imaginary(WithFloat(42.)), 42j) + self.assertRaises(TypeError, imaginary, WithFloat(None)) + + self.assertEqual(imaginary(WithIndex(42)), 42j) + self.assertRaises(OverflowError, imaginary, WithIndex(2**2000)) + + self.assertRaises(TypeError, imaginary, MyInt(42)) + self.assertRaises(TypeError, imaginary, 123, MyInt(42)) + self.assertRaises(ValueError, imaginary, "1.25j") + + self.assertRaises(TypeError, imaginary, complex()) + self.assertRaises(TypeError, imaginary, object()) + def test___complex__(self): z = 3 + 4j self.assertEqual(z.__complex__(), z) @@ -801,9 +898,13 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(NAN, NAN), "(nan+nanj)") test(complex(-NAN, -NAN), "(nan+nanj)") - test(complex(0, INF), "infj") - test(complex(0, -INF), "-infj") - test(complex(0, NAN), "nanj") + test(complex(0, INF), "(0.0+infj)") + test(complex(0, -INF), "(0.0-infj)") + test(complex(0, NAN), "(0.0+nanj)") + + test(imaginary(INF), "infj") + test(imaginary(-INF), "-infj") + test(imaginary(NAN), "nanj") self.assertEqual(1-6j,complex(repr(1-6j))) self.assertEqual(1+6j,complex(repr(1+6j))) @@ -816,29 +917,43 @@ def test(v, expected, test_fn=self.assertEqual): test_fn(repr(v), expected) test_fn(str(v), expected) - test(complex(0., 1.), "1j") - test(complex(-0., 1.), "(-0+1j)") - test(complex(0., -1.), "-1j") - test(complex(-0., -1.), "(-0-1j)") + test(complex(0., 1.), "(0.0+1j)") + test(complex(-0., 1.), "(-0.0+1j)") + test(complex(0., -1.), "(0.0-1j)") + test(complex(-0., -1.), "(-0.0-1j)") - test(complex(0., 0.), "0j") - test(complex(0., -0.), "-0j") - test(complex(-0., 0.), "(-0+0j)") - test(complex(-0., -0.), "(-0-0j)") + test(imaginary(+1.), "1j") + test(imaginary(-1.), "-1j") + + test(complex(0., 0.), "(0.0+0j)") + test(complex(0., -0.), "(0.0-0j)") + test(complex(-0., 0.), "(-0.0+0j)") + test(complex(-0., -0.), "(-0.0-0j)") + + test(imaginary(+0.0), "0j") + test(imaginary(-0.0), "-0j") def test_pos(self): self.assertEqual(+(1+6j), 1+6j) self.assertEqual(+ComplexSubclass(1, 6), 1+6j) self.assertIs(type(+ComplexSubclass(1, 6)), complex) + self.assertEqual(+1j, 1j) + self.assertEqual(+ImaginarySubclass(1), 1j) + self.assertIs(type(+ImaginarySubclass(1)), imaginary) + def test_neg(self): self.assertEqual(-(1+6j), -1-6j) + self.assertComplexesAreIdentical(-0j, complex(0, -0.0)) + self.assertComplexesAreIdentical(-complex(-0.0+0j), complex(0, -0.0)) def test_getnewargs(self): self.assertEqual((1+2j).__getnewargs__(), (1.0, 2.0)) self.assertEqual((1-2j).__getnewargs__(), (1.0, -2.0)) - self.assertEqual((2j).__getnewargs__(), (0.0, 2.0)) - self.assertEqual((-0j).__getnewargs__(), (0.0, -0.0)) + self.assertEqual((0.0+2j).__getnewargs__(), (0.0, 2.0)) + self.assertEqual((2j).__getnewargs__(), (2.0,)) + self.assertEqual((0.0-0j).__getnewargs__(), (0.0, -0.0)) + self.assertEqual((-0j).__getnewargs__(), (-0.0,)) self.assertEqual(complex(0, INF).__getnewargs__(), (0.0, INF)) self.assertEqual(complex(INF, 0).__getnewargs__(), (INF, 0.0)) @@ -854,15 +969,11 @@ def test_negated_imaginary_literal(self): z0 = -0j z1 = -7j z2 = -1e1000j - # Note: In versions of Python < 3.2, a negated imaginary literal - # accidentally ended up with real part 0.0 instead of -0.0, thanks to a - # modification during CST -> AST translation (see issue #9011). That's - # fixed in Python 3.2. - self.assertFloatsAreIdentical(z0.real, -0.0) + self.assertFloatsAreIdentical(z0.real, +0.0) self.assertFloatsAreIdentical(z0.imag, -0.0) - self.assertFloatsAreIdentical(z1.real, -0.0) + self.assertFloatsAreIdentical(z1.real, +0.0) self.assertFloatsAreIdentical(z1.imag, -7.0) - self.assertFloatsAreIdentical(z2.real, -0.0) + self.assertFloatsAreIdentical(z2.real, +0.0) self.assertFloatsAreIdentical(z2.imag, -INF) @support.requires_IEEE_754 @@ -928,7 +1039,8 @@ def test_format(self): self.assertEqual(format(z, '3'), str(z)) self.assertEqual(format(1+3j, 'g'), '1+3j') - self.assertEqual(format(3j, 'g'), '0+3j') + self.assertEqual(format(0+3j, 'g'), '0.0+3j') + self.assertEqual(format(3j, 'g'), '3j') self.assertEqual(format(1.5+3.5j, 'g'), '1.5+3.5j') self.assertEqual(format(1.5+3.5j, '+g'), '+1.5+3.5j') diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 9dde63e40d06db..7d65dcd3e5cbe4 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -595,10 +595,15 @@ def test_negative_zero(self): self.assertEqual(f"{-1.:+z.0f}", "-1") self.assertEqual(f"{-1.:-z.0f}", "-1") - self.assertEqual(f"{0.j:z.1f}", "0.0+0.0j") - self.assertEqual(f"{-0.j:z.1f}", "0.0+0.0j") - self.assertEqual(f"{.01j:z.1f}", "0.0+0.0j") - self.assertEqual(f"{-.01j:z.1f}", "0.0+0.0j") + self.assertEqual(f"{0.0+0.j:z.1f}", "0.0+0.0j") + self.assertEqual(f"{0.0-0.j:z.1f}", "0.0+0.0j") + self.assertEqual(f"{0.0+.01j:z.1f}", "0.0+0.0j") + self.assertEqual(f"{0.0-.01j:z.1f}", "0.0+0.0j") + + self.assertEqual(f"{0.j:z.1f}", "0.0j") + self.assertEqual(f"{-0.j:z.1f}", "0.0j") + self.assertEqual(f"{.01j:z.1f}", "0.0j") + self.assertEqual(f"{-.01j:z.1f}", "0.0j") self.assertEqual(f"{-0.:z>6.1f}", "zz-0.0") # test fill, esp. 'z' fill self.assertEqual(f"{-0.:z>z6.1f}", "zzz0.0") diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 98dccbec9566ac..dfbc289070adc7 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1679,7 +1679,7 @@ def test_complex_handling(self): # See issue gh-102840 for more details. a = F(1, 2) - b = 1j + b = 0.0+1j message = "unsupported operand type(s) for %s: '%s' and '%s'" # test forward self.assertRaisesMessage(TypeError, diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 4ed9f1fc1b8020..c6c88c18ccfde1 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -318,7 +318,8 @@ def test_exact_type_match(self): # >>> class Int(int): pass # >>> type(loads(dumps(Int()))) # - for typ in (int, float, complex, tuple, list, dict, set, frozenset): + for typ in (int, float, complex, tuple, list, dict, set, frozenset, + imaginary): # Note: str subclasses are not tested because they get handled # by marshal's routines for objects supporting the buffer API. subtyp = type('subtyp', (typ,), {}) @@ -372,7 +373,7 @@ def readinto(self, buf): if n is not None and n > 4: n += 10**6 return n - for value in (1.0, 1j, b'0123456789', '0123456789'): + for value in (1.0, 1j, 1+1j, b'0123456789', '0123456789'): self.assertRaises(ValueError, marshal.load, BadReader(marshal.dumps(value))) @@ -628,7 +629,7 @@ def test_write_long_to_file(self): self.assertEqual(data, b'\x78\x56\x34\x12') def test_write_object_to_file(self): - obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 'long line '*1000) + obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 122j, 'long line '*1000) for v in range(marshal.version + 1): _testcapi.pymarshal_write_object_to_file(obj, os_helper.TESTFN, v) with open(os_helper.TESTFN, 'rb') as f: @@ -665,7 +666,7 @@ def test_read_long_from_file(self): os_helper.unlink(os_helper.TESTFN) def test_read_last_object_from_file(self): - obj = ('\u20ac', b'abc', 123, 45.6, 7+8j) + obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 666j) for v in range(marshal.version + 1): data = marshal.dumps(obj, v) with open(os_helper.TESTFN, 'wb') as f: @@ -681,7 +682,7 @@ def test_read_last_object_from_file(self): os_helper.unlink(os_helper.TESTFN) def test_read_object_from_file(self): - obj = ('\u20ac', b'abc', 123, 45.6, 7+8j) + obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 1j) for v in range(marshal.version + 1): data = marshal.dumps(obj, v) with open(os_helper.TESTFN, 'wb') as f: diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index b77fa3cb21512a..492d15a265114f 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -945,7 +945,7 @@ def testSendtoErrors(self): self.assertEqual(str(cm.exception), "a bytes-like object is required, not 'str'") with self.assertRaises(TypeError) as cm: - s.sendto(5j, sockname) + s.sendto(1+5j, sockname) self.assertEqual(str(cm.exception), "a bytes-like object is required, not 'complex'") with self.assertRaises(TypeError) as cm: @@ -957,7 +957,7 @@ def testSendtoErrors(self): self.assertEqual(str(cm.exception), "a bytes-like object is required, not 'str'") with self.assertRaises(TypeError) as cm: - s.sendto(5j, 0, sockname) + s.sendto(1+5j, 0, sockname) self.assertEqual(str(cm.exception), "a bytes-like object is required, not 'complex'") with self.assertRaises(TypeError) as cm: diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index f3724ce6d4d15a..7aa4bb68f17c2e 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -341,6 +341,8 @@ def test_windows_feature_macros(self): "PyGILState_GetThisThreadState", "PyGILState_Release", "PyGetSetDescr_Type", + "PyImaginary_FromDouble", + "PyImaginary_Type", "PyImport_AddModule", "PyImport_AddModuleObject", "PyImport_AddModuleRef", diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index d1c9542c7d1317..95a80608310cb7 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -1573,12 +1573,12 @@ def __int__(self): self.assertRaisesRegex(TypeError, '%X format: an integer is required, not float', operator.mod, '%X', 2.11) self.assertRaisesRegex(TypeError, '%o format: an integer is required, not float', operator.mod, '%o', 1.79) self.assertRaisesRegex(TypeError, '%x format: an integer is required, not PseudoFloat', operator.mod, '%x', pi) - self.assertRaisesRegex(TypeError, '%x format: an integer is required, not complex', operator.mod, '%x', 3j) - self.assertRaisesRegex(TypeError, '%X format: an integer is required, not complex', operator.mod, '%X', 2j) - self.assertRaisesRegex(TypeError, '%o format: an integer is required, not complex', operator.mod, '%o', 1j) - self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j) - self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j) - self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1j) + self.assertRaisesRegex(TypeError, '%x format: an integer is required, not complex', operator.mod, '%x', 1+3j) + self.assertRaisesRegex(TypeError, '%X format: an integer is required, not complex', operator.mod, '%X', 1+2j) + self.assertRaisesRegex(TypeError, '%o format: an integer is required, not complex', operator.mod, '%o', 1+1j) + self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 1+3j) + self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 1+2j) + self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1+1j) self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi) class RaisingNumber: diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 9317be605f0065..ad6d2ff8803021 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2507,6 +2507,11 @@ [function.PyEval_GetFrameLocals] added = '3.13' +[function.PyImaginary_FromDouble] + added = '3.14' +[data.PyImaginary_Type] + added = '3.14' + [function.Py_TYPE] added = '3.14' [function.Py_REFCNT] diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index b726cd3236f179..0fa1e065ea106a 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -57,48 +57,54 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) 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_CX_FUNC2(suffix, prefix) \ + static PyObject * \ + _py_##prefix##_##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_##prefix##_##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); \ +#define _PY_XC_FUNC2(suffix, prefix) \ + static PyObject * \ + _py_##prefix##_##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_##prefix##_##suffix(a, b); \ + return Py_BuildValue("Di", &res, errno); \ }; _PY_C_FUNC2(sum) -_PY_CR_FUNC2(sum) +_PY_CX_FUNC2(sum, cr) +_PY_CX_FUNC2(sum, ci) _PY_C_FUNC2(diff) -_PY_CR_FUNC2(diff) -_PY_RC_FUNC2(diff) +_PY_CX_FUNC2(diff, cr) +_PY_CX_FUNC2(diff, ci) +_PY_XC_FUNC2(diff, rc) +_PY_XC_FUNC2(diff, ic) _PY_C_FUNC2(prod) -_PY_CR_FUNC2(prod) +_PY_CX_FUNC2(prod, cr) +_PY_CX_FUNC2(prod, ci) _PY_C_FUNC2(quot) -_PY_CR_FUNC2(quot) -_PY_RC_FUNC2(quot) +_PY_CX_FUNC2(quot, cr) +_PY_CX_FUNC2(quot, ci) +_PY_XC_FUNC2(quot, rc) +_PY_XC_FUNC2(quot, ic) _PY_C_FUNC2(pow) static PyObject* @@ -125,15 +131,21 @@ static PyMethodDef test_methods[] = { {"complex_asccomplex", complex_asccomplex, METH_O}, {"_py_c_sum", _py_c_sum, METH_VARARGS}, {"_py_cr_sum", _py_cr_sum, METH_VARARGS}, + {"_py_ci_sum", _py_ci_sum, METH_VARARGS}, {"_py_c_diff", _py_c_diff, METH_VARARGS}, {"_py_cr_diff", _py_cr_diff, METH_VARARGS}, + {"_py_ci_diff", _py_ci_diff, METH_VARARGS}, {"_py_rc_diff", _py_rc_diff, METH_VARARGS}, + {"_py_ic_diff", _py_ic_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_ci_prod", _py_ci_prod, METH_VARARGS}, {"_py_c_quot", _py_c_quot, METH_VARARGS}, {"_py_cr_quot", _py_cr_quot, METH_VARARGS}, + {"_py_ci_quot", _py_ci_quot, METH_VARARGS}, {"_py_rc_quot", _py_rc_quot, METH_VARARGS}, + {"_py_ic_quot", _py_ic_quot, METH_VARARGS}, {"_py_c_pow", _py_c_pow, METH_VARARGS}, {"_py_c_abs", _py_c_abs, METH_O}, {NULL}, diff --git a/Modules/_testlimitedcapi/complex.c b/Modules/_testlimitedcapi/complex.c index e4c244e5c88d06..a71502ad9959b2 100644 --- a/Modules/_testlimitedcapi/complex.c +++ b/Modules/_testlimitedcapi/complex.c @@ -16,6 +16,20 @@ complex_checkexact(PyObject *Py_UNUSED(module), PyObject *obj) return PyLong_FromLong(PyComplex_CheckExact(obj)); } +static PyObject * +imaginary_check(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyImaginary_Check(obj)); +} + +static PyObject * +imaginary_checkexact(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyImaginary_CheckExact(obj)); +} + static PyObject * complex_fromdoubles(PyObject *Py_UNUSED(module), PyObject *args) { @@ -28,6 +42,12 @@ complex_fromdoubles(PyObject *Py_UNUSED(module), PyObject *args) return PyComplex_FromDoubles(real, imag); } +static PyObject * +imaginary_fromdouble(PyObject *Py_UNUSED(module), PyObject *obj) +{ + return PyImaginary_FromDouble(PyFloat_AsDouble(obj)); +} + static PyObject * complex_realasdouble(PyObject *Py_UNUSED(module), PyObject *obj) { @@ -65,6 +85,9 @@ static PyMethodDef test_methods[] = { {"complex_fromdoubles", complex_fromdoubles, METH_VARARGS}, {"complex_realasdouble", complex_realasdouble, METH_O}, {"complex_imagasdouble", complex_imagasdouble, METH_O}, + {"imaginary_check", imaginary_check, METH_O}, + {"imaginary_checkexact", imaginary_checkexact, METH_O}, + {"imaginary_fromdouble", imaginary_fromdouble, METH_O}, {NULL}, }; diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index 81cbf0d554de3c..cbd4b8ef451f55 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -1188,15 +1188,13 @@ cmath_exec(PyObject *mod) return -1; } - Py_complex infj = {0.0, Py_INFINITY}; - if (PyModule_Add(mod, "infj", PyComplex_FromCComplex(infj)) < 0) { + if (PyModule_Add(mod, "infj", PyImaginary_FromDouble(Py_INFINITY)) < 0) { return -1; } if (PyModule_Add(mod, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) { return -1; } - Py_complex nanj = {0.0, fabs(Py_NAN)}; - if (PyModule_Add(mod, "nanj", PyComplex_FromCComplex(nanj)) < 0) { + if (PyModule_Add(mod, "nanj", PyImaginary_FromDouble(Py_NAN)) < 0) { return -1; } diff --git a/Objects/clinic/complexobject.c.h b/Objects/clinic/complexobject.c.h index e00da1d960c54d..ac20c9898675d2 100644 --- a/Objects/clinic/complexobject.c.h +++ b/Objects/clinic/complexobject.c.h @@ -170,4 +170,101 @@ PyDoc_STRVAR(complex_from_number__doc__, #define COMPLEX_FROM_NUMBER_METHODDEF \ {"from_number", (PyCFunction)complex_from_number, METH_O|METH_CLASS, complex_from_number__doc__}, -/*[clinic end generated code: output=252cddef7f9169a0 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(imaginary_new__doc__, +"imaginary(x=0)\n" +"--\n" +"\n" +"Create an imaginary number from a real number or string.\n" +"\n" +"This is equivalent of float(x)*1j."); + +static PyObject * +imaginary_new_impl(PyTypeObject *type, PyObject *x); + +static PyObject * +imaginary_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { _Py_LATIN1_CHR('x'), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"x", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "imaginary", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *x = NULL; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + x = fastargs[0]; +skip_optional_pos: + return_value = imaginary_new_impl(type, x); + +exit: + return return_value; +} + +PyDoc_STRVAR(imaginary_conjugate__doc__, +"conjugate($self, /)\n" +"--\n" +"\n" +"Return the complex conjugate of its argument. (-4j).conjugate() == 4j."); + +#define IMAGINARY_CONJUGATE_METHODDEF \ + {"conjugate", (PyCFunction)imaginary_conjugate, METH_NOARGS, imaginary_conjugate__doc__}, + +static PyObject * +imaginary_conjugate_impl(PyComplexObject *self); + +static PyObject * +imaginary_conjugate(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return imaginary_conjugate_impl((PyComplexObject *)self); +} + +PyDoc_STRVAR(imaginary___getnewargs____doc__, +"__getnewargs__($self, /)\n" +"--\n" +"\n"); + +#define IMAGINARY___GETNEWARGS___METHODDEF \ + {"__getnewargs__", (PyCFunction)imaginary___getnewargs__, METH_NOARGS, imaginary___getnewargs____doc__}, + +static PyObject * +imaginary___getnewargs___impl(PyComplexObject *self); + +static PyObject * +imaginary___getnewargs__(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return imaginary___getnewargs___impl((PyComplexObject *)self); +} +/*[clinic end generated code: output=4c1905a0b27e7bd1 input=a9049054013a1b77]*/ diff --git a/Objects/codeobject.c b/Objects/codeobject.c index a7b46aa2dfbbc0..a309f4fcedbdcc 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -2443,6 +2443,15 @@ _PyCode_ConstantKey(PyObject *op) else key = PyTuple_Pack(2, Py_TYPE(op), op); } + else if (PyImaginary_CheckExact(op)) { + double d = ((PyComplexObject *)(op))->cval.imag; + if (d == 0.0 && copysign(1.0, d) < 0.0) { + key = PyTuple_Pack(3, Py_TYPE(op), op, Py_None); + } + else { + key = PyTuple_Pack(2, Py_TYPE(op), op); + } + } else if (PyComplex_CheckExact(op)) { Py_complex z; int real_negzero, imag_negzero; @@ -2668,6 +2677,11 @@ compare_constants(const void *key1, const void *key2) { Py_complex c2 = ((PyComplexObject *)op2)->cval; return memcmp(&c1, &c2, sizeof(Py_complex)) == 0; } + else if (PyImaginary_CheckExact(op1)) { + double i1 = ((PyComplexObject *)op1)->cval.imag; + double i2 = ((PyComplexObject *)op2)->cval.imag; + return memcmp(&i1, &i2, sizeof(double)) == 0; + } _Py_FatalErrorFormat("unexpected type in compare_constants: %s", Py_TYPE(op1)->tp_name); return 0; diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 5d9b3c9f0e3e76..b64b644366d34b 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -19,8 +19,9 @@ /*[clinic input] class complex "PyComplexObject *" "&PyComplex_Type" +class imaginary "PyComplexObject *" "&PyComplex_Type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=819e057d2d10f5ec]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=041bba3f29a299d0]*/ #include "clinic/complexobject.c.h" @@ -51,6 +52,20 @@ _Py_rc_sum(double a, Py_complex b) return _Py_cr_sum(b, a); } +Py_complex +_Py_ci_sum(Py_complex a, double b) +{ + Py_complex r = a; + r.imag += b; + return r; +} + +static inline Py_complex +_Py_ic_sum(double a, Py_complex b) +{ + return _Py_ci_sum(b, a); +} + Py_complex _Py_c_diff(Py_complex a, Py_complex b) { @@ -77,6 +92,23 @@ _Py_rc_diff(double a, Py_complex b) return r; } +Py_complex +_Py_ci_diff(Py_complex a, double b) +{ + Py_complex r = a; + r.imag -= b; + return r; +} + +Py_complex +_Py_ic_diff(double a, Py_complex b) +{ + Py_complex r; + r.real = -b.real; + r.imag = a - b.imag; + return r; +} + Py_complex _Py_c_neg(Py_complex a) { @@ -162,6 +194,21 @@ _Py_rc_prod(double a, Py_complex b) return _Py_cr_prod(b, a); } +Py_complex +_Py_ci_prod(Py_complex a, double b) +{ + Py_complex r; + r.real = -a.imag*b; + r.imag = a.real*b; + return r; +} + +static inline Py_complex +_Py_ic_prod(double a, Py_complex b) +{ + return _Py_ci_prod(b, a); +} + /* Avoid bad optimization on Windows ARM64 until the compiler is fixed */ #ifdef _M_ARM64 #pragma optimize("", off) @@ -302,6 +349,28 @@ _Py_rc_quot(double a, Py_complex b) return r; } + +Py_complex +_Py_ci_quot(Py_complex a, double b) +{ + Py_complex r = a; + if (b) { + r.real = a.imag / b; + r.imag = -a.real / b; + } + else { + errno = EDOM; + r.real = r.imag = 0.0; + } + return r; +} + +Py_complex +_Py_ic_quot(double a, Py_complex b) +{ + Py_complex r = _Py_rc_quot(a, b); + return (Py_complex){-r.imag, r.real}; +} #ifdef _M_ARM64 #pragma optimize("", on) #endif @@ -573,7 +642,7 @@ complex_repr(PyObject *op) const char *lead = ""; const char *tail = ""; - if (v->cval.real == 0. && copysign(1.0, v->cval.real)==1.0) { + if (PyImaginary_Check(v)) { /* Real part is +0: just output the imaginary part and do not include parens. */ re = ""; @@ -587,7 +656,8 @@ complex_repr(PyObject *op) /* Format imaginary part with sign, real part without. Include parens in the result. */ pre = PyOS_double_to_string(v->cval.real, format_code, - precision, 0, NULL); + precision, + v->cval.real? 0 : Py_DTSF_ADD_DOT_0, NULL); if (!pre) { PyErr_NoMemory(); goto done; @@ -663,19 +733,29 @@ real_to_complex(PyObject **pobj, Py_complex *pc) } /* 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. + a pure-real (float or int) value and a complex value or a pure-imaginary + value (instances of the imaginary type, like 1j) and a complex value are + performed directly, without first coercing operands 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) + complex(a, b) + complex(c, d) = complex(a + c, b + d) (1) + float(a) + complex(b, c) = complex(a + b, c) (2) + imaginary(a) + complex(b, c) = complex(b, a + c) (3) + imaginary(a) + imaginary(b) = imaginary(a + b) (4) + float(a) + imaginary(b) = complex(a, b) (5) Similar rules are implemented for subtraction, multiplication and division. See C11's Annex G, sections G.5.1 and G.5.2. - */ + + Note, that the imaginary type implemented as a subtype of the complex + type. So, complex_op() functions below must implement only one type of + mixed-mode operations, i.e. when one argument is a float (2). The rest + (respectively: complex op imaginary (3), imaginary op imaginary (4) and + imaginary op float (5)) should be implemented by an appropriate + imaginary_op() function. */ #define COMPLEX_BINOP(NAME, FUNC) \ static PyObject * \ @@ -1420,3 +1500,245 @@ PyTypeObject PyComplex_Type = { PyObject_Free, /* tp_free */ .tp_version_tag = _Py_TYPE_VERSION_COMPLEX, }; + +/*[clinic input] +@classmethod +imaginary.__new__ as imaginary_new + x: object(c_default="NULL") = 0 + +Create an imaginary number from a real number or string. + +This is equivalent of float(x)*1j. +[clinic start generated code]*/ + +static PyObject * +imaginary_new_impl(PyTypeObject *type, PyObject *x) +/*[clinic end generated code: output=07242ea06eb219f6 input=0ec29c2535687795]*/ +{ + if (x == NULL) { + x = _PyLong_GetZero(); + } + + PyNumberMethods *nbx = Py_TYPE(x)->tp_as_number; + + if (nbx == NULL || (nbx->nb_float == NULL && nbx->nb_index == NULL + && !PyFloat_Check(x) && !PyUnicode_Check(x))) + { + PyErr_Format(PyExc_TypeError, + "imaginary() first argument must be a real number, " + "not '%.200s'", Py_TYPE(x)->tp_name); + return NULL; + } + + PyObject* tmp = PyNumber_Float(x); + + if (tmp == NULL) { + return NULL; + } + + PyObject *ret = type->tp_alloc(type, 0); + + if (ret != NULL) { + ((PyComplexObject *)ret)->cval = (Py_complex) {0.0, PyFloat_AS_DOUBLE(tmp)}; + } + Py_DECREF(tmp); + return ret; +} + +PyObject * +PyImaginary_FromDouble(double imag) +{ + /* Inline PyObject_New */ + PyComplexObject *op = PyObject_Malloc(sizeof(PyComplexObject)); + + if (op == NULL) { + return PyErr_NoMemory(); + } + _PyObject_Init((PyObject*)op, &PyImaginary_Type); + op->cval = (Py_complex){0.0, imag}; + return (PyObject *) op; +} + +#define CVAL(op) ((PyComplexObject *)(op))->cval +#define CREAL(op) ((PyComplexObject *)(op))->cval.real +#define CIMAG(op) ((PyComplexObject *)(op))->cval.imag + +static PyObject * +imaginary_neg(PyComplexObject *v) +{ + return PyImaginary_FromDouble(-v->cval.imag); +} + +static PyObject * +imaginary_pos(PyComplexObject *v) +{ + if (PyImaginary_CheckExact(v)) { + return Py_NewRef(v); + } + return PyImaginary_FromDouble(v->cval.imag); +} + +/* Imaginary type arithmetic. Binary operations below + must support also complex and float (or int) operands. */ + +#define IMAGINARY_ADDITIVE_BINOP(NAME, FUNC, OP) \ + static PyObject * \ + imaginary_##NAME(PyObject *v, PyObject *w) \ + { \ + Py_complex a; \ + if (PyImaginary_Check(w)) { \ + if (PyImaginary_Check(v)) { \ + return PyImaginary_FromDouble(CIMAG(v) OP CIMAG(w)); \ + } \ + if (PyComplex_Check(v)) { \ + a = _Py_ci_##FUNC(CVAL(v), CIMAG(w)); \ + } \ + else if (real_to_double(&v, &a.real) < 0) { \ + return v; \ + } \ + else { \ + a.imag = OP CIMAG(w); \ + } \ + } \ + else if (PyComplex_Check(w)) { \ + a = _Py_ic_##FUNC(CIMAG(v), CVAL(w)); \ + } \ + else if (real_to_double(&w, &a.real) < 0) { \ + return w; \ + } \ + else { \ + a.real = OP a.real; \ + a.imag = CIMAG(v); \ + } \ + return PyComplex_FromCComplex(a); \ + } + +IMAGINARY_ADDITIVE_BINOP(add, sum, +) +IMAGINARY_ADDITIVE_BINOP(sub, diff, -) + +static PyObject * +imaginary_mul(PyObject *v, PyObject *w) +{ + if (!PyImaginary_Check(v)) { + PyObject *tmp = v; + + v = w; + w = tmp; + } + + double b; + + if (PyComplex_Check(w)) { + if (PyImaginary_Check(w)) { + return PyFloat_FromDouble(-CIMAG(v)*CIMAG(w)); + } + + Py_complex a = _Py_ic_prod(CIMAG(v), CVAL(w)); + + return PyComplex_FromCComplex(a); + } + else if (real_to_double(&w, &b) < 0) { + return w; + } + return PyImaginary_FromDouble(CIMAG(v)*b); +} + +static PyObject * +imaginary_div(PyObject *v, PyObject *w) +{ + double b; + + if (PyImaginary_Check(w)) { + b = CIMAG(w); + if (b) { + if (PyImaginary_Check(v)) { + return PyFloat_FromDouble(CIMAG(v)/b); + } + if (PyComplex_Check(v)) { + Py_complex a = _Py_ci_quot(CVAL(v), b); + + return PyComplex_FromCComplex(a); + } + + double a; + + if (real_to_double(&v, &a) < 0) { + return v; + } + return PyImaginary_FromDouble(-a/b); + } + } + else { + if (PyComplex_Check(w)) { + Py_complex a = _Py_ic_quot(CIMAG(v), CVAL(w)); + + if (!errno) { + return PyComplex_FromCComplex(a); + } + } + else if (real_to_double(&w, &b) < 0) { + return w; + } + else if (b) { + return PyImaginary_FromDouble(CIMAG(v)/b); + } + } + PyErr_SetString(PyExc_ZeroDivisionError, "complex division by zero"); + return NULL; +} + +/*[clinic input] +imaginary.conjugate + +Return the complex conjugate of its argument. (-4j).conjugate() == 4j. +[clinic start generated code]*/ + +static PyObject * +imaginary_conjugate_impl(PyComplexObject *self) +/*[clinic end generated code: output=247bb3742efd769d input=8dcd1c93a873c492]*/ +{ + Py_complex c = self->cval; + + c.imag = -c.imag; + return PyImaginary_FromDouble(c.imag); +} + +/*[clinic input] +imaginary.__getnewargs__ + +[clinic start generated code]*/ + +static PyObject * +imaginary___getnewargs___impl(PyComplexObject *self) +/*[clinic end generated code: output=a587156eda821f7d input=da4b7e53915987d5]*/ +{ + Py_complex c = self->cval; + + return Py_BuildValue("(d)", c.imag); +} + +static PyMethodDef imaginary_methods[] = { + IMAGINARY_CONJUGATE_METHODDEF + IMAGINARY___GETNEWARGS___METHODDEF + {NULL} /* sentinel */ +}; + +static PyNumberMethods imaginary_as_number = { + .nb_add = (binaryfunc)imaginary_add, + .nb_subtract = (binaryfunc)imaginary_sub, + .nb_multiply = (binaryfunc)imaginary_mul, + .nb_true_divide = (binaryfunc)imaginary_div, + .nb_negative = (unaryfunc)imaginary_neg, + .nb_positive = (unaryfunc)imaginary_pos, +}; + +PyTypeObject PyImaginary_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "imaginary", + .tp_as_number = &imaginary_as_number, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = imaginary_new__doc__, + .tp_base = &PyComplex_Type, + .tp_new = imaginary_new, + .tp_methods = imaginary_methods, +}; diff --git a/Objects/object.c b/Objects/object.c index f3c7fa6d906ad6..86ec97ab5ad9a3 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2413,6 +2413,7 @@ static PyTypeObject* static_types[] = { // subclasses: _PyTypes_FiniTypes() deallocates them before their base // class &PyBool_Type, // base=&PyLong_Type + &PyImaginary_Type, // base=&PyComplex_Type &PyCMethod_Type, // base=&PyCFunction_Type &PyODictItems_Type, // base=&PyDictItems_Type &PyODictKeys_Type, // base=&PyDictKeys_Type diff --git a/PC/python3dll.c b/PC/python3dll.c index 84b3c735240b73..c97cc4bd16e3ae 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -299,6 +299,7 @@ EXPORT_FUNC(PyGC_IsEnabled) EXPORT_FUNC(PyGILState_Ensure) EXPORT_FUNC(PyGILState_GetThisThreadState) EXPORT_FUNC(PyGILState_Release) +EXPORT_FUNC(PyImaginary_FromDouble) EXPORT_FUNC(PyImport_AddModule) EXPORT_FUNC(PyImport_AddModuleObject) EXPORT_FUNC(PyImport_AddModuleRef) @@ -902,6 +903,7 @@ EXPORT_DATA(PyFilter_Type) EXPORT_DATA(PyFloat_Type) EXPORT_DATA(PyFrozenSet_Type) EXPORT_DATA(PyGetSetDescr_Type) +EXPORT_DATA(PyImaginary_Type) EXPORT_DATA(PyList_Type) EXPORT_DATA(PyListIter_Type) EXPORT_DATA(PyListRevIter_Type) diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c index 2fe8d11badcbac..a8c6d7e3c417f9 100644 --- a/Parser/action_helpers.c +++ b/Parser/action_helpers.c @@ -837,7 +837,7 @@ _PyPegen_seq_delete_starred_exprs(Parser *p, asdl_seq *kwargs) expr_ty _PyPegen_ensure_imaginary(Parser *p, expr_ty exp) { - if (exp->kind != Constant_kind || !PyComplex_CheckExact(exp->v.Constant.value)) { + if (exp->kind != Constant_kind || !PyImaginary_CheckExact(exp->v.Constant.value)) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(exp, "imaginary number required in complex literal"); return NULL; } @@ -847,7 +847,8 @@ _PyPegen_ensure_imaginary(Parser *p, expr_ty exp) expr_ty _PyPegen_ensure_real(Parser *p, expr_ty exp) { - if (exp->kind != Constant_kind || PyComplex_CheckExact(exp->v.Constant.value)) { + if (exp->kind != Constant_kind || (PyComplex_CheckExact(exp->v.Constant.value) + || PyImaginary_CheckExact(exp->v.Constant.value))) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(exp, "real number required in complex literal"); return NULL; } diff --git a/Parser/pegen.c b/Parser/pegen.c index 83b0022e47d619..35b2a6b7610df2 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -614,7 +614,7 @@ parsenumber_raw(const char *s) const char *end; long x; double dx; - Py_complex compl; + double imag; int imflag; assert(s != NULL); @@ -638,12 +638,11 @@ parsenumber_raw(const char *s) } /* XXX Huge floats may silently fail */ if (imflag) { - compl.real = 0.; - compl.imag = PyOS_string_to_double(s, (char **)&end, NULL); - if (compl.imag == -1.0 && PyErr_Occurred()) { + imag = PyOS_string_to_double(s, (char **)&end, NULL); + if (imag == -1.0 && PyErr_Occurred()) { return NULL; } - return PyComplex_FromCComplex(compl); + return PyImaginary_FromDouble(imag); } dx = PyOS_string_to_double(s, NULL, NULL); if (dx == -1.0 && PyErr_Occurred()) { diff --git a/Python/ast.c b/Python/ast.c index bf1ff5f3ec18ba..e95474141c870c 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -174,6 +174,7 @@ validate_constant(struct validator *state, PyObject *value) if (PyLong_CheckExact(value) || PyFloat_CheckExact(value) || PyComplex_CheckExact(value) + || PyImaginary_CheckExact(value) || PyBool_Check(value) || PyUnicode_CheckExact(value) || PyBytes_CheckExact(value)) @@ -419,7 +420,7 @@ ensure_literal_number(expr_ty exp, bool allow_real, bool allow_imaginary) PyObject *value = exp->v.Constant.value; return (allow_real && PyFloat_CheckExact(value)) || (allow_real && PyLong_CheckExact(value)) || - (allow_imaginary && PyComplex_CheckExact(value)); + (allow_imaginary && PyImaginary_CheckExact(value)); } static int @@ -499,7 +500,7 @@ validate_pattern_match_value(struct validator *state, expr_ty exp) PyObject *literal = exp->v.Constant.value; if (PyLong_CheckExact(literal) || PyFloat_CheckExact(literal) || PyBytes_CheckExact(literal) || PyComplex_CheckExact(literal) || - PyUnicode_CheckExact(literal)) { + PyImaginary_CheckExact(literal) || PyUnicode_CheckExact(literal)) { return 1; } PyErr_SetString(PyExc_ValueError, diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c index f3c669c33eb07c..2ce4946608d1fe 100644 --- a/Python/ast_unparse.c +++ b/Python/ast_unparse.c @@ -88,7 +88,7 @@ append_repr(PyUnicodeWriter *writer, PyObject *obj) } if ((PyFloat_CheckExact(obj) && isinf(PyFloat_AS_DOUBLE(obj))) || - PyComplex_CheckExact(obj)) + PyComplex_CheckExact(obj) || PyImaginary_CheckExact(obj)) { _Py_DECLARE_STR(str_replace_inf, "1e309"); // evaluates to inf PyObject *new_repr = PyUnicode_Replace( diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 46a6fd9a8ef017..3874d6a8fe51f9 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2836,6 +2836,11 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) return PyComplex_FromDoubles(cs_to_double(re_sum), cs_to_double(im_sum)); } + if (PyImaginary_CheckExact(item)) { + double value = PyComplex_ImagAsDouble(item); + im_sum = cs_add(im_sum, value); + continue; + } if (PyComplex_CheckExact(item)) { z = PyComplex_AsCComplex(item); re_sum = cs_add(re_sum, z.real); @@ -3371,6 +3376,7 @@ _PyBuiltin_Init(PyInterpreterState *interp) SETBUILTIN("float", &PyFloat_Type); SETBUILTIN("frozenset", &PyFrozenSet_Type); SETBUILTIN("property", &PyProperty_Type); + SETBUILTIN("imaginary", &PyImaginary_Type); SETBUILTIN("int", &PyLong_Type); SETBUILTIN("list", &PyList_Type); SETBUILTIN("map", &PyMap_Type); diff --git a/Python/formatter_unicode.c b/Python/formatter_unicode.c index 16f711184990ac..2bd130a3c66bfe 100644 --- a/Python/formatter_unicode.c +++ b/Python/formatter_unicode.c @@ -1271,7 +1271,7 @@ format_complex_internal(PyObject *value, /* Omitted type specifier. Should be like str(self). */ type = 'r'; default_precision = 0; - if (re == 0.0 && copysign(1.0, re) == 1.0) + if (PyImaginary_Check(value)) skip_re = 1; else add_parens = 1; @@ -1290,8 +1290,17 @@ format_complex_internal(PyObject *value, /* Cast "type", because if we're in unicode we need to pass an 8-bit char. This is safe, because we've restricted what "type" can be. */ - re_buf = PyOS_double_to_string(re, (char)type, precision, flags, - &re_float_type); + if (re == 0.0) { + if (PyImaginary_Check(value)) { + skip_re = 1; + } + re_buf = PyOS_double_to_string(re, (char)type, precision, + flags | Py_DTSF_ADD_DOT_0, + &re_float_type); + } + else + re_buf = PyOS_double_to_string(re, (char)type, precision, flags, + &re_float_type); if (re_buf == NULL) goto done; im_buf = PyOS_double_to_string(im, (char)type, precision, flags, diff --git a/Python/marshal.c b/Python/marshal.c index cf7011652513ae..20d0eff73541d1 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -59,6 +59,8 @@ module marshal #define TYPE_ELLIPSIS '.' #define TYPE_BINARY_FLOAT 'g' // Version 0 uses TYPE_FLOAT instead. #define TYPE_BINARY_COMPLEX 'y' // Version 0 uses TYPE_COMPLEX instead. +#define TYPE_IMAGINARY 'j' +#define TYPE_BINARY_IMAGINARY 'J' #define TYPE_LONG 'l' // See also TYPE_INT. #define TYPE_STRING 's' // Bytes. (Name comes from Python 2.) #define TYPE_TUPLE '(' // See also TYPE_SMALL_TUPLE. @@ -506,6 +508,16 @@ w_complex_object(PyObject *v, char flag, WFILE *p) w_float_str(PyComplex_ImagAsDouble(v), p); } } + else if (PyImaginary_CheckExact(v)) { + if (p->version > 1) { + W_TYPE(TYPE_BINARY_IMAGINARY, p); + w_float_bin(PyComplex_ImagAsDouble(v), p); + } + else { + W_TYPE(TYPE_IMAGINARY, p); + w_float_str(PyComplex_ImagAsDouble(v), p); + } + } else if (PyBytes_CheckExact(v)) { W_TYPE(TYPE_STRING, p); w_pstring(PyBytes_AS_STRING(v), PyBytes_GET_SIZE(v), p); @@ -1246,6 +1258,26 @@ r_object(RFILE *p) break; } + case TYPE_IMAGINARY: + { + double i = r_float_str(p); + if (i == -1.0 && PyErr_Occurred()) + break; + retval = PyImaginary_FromDouble(i); + R_REF(retval); + break; + } + + case TYPE_BINARY_IMAGINARY: + { + double i = r_float_bin(p); + if (i == -1.0 && PyErr_Occurred()) + break; + retval = PyImaginary_FromDouble(i); + R_REF(retval); + break; + } + case TYPE_STRING: { const char *ptr; diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 54954cfb5f83ff..e521174eda2147 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -20,6 +20,7 @@ Objects/classobject.c - PyInstanceMethod_Type - Objects/classobject.c - PyMethod_Type - Objects/codeobject.c - PyCode_Type - Objects/complexobject.c - PyComplex_Type - +Objects/complexobject.c - PyImaginary_Type - Objects/descrobject.c - PyClassMethodDescr_Type - Objects/descrobject.c - PyDictProxy_Type - Objects/descrobject.c - PyGetSetDescr_Type -