Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
126 changes: 126 additions & 0 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
from test import support
from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
INVALID_UNDERSCORE_LITERALS)
from test.support import import_helper
from test.test_capi.test_getargs import (BadComplex, BadComplex2, Complex,
FloatSubclass, Float, BadFloat,
BadFloat2, ComplexSubclass)

from random import random
from math import atan2, isnan, copysign
import operator

_testcapi = import_helper.import_module('_testcapi')

INF = float("inf")
NAN = float("nan")
# These tests ensure that complex math does the right thing
Expand All @@ -20,6 +26,8 @@
(1, 0+0j),
)

NULL = None

class ComplexTest(unittest.TestCase):

def assertAlmostEqual(self, a, b):
Expand Down Expand Up @@ -72,6 +80,15 @@ def assertFloatsAreIdentical(self, x, y):
msg += ': zeros have different signs'
self.fail(msg.format(x, y))

def assertComplexesAreIdentical(self, x, y):
"""assert that complex numbers x and y are identical

I.e. they have identical real and imag components.

"""
(self.assertFloatsAreIdentical(x.real, y.real)
and self.assertFloatsAreIdentical(x.imag, y.imag))

def assertClose(self, x, y, eps=1e-9):
"""Return true iff complexes x and y "are close"."""
self.assertCloseAbs(x.real, y.real, eps)
Expand Down Expand Up @@ -792,5 +809,114 @@ def test_format(self):
self.assertEqual(format(complex(INF, -1), 'F'), 'INF-1.000000j')


class CAPIComplexTest(ComplexTest):
def test_check(self):
# Test PyComplex_Check()
check = _testcapi.complex_check

self.assertTrue(check(1+2j))
self.assertTrue(check(ComplexSubclass(1+2j)))
self.assertFalse(check(Complex()))
self.assertFalse(check(3))
self.assertFalse(check([]))
self.assertFalse(check(object()))

# CRASHES check(NULL)

def test_checkexact(self):
# PyComplex_CheckExact()
checkexact = _testcapi.complex_checkexact

self.assertTrue(checkexact(1+2j))
self.assertFalse(checkexact(ComplexSubclass(1+2j)))
self.assertFalse(checkexact(Complex()))
self.assertFalse(checkexact(3))
self.assertFalse(checkexact([]))
self.assertFalse(checkexact(object()))

# CRASHES checkexact(NULL)

def test_fromccomplex(self):
# Test PyComplex_FromCComplex()
fromccomplex = _testcapi.complex_fromccomplex

self.assertComplexesAreIdentical(fromccomplex(1+2j), 1.0+2.0j)

def test_fromdoubles(self):
# Test PyComplex_FromDoubles()
fromdoubles = _testcapi.complex_fromdoubles

self.assertComplexesAreIdentical(fromdoubles(1.0, 2.0), 1.0+2.0j)

def test_realasdouble(self):
# Test PyComplex_RealAsDouble()
realasdouble = _testcapi.complex_realasdouble

self.assertFloatsAreIdentical(realasdouble(1+2j), 1.0)
self.assertFloatsAreIdentical(realasdouble(1), 1.0)
self.assertFloatsAreIdentical(realasdouble(-1), -1.0)
# Function doesn't support classes with __complex__ dunder, see #109598
#self.assertFloatsAreIdentical(realasdouble(Complex()), 4.25)
#self.assertFloatsAreIdentical(realasdouble(3.14), 3.14)
#self.assertFloatsAreIdentical(realasdouble(FloatSubclass(3.14)), 3.14)
#self.assertFloatsAreIdentical(realasdouble(Float()), 4.25)
#with self.assertWarns(DeprecationWarning):
# self.assertFloatsAreIdentical(realasdouble(BadComplex2()), 4.25)
#with self.assertWarns(DeprecationWarning):
# self.assertFloatsAreIdentical(realasdouble(BadFloat2()), 4.25)
self.assertRaises(TypeError, realasdouble, BadComplex())
self.assertRaises(TypeError, realasdouble, BadFloat())
self.assertRaises(TypeError, realasdouble, object())

# CRASHES realasdouble(NULL)

def test_imagasdouble(self):
# Test PyComplex_ImagAsDouble()
imagasdouble = _testcapi.complex_imagasdouble

