From facb60b523c3c69e68c529a32991c665f45b6cd3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 08:18:36 +0300 Subject: [PATCH 01/38] gh-109802: Increase test coverage for complexobject.c * _Py_c_pow: L134 (this case goes to c_powi() due to L523), L139 // line numbers wrt to 54fbfa8d5e --- Lib/test/test_complex.py | 2 ++ Objects/complexobject.c | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 9180cca62b28b8..93c265021a5fe1 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -212,7 +212,9 @@ def test_divmod_zero_division(self): def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) self.assertAlmostEqual(pow(0+0j, 2+0j), 0.0) + self.assertAlmostEqual(pow(0+0j, 2000+0j), 0.0) self.assertRaises(ZeroDivisionError, pow, 0+0j, 1j) + self.assertRaises(ZeroDivisionError, pow, 0+0j, -1000) self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 0e96f54584677c..55bd486b2486ed 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -131,11 +131,8 @@ _Py_c_pow(Py_complex a, Py_complex b) { Py_complex r; double vabs,len,at,phase; - if (b.real == 0. && b.imag == 0.) { - r.real = 1.; - r.imag = 0.; - } - else if (a.real == 0. && a.imag == 0.) { + assert(!(b.real == 0. && b.imag == 0.)); + if (a.real == 0. && a.imag == 0.) { if (b.imag != 0. || b.real < 0.) errno = EDOM; r.real = 0.; From deff888a0159e80c4974ffac877b4e935be1abee Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 08:41:24 +0300 Subject: [PATCH 02/38] * c_powu: L166 --- Objects/complexobject.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 55bd486b2486ed..866405388ba4fc 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -153,6 +153,8 @@ _Py_c_pow(Py_complex a, Py_complex b) return r; } +#define C_EXP_CUTOFF 100 + static Py_complex c_powu(Py_complex x, long n) { @@ -160,7 +162,9 @@ c_powu(Py_complex x, long n) long mask = 1; r = c_1; p = x; - while (mask > 0 && n >= mask) { + assert(0 <= n && n <= C_EXP_CUTOFF); + while (n >= mask) { + assert(mask>0); if (n & mask) r = _Py_c_prod(r,p); mask <<= 1; @@ -516,7 +520,7 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) errno = 0; // Check whether the exponent has a small integer value, and if so use // a faster and more accurate algorithm. - if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) { + if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= C_EXP_CUTOFF) { p = c_powi(a, (long)b.real); } else { From 8a7b344d2253e9d5591b86e5d3777e76a61d3070 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 10:21:17 +0300 Subject: [PATCH 03/38] * complex_hash: L411, L414, L423 https://docs.python.org/3/library/stdtypes.html#hashing-of-numeric-types Inaccessible cases transformed to asserts. --- Lib/test/test_complex.py | 2 ++ Objects/complexobject.c | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 93c265021a5fe1..d3a0cb9b91b7ad 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -555,6 +555,8 @@ def test_hash(self): x /= 3.0 # now check against floating point self.assertEqual(hash(x), hash(complex(x, 0.))) + self.assertEqual(hash(2000005 - 1j), -2) + def test_abs(self): nums = [complex(x/3., y/7.) for x in range(-9,9) for y in range(-9,9)] for num in nums: diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 866405388ba4fc..6d43196562fc2f 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -409,11 +409,9 @@ complex_hash(PyComplexObject *v) { Py_uhash_t hashreal, hashimag, combined; hashreal = (Py_uhash_t)_Py_HashDouble((PyObject *) v, v->cval.real); - if (hashreal == (Py_uhash_t)-1) - return -1; + assert(hashreal != (Py_uhash_t)-1); hashimag = (Py_uhash_t)_Py_HashDouble((PyObject *)v, v->cval.imag); - if (hashimag == (Py_uhash_t)-1) - return -1; + assert(hashimag != (Py_uhash_t)-1); /* Note: if the imaginary part is 0, hashimag is 0 now, * so the following returns hashreal unchanged. This is * important because numbers of different types that From e3831672ce61d0c094806592747e6a9d5b279f81 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 10:32:50 +0300 Subject: [PATCH 04/38] * to_complex: L443, L449, L453-454 --- Lib/test/test_complex.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index d3a0cb9b91b7ad..24fe629382edf9 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -180,6 +180,13 @@ def check(n, deltas, is_equal, imag = 0.0): check(2 ** pow, range(1, 101), lambda delta: False, float(i)) check(2 ** 53, range(-100, 0), lambda delta: True) + def test_add(self): + self.assertAlmostEqual(1j + 1, complex(+1, 1)) + self.assertAlmostEqual(1j + (-1), complex(-1, 1)) + self.assertRaises(OverflowError, operator.add, 1j, 10**1000) + self.assertRaises(TypeError, operator.add, 1j, None) + self.assertRaises(TypeError, operator.add, None, 1j) + def test_mod(self): # % is no longer supported on complex numbers with self.assertRaises(TypeError): From 85fae1aca1aebdb138fd771c7ec48904913c4fcd Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 11:14:07 +0300 Subject: [PATCH 05/38] * complex_sub: L474, L475 --- Lib/test/test_complex.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 24fe629382edf9..471322f9e9cc45 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -187,6 +187,13 @@ def test_add(self): self.assertRaises(TypeError, operator.add, 1j, None) self.assertRaises(TypeError, operator.add, None, 1j) + def test_sub(self): + self.assertAlmostEqual(1j - 1, complex(-1, 1)) + self.assertAlmostEqual(1j - (-1), complex(1, 1)) + self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) + self.assertRaises(TypeError, operator.sub, 1j, None) + self.assertRaises(TypeError, operator.sub, None, 1j) + def test_mod(self): # % is no longer supported on complex numbers with self.assertRaises(TypeError): From 406c3756dbe7c332065976816b9b374e9d0cf5fe Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 11:19:24 +0300 Subject: [PATCH 06/38] * complex_mul: L485, L486 --- Lib/test/test_complex.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 471322f9e9cc45..419b0dc85ca77b 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -194,6 +194,13 @@ def test_sub(self): self.assertRaises(TypeError, operator.sub, 1j, None) self.assertRaises(TypeError, operator.sub, None, 1j) + def test_mul(self): + self.assertAlmostEqual(1j * 2, complex(0, 2)) + self.assertAlmostEqual(1j * (-1), complex(0, -1)) + self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) + self.assertRaises(TypeError, operator.mul, 1j, None) + self.assertRaises(TypeError, operator.mul, None, 1j) + def test_mod(self): # % is no longer supported on complex numbers with self.assertRaises(TypeError): From aba6fb440ded32224d016d929055e39afede90f7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 11:43:54 +0300 Subject: [PATCH 07/38] * complex_div: L496, L497 --- Lib/test/test_complex.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 419b0dc85ca77b..889536dcedb761 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -109,6 +109,8 @@ def test_truediv(self): complex(random(), random())) self.assertAlmostEqual(complex.__truediv__(2+0j, 1+1j), 1-1j) + self.assertRaises(TypeError, operator.truediv, 1j, None) + self.assertRaises(TypeError, operator.truediv, None, 1j) for denom_real, denom_imag in [(0, NAN), (NAN, 0), (NAN, NAN)]: z = complex(0, 0) / complex(denom_real, denom_imag) From 204e2ecf9314eaa943cf2d97247eec10a773813a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 14:58:43 +0300 Subject: [PATCH 08/38] * complex_pow: L512, L513, L522 --- Lib/test/test_complex.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 889536dcedb761..b6abff0d5ff379 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -236,12 +236,17 @@ def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) self.assertAlmostEqual(pow(0+0j, 2+0j), 0.0) self.assertAlmostEqual(pow(0+0j, 2000+0j), 0.0) + self.assertAlmostEqual(pow(0, 0+0j), 1.0) + self.assertAlmostEqual(pow(-1, 0+0j), 1.0) self.assertRaises(ZeroDivisionError, pow, 0+0j, 1j) self.assertRaises(ZeroDivisionError, pow, 0+0j, -1000) self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) + self.assertRaises(TypeError, pow, 1j, None) + self.assertRaises(TypeError, pow, None, 1j) + self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j) a = 3.33+4.43j self.assertEqual(a ** 0j, 1) From b67e8d64b3549ac5149bdf30f99b224a2ece8f84 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 15:09:09 +0300 Subject: [PATCH 09/38] * complex_pos: L555-L559 --- Lib/test/test_complex.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index b6abff0d5ff379..86352dd89bb2e6 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -634,6 +634,12 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., 0.), "(-0+0j)") test(complex(-0., -0.), "(-0-0j)") + def test_pos(self): + from test.test_capi.test_getargs import ComplexSubclass + + self.assertEqual(+(1+6j), 1+6j) + self.assertEqual(+ComplexSubclass(1, 6), 1+6j) + def test_neg(self): self.assertEqual(-(1+6j), -1-6j) From 85f69946fe44dc9d4e8ad0898f109371f7466b4c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 15:35:39 +0300 Subject: [PATCH 10/38] * complex_richcompare: L595, L616, L621, L625 --- Lib/test/test_complex.py | 2 ++ Objects/complexobject.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 86352dd89bb2e6..918458da097e1d 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -142,6 +142,7 @@ def test_floordiv_zero_division(self): def test_richcompare(self): self.assertIs(complex.__eq__(1+1j, 1<<10000), False) self.assertIs(complex.__lt__(1+1j, None), NotImplemented) + self.assertIs(complex.__eq__(1+1j, None), NotImplemented) self.assertIs(complex.__eq__(1+1j, 1+1j), True) self.assertIs(complex.__eq__(1+1j, 2+2j), False) self.assertIs(complex.__ne__(1+1j, 1+1j), False) @@ -164,6 +165,7 @@ def test_richcompare(self): self.assertIs(operator.eq(1+1j, 2+2j), False) self.assertIs(operator.ne(1+1j, 1+1j), False) self.assertIs(operator.ne(1+1j, 2+2j), True) + self.assertIs(operator.eq(1+1j, 2.0), False) def test_richcompare_boundaries(self): def check(n, deltas, is_equal, imag = 0.0): diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 6d43196562fc2f..f6f4b5756eec12 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -591,7 +591,7 @@ complex_richcompare(PyObject *v, PyObject *w, int op) } assert(PyComplex_Check(v)); - TO_COMPLEX(v, i); + i = ((PyComplexObject*)v)->cval; if (PyLong_Check(w)) { /* Check for 0.0 imaginary part first to avoid the rich @@ -617,7 +617,7 @@ complex_richcompare(PyObject *v, PyObject *w, int op) else if (PyComplex_Check(w)) { Py_complex j; - TO_COMPLEX(w, j); + j = ((PyComplexObject*)w)->cval; equal = (i.real == j.real && i.imag == j.imag); } else { From ffd088a049c3ae400a91c6acc741103591b368b9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 16:45:13 +0300 Subject: [PATCH 11/38] * complex_from_string_inner: L786, L804 --- Lib/test/test_complex.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 918458da097e1d..e7353dd68fe504 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -391,6 +391,8 @@ def __complex__(self): return self.value self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) + self.assertAlmostEqual(complex('1-1j'), 1.0 - 1j) + self.assertAlmostEqual(complex('1J'), 1j) class complex2(complex): pass self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) From 72b349d27bf825c819ba0f428d0482451f29ac5f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 17:05:22 +0300 Subject: [PATCH 12/38] * complex_subtype_from_string: L860, L871-L874 (the function called in L926) --- Objects/complexobject.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index f6f4b5756eec12..e5c1861e80c105 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -856,22 +856,15 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) PyObject *s_buffer = NULL, *result = NULL; Py_ssize_t len; - if (PyUnicode_Check(v)) { - s_buffer = _PyUnicode_TransformDecimalAndSpaceToASCII(v); - if (s_buffer == NULL) { - return NULL; - } - assert(PyUnicode_IS_ASCII(s_buffer)); - /* Simply get a pointer to existing ASCII characters. */ - s = PyUnicode_AsUTF8AndSize(s_buffer, &len); - assert(s != NULL); - } - else { - PyErr_Format(PyExc_TypeError, - "complex() argument must be a string or a number, not '%.200s'", - Py_TYPE(v)->tp_name); + assert(PyUnicode_Check(v)); + s_buffer = _PyUnicode_TransformDecimalAndSpaceToASCII(v); + if (s_buffer == NULL) { return NULL; } + assert(PyUnicode_IS_ASCII(s_buffer)); + /* Simply get a pointer to existing ASCII characters. */ + s = PyUnicode_AsUTF8AndSize(s_buffer, &len); + assert(s != NULL); result = _Py_string_to_number_with_underscores(s, len, "complex", v, type, complex_from_string_inner); From c25563c68aba9ad499744a1ed33fae972ea74647 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 20 Sep 2023 17:56:52 +0300 Subject: [PATCH 13/38] * complex_new_impl: L944, L951, L958, L965, L985, L994 --- Lib/test/test_complex.py | 4 ++++ Objects/complexobject.c | 17 ++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index e7353dd68fe504..bd59d77f9dd081 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -338,6 +338,8 @@ def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) def test_constructor(self): + from test.test_capi.test_getargs import Complex + class NS: def __init__(self, value): self.value = value def __complex__(self): return self.value @@ -346,6 +348,8 @@ def __complex__(self): return self.value self.assertRaises(TypeError, complex, {}) self.assertRaises(TypeError, complex, NS(1.5)) self.assertRaises(TypeError, complex, NS(1)) + self.assertRaises(TypeError, complex, object()) + self.assertRaises(TypeError, complex, Complex(), object()) self.assertAlmostEqual(complex("1+10j"), 1+10j) self.assertAlmostEqual(complex(10), 10+0j) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index e5c1861e80c105..a2a96d66e7978f 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -940,9 +940,7 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) "complex() first argument must be a string or a number, " "not '%.200s'", Py_TYPE(r)->tp_name); - if (own_r) { - Py_DECREF(r); - } + assert(!own_r); return NULL; } if (i != NULL) { @@ -974,20 +972,17 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) value is (properly) of the builtin complex type. */ cr = ((PyComplexObject*)r)->cval; cr_is_complex = 1; - if (own_r) { - Py_DECREF(r); - } + assert(own_r); + Py_DECREF(r); } else { /* The "real" part really is entirely real, and contributes nothing in the imaginary direction. Just treat it as a double. */ tmp = PyNumber_Float(r); - if (own_r) { - /* r was a newly created complex number, rather - than the original "real" argument. */ - Py_DECREF(r); - } + /* r was a newly created complex number, rather + than the original "real" argument. */ + assert(!own_r); if (tmp == NULL) return NULL; assert(PyFloat_Check(tmp)); From e1c247ff19bc1df5c8311fbfc0afa8679f5d5820 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 21 Sep 2023 06:41:39 +0300 Subject: [PATCH 14/38] * complex_bool: L580 --- Lib/test/test_complex.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index bd59d77f9dd081..2b52e52df01169 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -334,6 +334,9 @@ def test_boolcontext(self): self.assertTrue(complex(random() + 1e-6, random() + 1e-6)) self.assertTrue(not complex(0.0, 0.0)) + def test_bool(self): + self.assertTrue(1j) + def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) From 64f217427ae9932b3158bf8e7b18046e4b710b87 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Nov 2023 05:52:07 +0300 Subject: [PATCH 15/38] Apply suggestions from code review Co-authored-by: Pieter Eendebak --- Objects/complexobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index a2a96d66e7978f..e37f917bae0502 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -973,6 +973,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) cr = ((PyComplexObject*)r)->cval; cr_is_complex = 1; assert(own_r); + /* r was a newly created complex number, rather + than the original "real" argument. */ Py_DECREF(r); } else { @@ -980,8 +982,6 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) nothing in the imaginary direction. Just treat it as a double. */ tmp = PyNumber_Float(r); - /* r was a newly created complex number, rather - than the original "real" argument. */ assert(!own_r); if (tmp == NULL) return NULL; From 2ef7d2586b4d2763a98595c1cf8c2c16792b516c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Nov 2023 15:23:54 +0300 Subject: [PATCH 16/38] address review: self.assertAlmostEqual -> self.assertEqual (except one) --- Lib/test/test_complex.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 2b52e52df01169..0fd110f160d65a 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -185,22 +185,22 @@ def check(n, deltas, is_equal, imag = 0.0): check(2 ** 53, range(-100, 0), lambda delta: True) def test_add(self): - self.assertAlmostEqual(1j + 1, complex(+1, 1)) - self.assertAlmostEqual(1j + (-1), complex(-1, 1)) + self.assertEqual(1j + 1, complex(+1, 1)) + self.assertEqual(1j + (-1), complex(-1, 1)) self.assertRaises(OverflowError, operator.add, 1j, 10**1000) self.assertRaises(TypeError, operator.add, 1j, None) self.assertRaises(TypeError, operator.add, None, 1j) def test_sub(self): - self.assertAlmostEqual(1j - 1, complex(-1, 1)) - self.assertAlmostEqual(1j - (-1), complex(1, 1)) + self.assertEqual(1j - 1, complex(-1, 1)) + self.assertEqual(1j - (-1), complex(1, 1)) self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) self.assertRaises(TypeError, operator.sub, 1j, None) self.assertRaises(TypeError, operator.sub, None, 1j) def test_mul(self): - self.assertAlmostEqual(1j * 2, complex(0, 2)) - self.assertAlmostEqual(1j * (-1), complex(0, -1)) + self.assertEqual(1j * 2, complex(0, 2)) + self.assertEqual(1j * (-1), complex(0, -1)) self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) self.assertRaises(TypeError, operator.mul, 1j, None) self.assertRaises(TypeError, operator.mul, None, 1j) @@ -237,9 +237,9 @@ def test_divmod_zero_division(self): def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) self.assertAlmostEqual(pow(0+0j, 2+0j), 0.0) - self.assertAlmostEqual(pow(0+0j, 2000+0j), 0.0) - self.assertAlmostEqual(pow(0, 0+0j), 1.0) - self.assertAlmostEqual(pow(-1, 0+0j), 1.0) + self.assertEqual(pow(0+0j, 2000+0j), 0.0) + self.assertEqual(pow(0, 0+0j), 1.0) + self.assertEqual(pow(-1, 0+0j), 1.0) self.assertRaises(ZeroDivisionError, pow, 0+0j, 1j) self.assertRaises(ZeroDivisionError, pow, 0+0j, -1000) self.assertAlmostEqual(pow(1j, -1), 1/1j) @@ -398,8 +398,8 @@ def __complex__(self): return self.value self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) - self.assertAlmostEqual(complex('1-1j'), 1.0 - 1j) - self.assertAlmostEqual(complex('1J'), 1j) + self.assertEqual(complex('1-1j'), 1.0 - 1j) + self.assertEqual(complex('1J'), 1j) class complex2(complex): pass self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) From 75fda9c1b6638adb1334f393c13da3a805efb26b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Nov 2023 15:26:40 +0300 Subject: [PATCH 17/38] address review: spam support classes --- Lib/test/test_complex.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 0fd110f160d65a..cd61491e4a8836 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -341,7 +341,9 @@ def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) def test_constructor(self): - from test.test_capi.test_getargs import Complex + class Complex: + def __complex__(self): + return 4.25+0.5j class NS: def __init__(self, value): self.value = value @@ -646,7 +648,8 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., -0.), "(-0-0j)") def test_pos(self): - from test.test_capi.test_getargs import ComplexSubclass + class ComplexSubclass(complex): + pass self.assertEqual(+(1+6j), 1+6j) self.assertEqual(+ComplexSubclass(1, 6), 1+6j) From 36ee551b00c9abb9fbc22c3e6345b53255b10645 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Nov 2023 16:01:51 +0300 Subject: [PATCH 18/38] address review: comment on assert in complex_new_impl() --- Objects/complexobject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index e37f917bae0502..1db4fecb364462 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -940,6 +940,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i) "complex() first argument must be a string or a number, " "not '%.200s'", Py_TYPE(r)->tp_name); + /* Here r is not a complex subtype, hence above + try_complex_special_method() call was unsuccessful. */ assert(!own_r); return NULL; } From aafe16e81d156d6b869da77ccc31175d76a920c2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Nov 2023 17:01:17 +0300 Subject: [PATCH 19/38] address review: revert _Py_c_pow() --- Objects/complexobject.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 1db4fecb364462..59e196ec7f8b97 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -131,8 +131,11 @@ _Py_c_pow(Py_complex a, Py_complex b) { Py_complex r; double vabs,len,at,phase; - assert(!(b.real == 0. && b.imag == 0.)); - if (a.real == 0. && a.imag == 0.) { + if (b.real == 0. && b.imag == 0.) { + r.real = 1.; + r.imag = 0.; + } + else if (a.real == 0. && a.imag == 0.) { if (b.imag != 0. || b.real < 0.) errno = EDOM; r.real = 0.; From f88c4bc1cedda469cbee23d936484c41a0cbbe8d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Nov 2023 17:18:47 +0300 Subject: [PATCH 20/38] address review: hash test --- Lib/test/test_complex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index cd61491e4a8836..55361e9f74b91a 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -596,7 +596,7 @@ def test_hash(self): x /= 3.0 # now check against floating point self.assertEqual(hash(x), hash(complex(x, 0.))) - self.assertEqual(hash(2000005 - 1j), -2) + self.assertNotEqual(hash(2000005 - 1j), -1) def test_abs(self): nums = [complex(x/3., y/7.) for x in range(-9,9) for y in range(-9,9)] From a2b6e7319101418fa25c410ce3c5aac72ddb9a84 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 13 Nov 2023 08:45:56 +0300 Subject: [PATCH 21/38] * _Py_c_pow: L135-136, also other tests for _Py_c_* C API --- Lib/test/test_capi/test_complex.py | 50 +++++++++++++++++++++++++++ Modules/_testcapi/complex.c | 54 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 9f51efb091dea5..b0959c42a74d44 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -1,3 +1,4 @@ +from math import isnan import unittest import warnings @@ -141,6 +142,55 @@ def test_asccomplex(self): # CRASHES asccomplex(NULL) + def test_py_c_sum(self): + # Test _Py_c_sum() + _py_c_sum = _testcapi._py_c_sum + + self.assertEqual(_py_c_sum(1, 1j), 1+1j) + + def test_py_c_diff(self): + # Test _Py_c_diff() + _py_c_diff = _testcapi._py_c_diff + + self.assertEqual(_py_c_diff(1, 1j), 1-1j) + + def test_py_c_neg(self): + # Test _Py_c_neg() + _py_c_neg = _testcapi._py_c_neg + + self.assertEqual(_py_c_neg(1+1j), -1-1j) + + def test_py_c_prod(self): + # Test _Py_c_prod() + _py_c_prod = _testcapi._py_c_prod + + self.assertEqual(_py_c_prod(2, 1j), 2j) + + def test_py_c_quot(self): + # Test _Py_c_quot() + _py_c_quot = _testcapi._py_c_quot + + self.assertEqual(_py_c_quot(1, 1j), -1j) + self.assertEqual(_py_c_quot(1j, 2), 0.5j) + self.assertEqual(_py_c_quot(1, 2j), -0.5j) + + z = _py_c_quot(float('nan'), 1j) + self.assertTrue(isnan(z.real)) + self.assertTrue(isnan(z.imag)) + + self.assertRaises(ZeroDivisionError, _py_c_quot, 1, 0j) + + def test_py_c_pow(self): + # Test _Py_c_pow() + _py_c_pow = _testcapi._py_c_pow + + self.assertEqual(_py_c_pow(1j, 0+0j), 1+0j) + self.assertEqual(_py_c_pow(0j, 1), 0j) + self.assertAlmostEqual(_py_c_pow(1+1j, -1), 0.5-0.5j) + + self.assertRaises(ZeroDivisionError, _py_c_pow, 0j, -1) + self.assertRaises(OverflowError, _py_c_pow, 1e200+1j, 1e200+1j) + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index 400f4054c613ee..ea63a29c3b7542 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -1,5 +1,7 @@ #include "parts.h" #include "util.h" +#define Py_BUILD_CORE +#include "pycore_complexobject.h" // _Py_c_* static PyObject * @@ -85,6 +87,52 @@ complex_asccomplex(PyObject *Py_UNUSED(module), PyObject *obj) return PyComplex_FromCComplex(complex); } +static PyObject* +_py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) +{ + Py_complex complex; + + complex = PyComplex_AsCComplex(num); + if (complex.real == -1. && PyErr_Occurred()) { + return NULL; + } + + return PyComplex_FromCComplex(_Py_c_neg(complex)); +} + +#define _PY_C_FUNC2(suffix) \ + static PyObject * \ + _py_c_##suffix(PyObject *Py_UNUSED(module), PyObject *args) \ + { \ + Py_complex num, exp, res; \ + \ + if (!PyArg_ParseTuple(args, "DD", &num, &exp)) { \ + return NULL; \ + } \ + \ + errno = 0; \ + res = _Py_c_##suffix(num, exp); \ + \ + if (errno == EDOM) { \ + PyErr_SetString(PyExc_ZeroDivisionError, \ + "complex division by zero"); \ + return NULL; \ + } \ + else if (errno == ERANGE) { \ + PyErr_SetString(PyExc_OverflowError, \ + "complex exponentiation"); \ + return NULL; \ + } \ + \ + return PyComplex_FromCComplex(res); \ + }; + +_PY_C_FUNC2(sum) +_PY_C_FUNC2(diff) +_PY_C_FUNC2(prod) +_PY_C_FUNC2(quot) +_PY_C_FUNC2(pow) + static PyMethodDef test_methods[] = { {"complex_check", complex_check, METH_O}, @@ -94,6 +142,12 @@ static PyMethodDef test_methods[] = { {"complex_realasdouble", complex_realasdouble, METH_O}, {"complex_imagasdouble", complex_imagasdouble, METH_O}, {"complex_asccomplex", complex_asccomplex, METH_O}, + {"_py_c_sum", _py_c_sum, METH_VARARGS}, + {"_py_c_diff", _py_c_diff, METH_VARARGS}, + {"_py_c_neg", _py_c_neg, METH_O}, + {"_py_c_prod", _py_c_prod, METH_VARARGS}, + {"_py_c_quot", _py_c_quot, METH_VARARGS}, + {"_py_c_pow", _py_c_pow, METH_VARARGS}, {NULL}, }; From 32d9f7f453a7a02392c9b14779ceb5312b1b3606 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 13 Nov 2023 09:53:08 +0300 Subject: [PATCH 22/38] +1 --- Modules/_testcapi/complex.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index ea63a29c3b7542..6ee71e26862c47 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -1,7 +1,8 @@ #include "parts.h" #include "util.h" #define Py_BUILD_CORE -#include "pycore_complexobject.h" // _Py_c_* +#include "pycore_complexobject.h" // _Py_c_*() +#include "pycore_pymath.h" // _Py_ADJUST_ERANGE2() static PyObject * @@ -112,6 +113,7 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) \ errno = 0; \ res = _Py_c_##suffix(num, exp); \ + _Py_ADJUST_ERANGE2(res.real, res.imag); \ \ if (errno == EDOM) { \ PyErr_SetString(PyExc_ZeroDivisionError, \ From 9e21cc10a3efbb8d7092fd6af6654aeddc0efb64 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 14 Nov 2023 13:02:41 +0300 Subject: [PATCH 23/38] address review: * move test in test_bool() * drop redundant test class * test type in test_pos * avoid removing BINARY_OP instructions at compile time in arithmetic tests --- Lib/test/test_complex.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 55361e9f74b91a..b057121f285dc7 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -185,22 +185,22 @@ def check(n, deltas, is_equal, imag = 0.0): check(2 ** 53, range(-100, 0), lambda delta: True) def test_add(self): - self.assertEqual(1j + 1, complex(+1, 1)) - self.assertEqual(1j + (-1), complex(-1, 1)) + self.assertEqual(1j + int(+1), complex(+1, 1)) + self.assertEqual(1j + int(-1), complex(-1, 1)) self.assertRaises(OverflowError, operator.add, 1j, 10**1000) self.assertRaises(TypeError, operator.add, 1j, None) self.assertRaises(TypeError, operator.add, None, 1j) def test_sub(self): - self.assertEqual(1j - 1, complex(-1, 1)) - self.assertEqual(1j - (-1), complex(1, 1)) + self.assertEqual(1j - int(+1), complex(-1, 1)) + self.assertEqual(1j - int(-1), complex(1, 1)) self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) self.assertRaises(TypeError, operator.sub, 1j, None) self.assertRaises(TypeError, operator.sub, None, 1j) def test_mul(self): - self.assertEqual(1j * 2, complex(0, 2)) - self.assertEqual(1j * (-1), complex(0, -1)) + self.assertEqual(1j * int(20), complex(0, 20)) + self.assertEqual(1j * int(-1), complex(0, -1)) self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) self.assertRaises(TypeError, operator.mul, 1j, None) self.assertRaises(TypeError, operator.mul, None, 1j) @@ -333,18 +333,12 @@ def test_boolcontext(self): for i in range(100): self.assertTrue(complex(random() + 1e-6, random() + 1e-6)) self.assertTrue(not complex(0.0, 0.0)) - - def test_bool(self): self.assertTrue(1j) def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) def test_constructor(self): - class Complex: - def __complex__(self): - return 4.25+0.5j - class NS: def __init__(self, value): self.value = value def __complex__(self): return self.value @@ -354,7 +348,7 @@ def __complex__(self): return self.value self.assertRaises(TypeError, complex, NS(1.5)) self.assertRaises(TypeError, complex, NS(1)) self.assertRaises(TypeError, complex, object()) - self.assertRaises(TypeError, complex, Complex(), object()) + self.assertRaises(TypeError, complex, NS(4.25+0.5j), object()) self.assertAlmostEqual(complex("1+10j"), 1+10j) self.assertAlmostEqual(complex(10), 10+0j) @@ -653,6 +647,7 @@ class ComplexSubclass(complex): self.assertEqual(+(1+6j), 1+6j) self.assertEqual(+ComplexSubclass(1, 6), 1+6j) + self.assertIs(type(+ComplexSubclass(1, 6)), complex) def test_neg(self): self.assertEqual(-(1+6j), -1-6j) From 3494b0d0ad8a9cbf7c5a715e948540e017a8aed6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 14 Nov 2023 13:58:22 +0300 Subject: [PATCH 24/38] address review: comment in complex_hash() --- Objects/complexobject.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 59e196ec7f8b97..7e49e4666ae3de 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -412,9 +412,10 @@ complex_hash(PyComplexObject *v) { Py_uhash_t hashreal, hashimag, combined; hashreal = (Py_uhash_t)_Py_HashDouble((PyObject *) v, v->cval.real); - assert(hashreal != (Py_uhash_t)-1); hashimag = (Py_uhash_t)_Py_HashDouble((PyObject *)v, v->cval.imag); - assert(hashimag != (Py_uhash_t)-1); + /* In current implementation of hasing for numberic types, + * -1 is reserved. */ + assert(hashreal != (Py_uhash_t)-1 && hashimag != (Py_uhash_t)-1); /* Note: if the imaginary part is 0, hashimag is 0 now, * so the following returns hashreal unchanged. This is * important because numbers of different types that From 17236ee481ad56e9cb745d1a5b8087c15bcdaccf Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 14 Nov 2023 13:58:53 +0300 Subject: [PATCH 25/38] address review: simplify _PY_C_FUNC2 macro --- Lib/test/test_capi/test_complex.py | 31 +++++++++++++++++------------- Modules/_testcapi/complex.c | 15 +++------------ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index b0959c42a74d44..5cd95c0257801b 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -1,4 +1,5 @@ from math import isnan +import errno import unittest import warnings @@ -146,13 +147,13 @@ def test_py_c_sum(self): # Test _Py_c_sum() _py_c_sum = _testcapi._py_c_sum - self.assertEqual(_py_c_sum(1, 1j), 1+1j) + self.assertEqual(_py_c_sum(1, 1j), (1+1j, 0)) def test_py_c_diff(self): # Test _Py_c_diff() _py_c_diff = _testcapi._py_c_diff - self.assertEqual(_py_c_diff(1, 1j), 1-1j) + self.assertEqual(_py_c_diff(1, 1j), (1-1j, 0)) def test_py_c_neg(self): # Test _Py_c_neg() @@ -164,32 +165,36 @@ def test_py_c_prod(self): # Test _Py_c_prod() _py_c_prod = _testcapi._py_c_prod - self.assertEqual(_py_c_prod(2, 1j), 2j) + self.assertEqual(_py_c_prod(2, 1j), (2j, 0)) def test_py_c_quot(self): # Test _Py_c_quot() _py_c_quot = _testcapi._py_c_quot - self.assertEqual(_py_c_quot(1, 1j), -1j) - self.assertEqual(_py_c_quot(1j, 2), 0.5j) - self.assertEqual(_py_c_quot(1, 2j), -0.5j) + self.assertEqual(_py_c_quot(1, 1j), (-1j, 0)) + self.assertEqual(_py_c_quot(1j, 2), (0.5j, 0)) + self.assertEqual(_py_c_quot(1, 2j), (-0.5j, 0)) - z = _py_c_quot(float('nan'), 1j) + z, e = _py_c_quot(float('nan'), 1j) self.assertTrue(isnan(z.real)) self.assertTrue(isnan(z.imag)) + self.assertEqual(e, 0) - self.assertRaises(ZeroDivisionError, _py_c_quot, 1, 0j) + self.assertEqual(_py_c_quot(1, 0j)[1], errno.EDOM) def test_py_c_pow(self): # Test _Py_c_pow() _py_c_pow = _testcapi._py_c_pow - self.assertEqual(_py_c_pow(1j, 0+0j), 1+0j) - self.assertEqual(_py_c_pow(0j, 1), 0j) - self.assertAlmostEqual(_py_c_pow(1+1j, -1), 0.5-0.5j) + self.assertEqual(_py_c_pow(1j, 0+0j), (1+0j, 0)) + self.assertEqual(_py_c_pow(0j, 1), (0j, 0)) - self.assertRaises(ZeroDivisionError, _py_c_pow, 0j, -1) - self.assertRaises(OverflowError, _py_c_pow, 1e200+1j, 1e200+1j) + r, e = _py_c_pow(1+1j, -1) + self.assertAlmostEqual(r, 0.5-0.5j) + self.assertEqual(e, 0) + + self.assertEqual(_py_c_pow(0j, -1)[1], errno.EDOM) + self.assertEqual(_py_c_pow(1e200+1j, 1e200+1j)[1], errno.ERANGE) if __name__ == "__main__": diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index 6ee71e26862c47..d3c0456a53edac 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -115,18 +115,9 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) res = _Py_c_##suffix(num, exp); \ _Py_ADJUST_ERANGE2(res.real, res.imag); \ \ - if (errno == EDOM) { \ - PyErr_SetString(PyExc_ZeroDivisionError, \ - "complex division by zero"); \ - return NULL; \ - } \ - else if (errno == ERANGE) { \ - PyErr_SetString(PyExc_OverflowError, \ - "complex exponentiation"); \ - return NULL; \ - } \ - \ - return PyComplex_FromCComplex(res); \ + return PyTuple_Pack(2, \ + PyComplex_FromCComplex(res), \ + PyLong_FromLong(errno)); \ }; _PY_C_FUNC2(sum) From aea52edc6f87af769240a1a5bc7839c1cb12776c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 16 Nov 2023 13:47:43 +0300 Subject: [PATCH 26/38] Drop pycore_complexobject.h --- Modules/_testcapi/complex.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index d3c0456a53edac..a7ebfd0cf10676 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -1,7 +1,6 @@ #include "parts.h" #include "util.h" #define Py_BUILD_CORE -#include "pycore_complexobject.h" // _Py_c_*() #include "pycore_pymath.h" // _Py_ADJUST_ERANGE2() From 589a2e461c763002aeb077a5d92f13b85a848ae9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 16 Nov 2023 15:18:08 +0300 Subject: [PATCH 27/38] + test _Py_c_abs() --- Lib/test/test_capi/test_complex.py | 35 ++++++++++++++++++++++++++++-- Modules/_testcapi/complex.c | 20 +++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 5cd95c0257801b..51fbe90d577c64 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -12,6 +12,9 @@ _testcapi = import_helper.import_module('_testcapi') NULL = None +INF = float("inf") +NAN = float("nan") + class BadComplex3: def __complex__(self): @@ -172,10 +175,17 @@ def test_py_c_quot(self): _py_c_quot = _testcapi._py_c_quot self.assertEqual(_py_c_quot(1, 1j), (-1j, 0)) + self.assertEqual(_py_c_quot(1, -1j), (1j, 0)) self.assertEqual(_py_c_quot(1j, 2), (0.5j, 0)) + self.assertEqual(_py_c_quot(1j, -2), (-0.5j, 0)) self.assertEqual(_py_c_quot(1, 2j), (-0.5j, 0)) - z, e = _py_c_quot(float('nan'), 1j) + z, e = _py_c_quot(NAN, 1j) + self.assertTrue(isnan(z.real)) + self.assertTrue(isnan(z.imag)) + self.assertEqual(e, 0) + + z, e = _py_c_quot(1j, NAN) self.assertTrue(isnan(z.real)) self.assertTrue(isnan(z.imag)) self.assertEqual(e, 0) @@ -186,16 +196,37 @@ def test_py_c_pow(self): # Test _Py_c_pow() _py_c_pow = _testcapi._py_c_pow - self.assertEqual(_py_c_pow(1j, 0+0j), (1+0j, 0)) + self.assertEqual(_py_c_pow(1j, 0j), (1+0j, 0)) + self.assertEqual(_py_c_pow(1, 1j), (1+0j, 0)) self.assertEqual(_py_c_pow(0j, 1), (0j, 0)) + self.assertAlmostEqual(_py_c_pow(1j, 2)[0], -1.0+0j) r, e = _py_c_pow(1+1j, -1) self.assertAlmostEqual(r, 0.5-0.5j) self.assertEqual(e, 0) self.assertEqual(_py_c_pow(0j, -1)[1], errno.EDOM) + self.assertEqual(_py_c_pow(0j, 1j)[1], errno.EDOM) self.assertEqual(_py_c_pow(1e200+1j, 1e200+1j)[1], errno.ERANGE) + def test_py_c_abs(self): + # Test _Py_c_abs() + _py_c_abs = _testcapi._py_c_abs + + self.assertEqual(_py_c_abs(-1), (1.0, 0)) + self.assertEqual(_py_c_abs(1j), (1.0, 0)) + + self.assertEqual(_py_c_abs(complex('+inf+1j')), (INF, 0)) + self.assertEqual(_py_c_abs(complex('-inf+1j')), (INF, 0)) + self.assertEqual(_py_c_abs(complex('1.25+infj')), (INF, 0)) + self.assertEqual(_py_c_abs(complex('1.25-infj')), (INF, 0)) + + self.assertTrue(isnan(_py_c_abs(complex('1.25+nanj'))[0])) + self.assertTrue(isnan(_py_c_abs(complex('nan-1j'))[0])) + + self.assertEqual(_py_c_abs(complex(1.4e308, 1.4e308))[1], errno.ERANGE) + + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index a7ebfd0cf10676..c5efa711130b84 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -125,6 +125,25 @@ _PY_C_FUNC2(prod) _PY_C_FUNC2(quot) _PY_C_FUNC2(pow) +static PyObject* +_py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) +{ + Py_complex complex; + double res; + + NULLABLE(obj); + complex = PyComplex_AsCComplex(obj); + + if (complex.real == -1. && PyErr_Occurred()) { + return NULL; + } + + errno = 0; + res = _Py_c_abs(complex); + return PyTuple_Pack(2, PyFloat_FromDouble(res), + PyLong_FromLong(errno)); +} + static PyMethodDef test_methods[] = { {"complex_check", complex_check, METH_O}, @@ -140,6 +159,7 @@ static PyMethodDef test_methods[] = { {"_py_c_prod", _py_c_prod, METH_VARARGS}, {"_py_c_quot", _py_c_quot, METH_VARARGS}, {"_py_c_pow", _py_c_pow, METH_VARARGS}, + {"_py_c_abs", _py_c_abs, METH_O}, {NULL}, }; From 2f63950601cca70299c6e83afa37ee07ae48dab0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Nov 2023 14:14:24 +0300 Subject: [PATCH 28/38] Oops, typo fixed --- Objects/complexobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 7e49e4666ae3de..f07295ac47b1e6 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -413,7 +413,7 @@ complex_hash(PyComplexObject *v) Py_uhash_t hashreal, hashimag, combined; hashreal = (Py_uhash_t)_Py_HashDouble((PyObject *) v, v->cval.real); hashimag = (Py_uhash_t)_Py_HashDouble((PyObject *)v, v->cval.imag); - /* In current implementation of hasing for numberic types, + /* In current implementation of hashing for numberic types, * -1 is reserved. */ assert(hashreal != (Py_uhash_t)-1 && hashimag != (Py_uhash_t)-1); /* Note: if the imaginary part is 0, hashimag is 0 now, From f73c90731f6819c6d6d39e81d1904cfbd9eb838f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Nov 2023 14:14:46 +0300 Subject: [PATCH 29/38] address review: check for failures in test code before PyTuple_Pack'ing --- Modules/_testcapi/complex.c | 38 +++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index c5efa711130b84..7c9e3169b532e2 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -104,19 +104,27 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) static PyObject * \ _py_c_##suffix(PyObject *Py_UNUSED(module), PyObject *args) \ { \ - Py_complex num, exp, res; \ + Py_complex num, exp; \ + PyObject *res, *err; \ \ if (!PyArg_ParseTuple(args, "DD", &num, &exp)) { \ return NULL; \ } \ \ errno = 0; \ - res = _Py_c_##suffix(num, exp); \ - _Py_ADJUST_ERANGE2(res.real, res.imag); \ + num = _Py_c_##suffix(num, exp); \ + _Py_ADJUST_ERANGE2(num.real, num.imag); \ \ - return PyTuple_Pack(2, \ - PyComplex_FromCComplex(res), \ - PyLong_FromLong(errno)); \ + res = PyComplex_FromCComplex(num); \ + if (!res) { \ + return NULL; \ + } \ + err = PyLong_FromLong(errno); \ + if (!err) { \ + return NULL; \ + } \ + \ + return PyTuple_Pack(2, res, err); \ }; _PY_C_FUNC2(sum) @@ -129,7 +137,8 @@ static PyObject* _py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) { Py_complex complex; - double res; + PyObject *res, *err; + double val; NULLABLE(obj); complex = PyComplex_AsCComplex(obj); @@ -139,9 +148,18 @@ _py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) } errno = 0; - res = _Py_c_abs(complex); - return PyTuple_Pack(2, PyFloat_FromDouble(res), - PyLong_FromLong(errno)); + val = _Py_c_abs(complex); + + res = PyFloat_FromDouble(val); + if (!res) { + return NULL; + } + err = PyLong_FromLong(errno); + if (!err) { + return NULL; + } + + return PyTuple_Pack(2, res, err); } From 86c364753ef35a4d6892a5a9fa59a9b315dee377 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Nov 2023 14:16:31 +0300 Subject: [PATCH 30/38] address review: use _testcapi.DBL_MAX instead of magic numbers --- Lib/test/test_capi/test_complex.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 51fbe90d577c64..0021f6bbfb6182 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -14,6 +14,7 @@ NULL = None INF = float("inf") NAN = float("nan") +DBL_MAX = _testcapi.DBL_MAX class BadComplex3: @@ -207,7 +208,7 @@ def test_py_c_pow(self): self.assertEqual(_py_c_pow(0j, -1)[1], errno.EDOM) self.assertEqual(_py_c_pow(0j, 1j)[1], errno.EDOM) - self.assertEqual(_py_c_pow(1e200+1j, 1e200+1j)[1], errno.ERANGE) + self.assertEqual(_py_c_pow(*[DBL_MAX+1j]*2)[1], errno.ERANGE) def test_py_c_abs(self): @@ -225,7 +226,7 @@ def test_py_c_abs(self): self.assertTrue(isnan(_py_c_abs(complex('1.25+nanj'))[0])) self.assertTrue(isnan(_py_c_abs(complex('nan-1j'))[0])) - self.assertEqual(_py_c_abs(complex(1.4e308, 1.4e308))[1], errno.ERANGE) + self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) if __name__ == "__main__": From deace0fdcdd15a10bb4b96e159e56eeba198fe77 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Nov 2023 15:11:40 +0300 Subject: [PATCH 31/38] Amend f73c90731f --- Modules/_testcapi/complex.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index 7c9e3169b532e2..5f18b6cd23e661 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -121,6 +121,7 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) } \ err = PyLong_FromLong(errno); \ if (!err) { \ + Py_DECREF(res); \ return NULL; \ } \ \ @@ -156,6 +157,7 @@ _py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) } err = PyLong_FromLong(errno); if (!err) { + Py_DECREF(res); return NULL; } From f26139207a0721c240a7e6182a6059ab8bac3c23 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Nov 2023 15:57:39 +0300 Subject: [PATCH 32/38] + use Py_BuildValue --- Modules/_testcapi/complex.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index 5f18b6cd23e661..98277eb072037a 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -125,7 +125,7 @@ _py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) return NULL; \ } \ \ - return PyTuple_Pack(2, res, err); \ + return Py_BuildValue("NN", res, err); \ }; _PY_C_FUNC2(sum) @@ -161,7 +161,7 @@ _py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) return NULL; } - return PyTuple_Pack(2, res, err); + return Py_BuildValue("NN", res, err); } From 864975569d00a98bf4d810d8196c96aa8aa910cc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 9 Dec 2023 05:56:11 +0300 Subject: [PATCH 33/38] * complex_abs: L569 --- Lib/test/test_complex.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index b057121f285dc7..0ec9e0d8ca99c6 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -10,6 +10,7 @@ INF = float("inf") NAN = float("nan") +DBL_MAX = sys.float_info.max # These tests ensure that complex math does the right thing ZERO_DIVISION = ( @@ -597,6 +598,8 @@ def test_abs(self): for num in nums: self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num)) + self.assertRaises(OverflowError, abs, complex(*[DBL_MAX]*2)) + def test_repr_str(self): def test(v, expected, test_fn=self.assertEqual): test_fn(repr(v), expected) From 2a25b9a27d43117051fe90b78b1b2b8eb31cd65e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 31 May 2024 11:57:03 +0300 Subject: [PATCH 34/38] Update wrt recent changes --- Lib/test/test_complex.py | 3 +++ Objects/complexobject.c | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index fb510ca9b70902..d46d4b21ea5e91 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -510,6 +510,9 @@ def __int__(self): self.assertRaises(TypeError, complex, MyInt()) self.assertRaises(TypeError, complex, MyInt(), 1.5) self.assertRaises(TypeError, complex, 1.5, MyInt()) + self.assertRaises(TypeError, complex, object()) + with self.assertRaises(TypeError): + complex(real=object()) class complex0(complex): """Test usage of __complex__() when inheriting from 'complex'""" diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 4772f5315001d8..d9e66e53ef3cb9 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -946,13 +946,6 @@ actual_complex_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) else if (PyErr_Occurred()) { return NULL; } - else if (PyComplex_Check(arg)) { - /* Note that if arg is of a complex subtype, we're only - retaining its real & imag parts here, and the return - value is (properly) of the builtin complex type. */ - Py_complex c = ((PyComplexObject*)arg)->cval; - res = complex_subtype_from_doubles(type, c.real, c.imag); - } else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL && (nbr->nb_float != NULL || nbr->nb_index != NULL)) { From d9f2c6727685ba2c1f7721fa026d67675f05b184 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Oct 2024 13:37:40 +0300 Subject: [PATCH 35/38] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Objects/complexobject.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 514efe0da152cb..b46a72dc78e686 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -177,6 +177,7 @@ _Py_c_pow(Py_complex a, Py_complex b) return r; } +/* Switch to exponentiation by squaring is integer exponent less that this. */ #define C_EXP_CUTOFF 100 static Py_complex @@ -188,7 +189,7 @@ c_powu(Py_complex x, long n) p = x; assert(0 <= n && n <= C_EXP_CUTOFF); while (n >= mask) { - assert(mask>0); + assert(mask > 0); if (n & mask) r = _Py_c_prod(r,p); mask <<= 1; @@ -641,7 +642,7 @@ complex_richcompare(PyObject *v, PyObject *w, int op) } assert(PyComplex_Check(v)); - i = ((PyComplexObject*)v)->cval; + i = ((PyComplexObject *)v)->cval; if (PyLong_Check(w)) { /* Check for 0.0 imaginary part first to avoid the rich @@ -667,7 +668,7 @@ complex_richcompare(PyObject *v, PyObject *w, int op) else if (PyComplex_Check(w)) { Py_complex j; - j = ((PyComplexObject*)w)->cval; + j = ((PyComplexObject *)w)->cval; equal = (i.real == j.real && i.imag == j.imag); } else { From 6cad37accc23aebcf135b10d1ad2ada27da5dbe8 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Oct 2024 18:24:14 +0300 Subject: [PATCH 36/38] Update Objects/complexobject.c --- Objects/complexobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index b46a72dc78e686..d6547d24c5a214 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -462,7 +462,8 @@ complex_hash(PyComplexObject *v) hashimag = (Py_uhash_t)_Py_HashDouble((PyObject *)v, v->cval.imag); /* In current implementation of hashing for numberic types, * -1 is reserved. */ - assert(hashreal != (Py_uhash_t)-1 && hashimag != (Py_uhash_t)-1); + assert(hashreal != (Py_uhash_t)-1); + assert(hashimag != (Py_uhash_t)-1); /* Note: if the imaginary part is 0, hashimag is 0 now, * so the following returns hashreal unchanged. This is * important because numbers of different types that From f1f3d97ade00a223bdfbe5c2818eb9f91604be53 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Oct 2024 18:39:49 +0300 Subject: [PATCH 37/38] Apply suggestions from code review --- Objects/complexobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index d6547d24c5a214..48febab2fbb99b 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -187,7 +187,8 @@ c_powu(Py_complex x, long n) long mask = 1; r = c_1; p = x; - assert(0 <= n && n <= C_EXP_CUTOFF); + assert(0 <= n); + assert(n <= C_EXP_CUTOFF); while (n >= mask) { assert(mask > 0); if (n & mask) From 65c30f1c0dc7f6eb73dcc1ea23b7847c80396b49 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 13 Oct 2024 12:38:41 +0300 Subject: [PATCH 38/38] Add test for complex_from_number --- Lib/test/test_complex.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 6346ed36d1fdc1..7364bf4338a0c7 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -670,6 +670,7 @@ def eq(actual, expected): eq(cls.from_number(3.14), 3.14+0j) eq(cls.from_number(3.14j), 3.14j) eq(cls.from_number(314), 314.0+0j) + eq(cls.from_number(-1), -1+0j) eq(cls.from_number(OtherComplexSubclass(3.14, 2.72)), 3.14+2.72j) eq(cls.from_number(WithComplex(3.14+2.72j)), 3.14+2.72j) eq(cls.from_number(WithFloat(3.14)), 3.14+0j)