gh-69639: Add mixed-mode rules for complex arithmetic (C-like) (GH-124829)

"Generally, mixed-mode arithmetic combining real and complex variables should
be performed directly, not by first coercing the real to complex, lest the sign
of zero be rendered uninformative; the same goes for combinations of pure
imaginary quantities with complex variables." (c) Kahan, W: Branch cuts for
complex elementary functions.

This patch implements mixed-mode arithmetic rules, combining real and
complex variables as specified by C standards since C99 (in particular,
there is no special version for the true division with real lhs
operand).  Most C compilers implementing C99+ Annex G have only these
special rules (without support for imaginary type, which is going to be
deprecated in C2y).
This commit is contained in:
Sergey B Kirpichev 2024-11-26 18:57:39 +03:00 committed by GitHub
parent dcf629213b
commit 987311d42e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 449 additions and 98 deletions

View file

@ -126,6 +126,12 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
z = complex(0, 0) / complex(denom_real, denom_imag)
self.assertTrue(isnan(z.real))
self.assertTrue(isnan(z.imag))
z = float(0) / complex(denom_real, denom_imag)
self.assertTrue(isnan(z.real))
self.assertTrue(isnan(z.imag))
self.assertComplexesAreIdentical(complex(INF, NAN) / 2,
complex(INF, NAN))
self.assertComplexesAreIdentical(complex(INF, 1)/(0.0+1j),
complex(NAN, -INF))
@ -154,6 +160,39 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
self.assertComplexesAreIdentical(complex(INF, 1)/complex(1, INF),
complex(NAN, NAN))
# mixed types
self.assertEqual((1+1j)/float(2), 0.5+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)
self.assertEqual(float(1)/(2+1j), 0.4-0.2j)
self.assertEqual(float(1)/(-2+1j), -0.4-0.2j)
self.assertEqual(float(1)/(2-1j), 0.4+0.2j)
self.assertComplexesAreIdentical(INF/(1+0j),
complex(INF, NAN))
self.assertComplexesAreIdentical(INF/(0.0+1j),
complex(NAN, -INF))
self.assertComplexesAreIdentical(INF/complex(2**1000, 2**-1000),
complex(INF, NAN))
self.assertComplexesAreIdentical(INF/complex(NAN, NAN),
complex(NAN, NAN))
self.assertComplexesAreIdentical(float(1)/complex(INF, INF), (0.0-0j))
self.assertComplexesAreIdentical(float(1)/complex(INF, -INF), (0.0+0j))
self.assertComplexesAreIdentical(float(1)/complex(-INF, INF),
complex(-0.0, -0.0))
self.assertComplexesAreIdentical(float(1)/complex(-INF, -INF),
complex(-0.0, 0))
self.assertComplexesAreIdentical(float(1)/complex(INF, NAN),
complex(0.0, -0.0))
self.assertComplexesAreIdentical(float(1)/complex(-INF, NAN),
complex(-0.0, -0.0))
self.assertComplexesAreIdentical(float(1)/complex(NAN, INF),
complex(0.0, -0.0))
self.assertComplexesAreIdentical(float(INF)/complex(NAN, INF),
complex(NAN, NAN))
def test_truediv_zero_division(self):
for a, b in ZERO_DIVISION:
with self.assertRaises(ZeroDivisionError):
@ -224,6 +263,10 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
def test_add(self):
self.assertEqual(1j + int(+1), complex(+1, 1))
self.assertEqual(1j + int(-1), complex(-1, 1))
self.assertComplexesAreIdentical(complex(-0.0, -0.0) + (-0.0),
complex(-0.0, -0.0))
self.assertComplexesAreIdentical((-0.0) + complex(-0.0, -0.0),
complex(-0.0, -0.0))
self.assertRaises(OverflowError, operator.add, 1j, 10**1000)
self.assertRaises(TypeError, operator.add, 1j, None)
self.assertRaises(TypeError, operator.add, None, 1j)
@ -231,6 +274,14 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
def test_sub(self):
self.assertEqual(1j - int(+1), complex(-1, 1))
self.assertEqual(1j - int(-1), complex(1, 1))
self.assertComplexesAreIdentical(complex(-0.0, -0.0) - 0.0,
complex(-0.0, -0.0))
self.assertComplexesAreIdentical(-0.0 - complex(0.0, 0.0),
complex(-0.0, -0.0))
self.assertComplexesAreIdentical(complex(1, 2) - complex(2, 1),
complex(-1, 1))
self.assertComplexesAreIdentical(complex(2, 1) - complex(1, 2),
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)
@ -238,6 +289,12 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
def test_mul(self):
self.assertEqual(1j * int(20), complex(0, 20))
self.assertEqual(1j * int(-1), complex(0, -1))
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))]:
with self.subTest(c=c, r=r):
self.assertComplexesAreIdentical(complex(INF, 1) * c, r)
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, None, 1j)