self.assertFloatsAreIdentical(imagasdouble(1+2j), 2.0)
self.assertFloatsAreIdentical(imagasdouble(1), 0.0)
self.assertFloatsAreIdentical(imagasdouble(-1), 0.0)
# Function doesn't support classes with __complex__ dunder, see #109598
#self.assertFloatsAreIdentical(imagasdouble(Complex()), 0.5)
#self.assertFloatsAreIdentical(imagasdouble(3.14), 0.0)
#self.assertFloatsAreIdentical(imagasdouble(FloatSubclass(3.14)), 0.0)
#self.assertFloatsAreIdentical(imagasdouble(Float()), 0.0)
#with self.assertWarns(DeprecationWarning):
# self.assertFloatsAreIdentical(imagasdouble(BadComplex2()), 0.5)
#with self.assertWarns(DeprecationWarning):
# self.assertFloatsAreIdentical(imagasdouble(BadFloat2()), 0.0)
# Function returns 0.0 anyway, see #109598
#self.assertRaises(TypeError, imagasdouble, BadComplex())
#self.assertRaises(TypeError, imagasdouble, BadFloat())
#self.assertRaises(TypeError, imagasdouble, object())
self.assertFloatsAreIdentical(imagasdouble(BadComplex()), 0.0)
self.assertFloatsAreIdentical(imagasdouble(BadFloat()), 0.0)
self.assertFloatsAreIdentical(imagasdouble(object()), 0.0)

# CRASHES imagasdouble(NULL)

def test_asccomplex(self):
# Test PyComplex_AsCComplex()
asccomplex = _testcapi.complex_asccomplex

self.assertComplexesAreIdentical(asccomplex(1+2j), 1.0+2.0j)
self.assertComplexesAreIdentical(asccomplex(1), 1.0+0.0j)
self.assertComplexesAreIdentical(asccomplex(-1), -1.0+0.0j)
self.assertComplexesAreIdentical(asccomplex(Complex()), 4.25+0.5j)
self.assertComplexesAreIdentical(asccomplex(3.14), 3.14+0.0j)
self.assertComplexesAreIdentical(asccomplex(FloatSubclass(3.14)), 3.14+0.0j)
self.assertComplexesAreIdentical(asccomplex(Float()), 4.25+0.0j)
with self.assertWarns(DeprecationWarning):
self.assertComplexesAreIdentical(asccomplex(BadComplex2()), 4.25+0.5j)
with self.assertWarns(DeprecationWarning):
self.assertComplexesAreIdentical(asccomplex(BadFloat2()), 4.25+0.0j)
self.assertRaises(TypeError, asccomplex, BadComplex())
self.assertRaises(TypeError, asccomplex, BadFloat())
self.assertRaises(TypeError, asccomplex, object())

# CRASHES asccomplex(NULL)


if __name__ == "__main__":
unittest.main()
88 changes: 88 additions & 0 deletions Modules/_testcapi/complex.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,95 @@
#include "parts.h"
#include "util.h"


static PyObject *
complex_check(PyObject *Py_UNUSED(module), PyObject *obj)
{
NULLABLE(obj);
return PyLong_FromLong(PyComplex_Check(obj));
}

static PyObject *
complex_checkexact(PyObject *Py_UNUSED(module), PyObject *obj)
{
NULLABLE(obj);
return PyLong_FromLong(PyComplex_CheckExact(obj));
}

static PyObject *
complex_fromccomplex(PyObject *Py_UNUSED(module), PyObject *obj)
{
Py_complex complex = ((PyComplexObject*)obj)->cval;

return PyComplex_FromCComplex(complex);
}

static PyObject *
complex_fromdoubles(PyObject *Py_UNUSED(module), PyObject *args)
{
double real, imag;

if (!PyArg_ParseTuple(args, "dd", &real, &imag)) {
return NULL;
}

return PyComplex_FromDoubles(real, imag);
}

static PyObject *
complex_realasdouble(PyObject *Py_UNUSED(module), PyObject *obj)
{
double real;

NULLABLE(obj);
real = PyComplex_RealAsDouble(obj);

if (real == -1. && PyErr_Occurred()) {
return NULL;
}

return PyFloat_FromDouble(real);
}

static PyObject *
complex_imagasdouble(PyObject *Py_UNUSED(module), PyObject *obj)
{
double imag;

NULLABLE(obj);
imag = PyComplex_ImagAsDouble(obj);

if (imag == -1. && PyErr_Occurred()) {
return NULL;
}

return PyFloat_FromDouble(imag);
}

static PyObject *
complex_asccomplex(PyObject *Py_UNUSED(module), PyObject *obj)
{
Py_complex complex;

NULLABLE(obj);
complex = PyComplex_AsCComplex(obj);

if (complex.real == -1. && PyErr_Occurred()) {
return NULL;
}

return PyComplex_FromCComplex(complex);
}


static PyMethodDef test_methods[] = {
{"complex_check", complex_check, METH_O},
{"complex_checkexact", complex_checkexact, METH_O},
{"complex_fromccomplex", complex_fromccomplex, METH_O},
{"complex_fromdoubles", complex_fromdoubles, METH_VARARGS},
{"complex_realasdouble", complex_realasdouble, METH_O},
{"complex_imagasdouble", complex_imagasdouble, METH_O},
{"complex_asccomplex", complex_asccomplex, METH_O},
{NULL},
};

Expand Down