Skip to content

[3.12] gh-109802: Increase test coverage for complexobject.c (GH-112452) #112489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions Lib/test/test_capi/test_complex.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from math import isnan
import errno
import unittest
import warnings

Expand All @@ -10,6 +12,10 @@
_testcapi = import_helper.import_module('_testcapi')

NULL = None
INF = float("inf")
NAN = float("nan")
DBL_MAX = _testcapi.DBL_MAX


class BadComplex3:
def __complex__(self):
Expand Down Expand Up @@ -141,6 +147,87 @@ 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, 0))

def test_py_c_diff(self):
# Test _Py_c_diff()
_py_c_diff = _testcapi._py_c_diff

self.assertEqual(_py_c_diff(1, 1j), (1-1j, 0))

def test_py_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, 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, 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(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)

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, 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(*[DBL_MAX+1j]*2)[0], complex(*[INF]*2))


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(*[DBL_MAX]*2))[1], errno.ERANGE)


if __name__ == "__main__":
unittest.main()
47 changes: 47 additions & 0 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -140,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)
Expand All @@ -162,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):
Expand All @@ -180,6 +184,27 @@ 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.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 - 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 * 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)

def test_mod(self):
# % is no longer supported on complex numbers
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -212,11 +237,18 @@ 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.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)
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)
Expand Down Expand Up @@ -301,6 +333,7 @@ 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))
self.assertTrue(1j)

def test_conjugate(self):
self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j)
Expand All @@ -314,6 +347,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, NS(4.25+0.5j), object())

self.assertAlmostEqual(complex("1+10j"), 1+10j)
self.assertAlmostEqual(complex(10), 10+0j)
Expand Down Expand Up @@ -359,6 +394,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.assertEqual(complex('1-1j'), 1.0 - 1j)
self.assertEqual(complex('1J'), 1j)

class complex2(complex): pass
self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j)
Expand Down Expand Up @@ -553,6 +590,8 @@ def test_hash(self):
x /= 3.0 # now check against floating point
self.assertEqual(hash(x), hash(complex(x, 0.)))

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)]
for num in nums:
Expand Down Expand Up @@ -602,6 +641,14 @@ def test(v, expected, test_fn=self.assertEqual):
test(complex(-0., 0.), "(-0+0j)")
test(complex(-0., -0.), "(-0-0j)")

def test_pos(self):
class ComplexSubclass(complex):
pass

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)

Expand Down
59 changes: 59 additions & 0 deletions Modules/_testcapi/complex.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,58 @@ 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); \
return Py_BuildValue("Di", &res, errno); \
};

_PY_C_FUNC2(sum)
_PY_C_FUNC2(diff)
_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 Py_BuildValue("di", res, errno);
}


static PyMethodDef test_methods[] = {
{"complex_check", complex_check, METH_O},
Expand All @@ -94,6 +146,13 @@ 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},
{"_py_c_abs", _py_c_abs, METH_O},
{NULL},
};

Expand Down