diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 629312bd771beb..3668e497cdac03 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -120,6 +120,42 @@ Complex Number Objects Use :meth:`~object.__index__` if available. +Imaginary Number Objects +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. index:: pair: object; imaginary number + + +.. c:type:: PyImaginaryObject + + This subtype of :c:type:`PyComplexObject` represents a Python imaginary + number object. + + +.. c:var:: PyTypeObject PyImaginary_Type + + This instance of :c:type:`PyTypeObject` represents purely imaginary numbers, + the Python complex number type *without* real component. + + +.. c:function:: int PyImaginary_Check(PyObject *p) + + Return true if its argument is a :c:type:`PyImaginaryObject` or a subtype of + :c:type:`PyImaginaryObject`. This function always succeeds. + + +.. c:function:: int PyImaginary_CheckExact(PyObject *p) + + Return true if its argument is a :c:type:`PyImaginaryObject`, but not a + subtype of :c:type:`PyImaginaryObject`. This function always succeeds. + + +.. c:function:: PyObject* PyImaginary_FromDouble(double imag) + + Return a new :c:type:`PyImaginaryObject` object with *imag* imaginary + component. Return ``NULL`` with an exception set on error. + + Complex Numbers as C Structures ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 95e032655cf0cc..0cac8e354cf64e 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -342,6 +342,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/ast.rst b/Doc/library/ast.rst index 2e7d0dbc26e5bc..8cae88fc2d732d 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -268,7 +268,7 @@ Literals A constant value. The ``value`` attribute of the ``Constant`` literal contains the Python object it represents. The values represented can be instances of :class:`str`, - :class:`bytes`, :class:`int`, :class:`float`, :class:`complex`, and :class:`bool`, + :class:`bytes`, :class:`int`, :class:`float`, :class:`imaginary`, and :class:`bool`, and the constants :data:`None` and :data:`Ellipsis`. .. doctest:: diff --git a/Doc/library/cmath.rst b/Doc/library/cmath.rst index b6d5dbee21dcd5..e5fdf5a4622738 100644 --- a/Doc/library/cmath.rst +++ b/Doc/library/cmath.rst @@ -330,7 +330,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 @@ -346,7 +346,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 8314fed80fa512..64d8bedab45fb0 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__` | +-------------------------+-----------------------+-----------------------+-------------------------+ @@ -395,7 +396,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') @@ -405,7 +406,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) @@ -449,7 +450,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`. @@ -977,6 +978,15 @@ are always available. They are listed here in alphabetical order. .. audit-event:: builtins.id id id +.. class:: imaginary(x=0.0) + + Create an imaginary number from a number or a string. + + This is equivalent of ``float(x)*1j``. + + .. versionadded:: next + + .. function:: input() input(prompt, /) diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst index ea386706e8b511..a929f51b2af235 100644 --- a/Doc/reference/lexical_analysis.rst +++ b/Doc/reference/lexical_analysis.rst @@ -1363,8 +1363,8 @@ Imaginary literals Python has :ref:`complex number ` objects, but no complex literals. -Instead, *imaginary literals* denote complex numbers with a zero -real part. +Instead, *imaginary literals* denote complex numbers without a real part, an instance +of :class:`imaginary`. For example, in math, the complex number 3+4.2\ *i* is written as the real number 3 added to the imaginary number 4.2\ *i*. 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 58da80140dc4c9..72b611639620c3 100644 --- a/Include/cpython/complexobject.h +++ b/Include/cpython/complexobject.h @@ -29,6 +29,8 @@ typedef struct { Py_complex cval; } PyComplexObject; +#define PyImaginaryObject PyComplexObject + PyAPI_FUNC(PyObject *) PyComplex_FromCComplex(Py_complex); PyAPI_FUNC(Py_complex) PyComplex_AsCComplex(PyObject *op); diff --git a/Include/cpython/marshal.h b/Include/cpython/marshal.h index 6c1f7f96b6a2e8..159459fcaec3d9 100644 --- a/Include/cpython/marshal.h +++ b/Include/cpython/marshal.h @@ -6,7 +6,7 @@ PyAPI_FUNC(PyObject *) PyMarshal_ReadObjectFromString(const char *, Py_ssize_t); PyAPI_FUNC(PyObject *) PyMarshal_WriteObjectToString(PyObject *, int); -#define Py_MARSHAL_VERSION 5 +#define Py_MARSHAL_VERSION 6 PyAPI_FUNC(long) PyMarshal_ReadLongFromFile(FILE *); PyAPI_FUNC(int) PyMarshal_ReadShortFromFile(FILE *); diff --git a/Include/internal/pycore_complexobject.h b/Include/internal/pycore_complexobject.h index f595f6ab7a674d..0ce974c1e817f5 100644 --- a/Include/internal/pycore_complexobject.h +++ b/Include/internal/pycore_complexobject.h @@ -21,11 +21,17 @@ extern int _PyComplex_FormatAdvancedWriter( // Operations on complex numbers. 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_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_cr_prod(Py_complex, double); +PyAPI_FUNC(Py_complex) _Py_ci_prod(Py_complex, double); 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); #ifdef __cplusplus diff --git a/Lib/ast.py b/Lib/ast.py index d9743ba7ab40b1..45181779c576a8 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -90,7 +90,7 @@ def _convert_literal(node): isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)) and isinstance(node.operand, Constant) - and type(operand := node.operand.value) in (int, float, complex) + and type(operand := node.operand.value) in (int, float, imaginary) ): if isinstance(node.op, UAdd): return + operand @@ -102,7 +102,7 @@ def _convert_literal(node): and isinstance(node.left, (Constant, UnaryOp)) and isinstance(node.right, Constant) and type(left := _convert_literal(node.left)) in (int, float) - and type(right := _convert_literal(node.right)) is complex + and type(right := _convert_literal(node.right)) is imaginary ): if isinstance(node.op, Add): return left + right diff --git a/Lib/copy.py b/Lib/copy.py index fff7e93c2a1b89..ea6133336d457d 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -101,7 +101,7 @@ def copy(x): _copy_atomic_types = frozenset({types.NoneType, int, float, bool, complex, str, tuple, - bytes, frozenset, type, range, slice, property, + imaginary, bytes, frozenset, type, range, slice, property, types.BuiltinFunctionType, types.EllipsisType, types.NotImplementedType, types.FunctionType, types.CodeType, weakref.ref, super}) @@ -164,7 +164,8 @@ def deepcopy(x, memo=None): _atomic_types = frozenset({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_bytes.py b/Lib/test/test_bytes.py index 86898bfcab9135..596aae76056ddd 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -785,12 +785,12 @@ def __int__(self): ('%X format: an integer is required, not float', b'%X', 2.11), ('%o format: an integer is required, not float', b'%o', 1.79), ('%x format: an integer is required, not PseudoFloat', b'%x', pi), - ('%x format: an integer is required, not complex', b'%x', 3j), - ('%X format: an integer is required, not complex', b'%X', 2j), - ('%o format: an integer is required, not complex', b'%o', 1j), - ('%u format: a real number is required, not complex', b'%u', 3j), - ('%i format: a real number is required, not complex', b'%i', 2j), - ('%d format: a real number is required, not complex', b'%d', 2j), + ('%x format: an integer is required, not complex', b'%x', 1+3j), + ('%X format: an integer is required, not complex', b'%X', 1+2j), + ('%o format: an integer is required, not complex', b'%o', 1+1j), + ('%u format: a real number is required, not complex', b'%u', 1+3j), + ('%i format: a real number is required, not complex', b'%i', 1+2j), + ('%d format: a real number is required, not complex', b'%d', 1+2j), ( r'%c requires an integer in range\(256\)' r' or a single byte, not .*\.PseudoFloat', diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index c3189a67cc7e2d..caef8bebdf148e 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -24,6 +24,9 @@ class BadComplex3: def __complex__(self): raise RuntimeError +class ImaginarySubclass(imaginary): + pass + class CAPIComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase): def test_check(self): @@ -177,7 +180,14 @@ def test_py_cr_sum(self): # Test _Py_cr_sum() _py_cr_sum = _testinternalcapi._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 = _testinternalcapi._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): @@ -190,7 +200,14 @@ def test_py_cr_diff(self): # Test _Py_cr_diff() _py_cr_diff = _testinternalcapi._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 = _testinternalcapi._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): @@ -200,6 +217,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 = _testinternalcapi._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 @@ -219,6 +243,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 = _testinternalcapi._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 @@ -248,6 +279,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 = _testinternalcapi._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 = _testinternalcapi._py_rc_quot @@ -255,6 +293,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 = _testinternalcapi._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 @@ -276,7 +321,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 @@ -295,5 +339,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 0c7e7341f13d4e..7532400714f44c 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -39,6 +39,9 @@ def __float__(self): class ComplexSubclass(complex): pass +class ImaginarySubclass(imaginary): + pass + class OtherComplexSubclass(complex): pass @@ -195,6 +198,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): @@ -269,10 +292,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)) @@ -284,13 +325,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))]: @@ -299,8 +361,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)), @@ -455,6 +536,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): @@ -488,10 +570,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"): @@ -503,19 +585,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) @@ -599,18 +681,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""" @@ -625,11 +703,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) @@ -803,9 +900,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))) @@ -818,29 +919,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)) @@ -856,15 +971,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 @@ -930,7 +1041,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_descr.py b/Lib/test/test_descr.py index 82a48ad4d1aced..1bbaf6632c723a 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -2170,7 +2170,7 @@ def empty_seq(self): def zero(self): return 0 def complex_num(self): - return 1j + return 1+1j def stop(self): raise StopIteration def return_true(self, thing=None): diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 1f626d87fa6c7a..32b46edea61a12 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -603,10 +603,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 cf42b86358dbca..388bb250d29322 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1673,7 +1673,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 8b1fb0eba1f8b6..d6a345447b73eb 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -317,7 +317,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,), {}) @@ -371,7 +372,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))) @@ -627,7 +628,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: @@ -664,7 +665,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: @@ -680,7 +681,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_pickle.py b/Lib/test/test_pickle.py index e2384b33345a45..7a92432417bbe9 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -736,12 +736,12 @@ def invoke_pickle(self, *flags): def test_invocation(self): # test 'python -m pickle pickle_file' data = { - 'a': [1, 2.0, 3+4j], + 'a': [1, 2.0, 3+4j, -123j], 'b': ('character string', b'byte string'), 'c': 'string' } expect = ''' - {'a': [1, 2.0, (3+4j)], + {'a': [1, 2.0, (3+4j), -123j], 'b': ('character string', b'byte string'), 'c': 'string'} ''' diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 934b7137096bc0..f31f4d94e10bdc 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -988,7 +988,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: @@ -1000,7 +1000,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 bc834f5a6816f3..83c324df76afc2 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -343,6 +343,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 2584fbf72d3fa6..c1d184ebee661f 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -1582,12 +1582,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/Lib/test/test_unittest/testmock/testmagicmethods.py b/Lib/test/test_unittest/testmock/testmagicmethods.py index acdbd699d18134..684ded2a678163 100644 --- a/Lib/test/test_unittest/testmock/testmagicmethods.py +++ b/Lib/test/test_unittest/testmock/testmagicmethods.py @@ -273,7 +273,7 @@ def test_magic_mock_equality(self): def test_asyncmock_defaults(self): mock = AsyncMock() self.assertEqual(int(mock), 1) - self.assertEqual(complex(mock), 1j) + self.assertEqual(complex(mock), 1+1j) self.assertEqual(float(mock), 1.0) self.assertNotIn(object(), mock) self.assertEqual(len(mock), 0) @@ -299,7 +299,7 @@ def test_asyncmock_defaults(self): def test_magicmock_defaults(self): mock = MagicMock() self.assertEqual(int(mock), 1) - self.assertEqual(complex(mock), 1j) + self.assertEqual(complex(mock), 1+1j) self.assertEqual(float(mock), 1.0) self.assertNotIn(object(), mock) self.assertEqual(len(mock), 0) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 0bb6750655380d..99bb469c132446 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2098,7 +2098,7 @@ def method(self, /, *args, **kw): '__contains__': False, '__len__': 0, '__exit__': False, - '__complex__': 1j, + '__complex__': 1+1j, '__float__': 1.0, '__bool__': True, '__index__': 1, diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 5c503f81d3299a..663ae3f49e9fbf 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2523,6 +2523,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/_testinternalcapi/complex.c b/Modules/_testinternalcapi/complex.c index 50080d5a389ceb..a331e0a0bffdc0 100644 --- a/Modules/_testinternalcapi/complex.c +++ b/Modules/_testinternalcapi/complex.c @@ -5,53 +5,67 @@ #include "pycore_complexobject.h" -#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_CR_FUNC2(sum) -_PY_CR_FUNC2(diff) -_PY_RC_FUNC2(diff) -_PY_CR_FUNC2(prod) -_PY_CR_FUNC2(quot) -_PY_RC_FUNC2(quot) + +_PY_CX_FUNC2(sum, cr) +_PY_CX_FUNC2(sum, ci) +_PY_CX_FUNC2(diff, cr) +_PY_CX_FUNC2(diff, ci) +_PY_XC_FUNC2(diff, rc) +_PY_XC_FUNC2(diff, ic) +_PY_CX_FUNC2(prod, cr) +_PY_CX_FUNC2(prod, ci) +_PY_CX_FUNC2(quot, cr) +_PY_CX_FUNC2(quot, ci) +_PY_XC_FUNC2(quot, rc) +_PY_XC_FUNC2(quot, ic) + static PyMethodDef test_methods[] = { {"_py_cr_sum", _py_cr_sum, METH_VARARGS}, + {"_py_ci_sum", _py_ci_sum, 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_cr_prod", _py_cr_prod, METH_VARARGS}, + {"_py_ci_prod", _py_ci_prod, 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}, {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 aee3e4f343d8be..9b847ea13fd3f0 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -1190,15 +1190,13 @@ cmath_exec(PyObject *mod) return -1; } - Py_complex infj = {0.0, INFINITY}; - if (PyModule_Add(mod, "infj", PyComplex_FromCComplex(infj)) < 0) { + if (PyModule_Add(mod, "infj", PyImaginary_FromDouble(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(fabs(Py_NAN))) < 0) { return -1; } diff --git a/Objects/clinic/complexobject.c.h b/Objects/clinic/complexobject.c.h index c7303380de55f4..333468a94adb90 100644 --- a/Objects/clinic/complexobject.c.h +++ b/Objects/clinic/complexobject.c.h @@ -185,4 +185,103 @@ complex_from_number(PyObject *type, PyObject *number) return return_value; } -/*[clinic end generated code: output=05d2ff43fc409733 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(imaginary_new__doc__, +"imaginary(x=0)\n" +"--\n" +"\n" +"Create an imaginary number from a number or a 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 + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .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=c2de307dcaf92b58 input=a9049054013a1b77]*/ diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 3aea2038fd17e7..c61ddf81b0fd4b 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -2978,6 +2978,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; @@ -3205,6 +3214,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; + } // gh-130851: Treat instances of unexpected types as distinct if they are // not the same object. return 0; diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 3612c2699a557d..93941091ecb921 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 @@ -589,7 +658,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 = ""; @@ -603,7 +672,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; @@ -679,19 +749,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 * \ @@ -1437,3 +1517,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 number or a 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=b20209245b6b7da8]*/ +{ + 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 0a80c6edcf158c..d6f3402b2ed30d 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2559,6 +2559,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/Objects/unicode_formatter.c b/Objects/unicode_formatter.c index b8604d1355940a..0165f2403182b4 100644 --- a/Objects/unicode_formatter.c +++ b/Objects/unicode_formatter.c @@ -1608,7 +1608,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; @@ -1627,8 +1627,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/PC/python3dll.c b/PC/python3dll.c index 35db1a660a762f..69bdf3d186fc4a 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -301,6 +301,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) @@ -916,6 +917,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 b7a5b9d5e307b1..e17f593451dcba 100644 --- a/Parser/action_helpers.c +++ b/Parser/action_helpers.c @@ -839,7 +839,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; } @@ -849,7 +849,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 a38e973b3f64c6..411e5ce9064449 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -617,7 +617,7 @@ parsenumber_raw(const char *s) const char *end; long x; double dx; - Py_complex compl; + double imag; int imflag; assert(s != NULL); @@ -641,12 +641,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 e01dd0de51e596..4abd9c11b5681f 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -162,7 +162,7 @@ validate_constant(PyObject *value) if (PyLong_CheckExact(value) || PyFloat_CheckExact(value) - || PyComplex_CheckExact(value) + || PyImaginary_CheckExact(value) || PyBool_Check(value) || PyUnicode_CheckExact(value) || PyBytes_CheckExact(value)) @@ -420,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 +499,7 @@ validate_pattern_match_value(expr_ty exp) } PyObject *literal = exp->v.Constant.value; if (PyLong_CheckExact(literal) || PyFloat_CheckExact(literal) || - PyBytes_CheckExact(literal) || PyComplex_CheckExact(literal) || + PyBytes_CheckExact(literal) || PyImaginary_CheckExact(literal) || PyUnicode_CheckExact(literal)) { return 1; } diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c index c25699978cf651..08d94e93703f5f 100644 --- a/Python/ast_unparse.c +++ b/Python/ast_unparse.c @@ -92,7 +92,7 @@ append_repr(PyUnicodeWriter *writer, PyObject *obj) } if ((PyFloat_CheckExact(obj) && isinf(PyFloat_AS_DOUBLE(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 c2d780ac9b9270..73ff2d517eb2b1 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2948,6 +2948,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); @@ -3481,6 +3486,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/marshal.c b/Python/marshal.c index 8b56de6575559c..5fb78293c397a4 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -62,6 +62,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. @@ -509,6 +511,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); @@ -1249,6 +1261,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 3c3cb2f9c86f16..574a7285a458d2 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 -