bpo-33089: Multidimensional math.hypot() (GH-8474)

This commit is contained in:
Raymond Hettinger 2018-07-28 07:48:04 -07:00 committed by GitHub
parent 5032692746
commit c6dabe37e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 82 deletions

View file

@ -16,6 +16,7 @@ NAN = float('nan')
INF = float('inf')
NINF = float('-inf')
FLOAT_MAX = sys.float_info.max
FLOAT_MIN = sys.float_info.min
# detect evidence of double-rounding: fsum is not always correctly
# rounded on machines that suffer from double rounding.
@ -720,16 +721,71 @@ class MathTests(unittest.TestCase):
self.assertEqual(gcd(MyIndexable(120), MyIndexable(84)), 12)
def testHypot(self):
self.assertRaises(TypeError, math.hypot)
self.ftest('hypot(0,0)', math.hypot(0,0), 0)
self.ftest('hypot(3,4)', math.hypot(3,4), 5)
self.assertEqual(math.hypot(NAN, INF), INF)
self.assertEqual(math.hypot(INF, NAN), INF)
self.assertEqual(math.hypot(NAN, NINF), INF)
self.assertEqual(math.hypot(NINF, NAN), INF)
self.assertRaises(OverflowError, math.hypot, FLOAT_MAX, FLOAT_MAX)
self.assertTrue(math.isnan(math.hypot(1.0, NAN)))
self.assertTrue(math.isnan(math.hypot(NAN, -2.0)))
from decimal import Decimal
from fractions import Fraction
hypot = math.hypot
# Test different numbers of arguments (from zero to five)
# against a straightforward pure python implementation
args = math.e, math.pi, math.sqrt(2.0), math.gamma(3.5), math.sin(2.1)
for i in range(len(args)+1):
self.assertAlmostEqual(
hypot(*args[:i]),
math.sqrt(sum(s**2 for s in args[:i]))
)
# Test allowable types (those with __float__)
self.assertEqual(hypot(12.0, 5.0), 13.0)
self.assertEqual(hypot(12, 5), 13)
self.assertEqual(hypot(Decimal(12), Decimal(5)), 13)
self.assertEqual(hypot(Fraction(12, 32), Fraction(5, 32)), Fraction(13, 32))
self.assertEqual(hypot(bool(1), bool(0), bool(1), bool(1)), math.sqrt(3))
# Test corner cases
self.assertEqual(hypot(0.0, 0.0), 0.0) # Max input is zero
self.assertEqual(hypot(-10.5), 10.5) # Negative input
self.assertEqual(hypot(), 0.0) # Negative input
self.assertEqual(1.0,
math.copysign(1.0, hypot(-0.0)) # Convert negative zero to positive zero
)
# Test handling of bad arguments
with self.assertRaises(TypeError): # Reject keyword args
hypot(x=1)
with self.assertRaises(TypeError): # Reject values without __float__
hypot(1.1, 'string', 2.2)
# Any infinity gives positive infinity.
self.assertEqual(hypot(INF), INF)
self.assertEqual(hypot(0, INF), INF)
self.assertEqual(hypot(10, INF), INF)
self.assertEqual(hypot(-10, INF), INF)
self.assertEqual(hypot(NAN, INF), INF)
self.assertEqual(hypot(INF, NAN), INF)
self.assertEqual(hypot(NINF, NAN), INF)
self.assertEqual(hypot(NAN, NINF), INF)
self.assertEqual(hypot(-INF, INF), INF)
self.assertEqual(hypot(-INF, -INF), INF)
self.assertEqual(hypot(10, -INF), INF)
# If no infinity, any NaN gives a Nan.
self.assertTrue(math.isnan(hypot(NAN)))
self.assertTrue(math.isnan(hypot(0, NAN)))
self.assertTrue(math.isnan(hypot(NAN, 10)))
self.assertTrue(math.isnan(hypot(10, NAN)))
self.assertTrue(math.isnan(hypot(NAN, NAN)))
self.assertTrue(math.isnan(hypot(NAN)))
# Verify scaling for extremely large values
fourthmax = FLOAT_MAX / 4.0
for n in range(32):
self.assertEqual(hypot(*([fourthmax]*n)), fourthmax * math.sqrt(n))
# Verify scaling for extremely small values
for exp in range(32):
scale = FLOAT_MIN / 2.0 ** exp
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)
def testLdexp(self):
self.assertRaises(TypeError, math.ldexp)