mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
bpo-33089: Add math.dist() for computing the Euclidean distance between two points (GH-8561)
This commit is contained in:
parent
9d5727326a
commit
9c18b1ae52
5 changed files with 236 additions and 1 deletions
|
@ -4,9 +4,11 @@
|
|||
from test.support import run_unittest, verbose, requires_IEEE_754
|
||||
from test import support
|
||||
import unittest
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import struct
|
||||
import sys
|
||||
import sysconfig
|
||||
|
@ -787,6 +789,107 @@ class MathTests(unittest.TestCase):
|
|||
scale = FLOAT_MIN / 2.0 ** exp
|
||||
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)
|
||||
|
||||
def testDist(self):
|
||||
from decimal import Decimal as D
|
||||
from fractions import Fraction as F
|
||||
|
||||
dist = math.dist
|
||||
sqrt = math.sqrt
|
||||
|
||||
# Simple exact case
|
||||
self.assertEqual(dist((1, 2, 3), (4, 2, -1)), 5.0)
|
||||
|
||||
# Test different numbers of arguments (from zero to nine)
|
||||
# against a straightforward pure python implementation
|
||||
for i in range(9):
|
||||
for j in range(5):
|
||||
p = tuple(random.uniform(-5, 5) for k in range(i))
|
||||
q = tuple(random.uniform(-5, 5) for k in range(i))
|
||||
self.assertAlmostEqual(
|
||||
dist(p, q),
|
||||
sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q)))
|
||||
)
|
||||
|
||||
# Test allowable types (those with __float__)
|
||||
self.assertEqual(dist((14.0, 1.0), (2.0, -4.0)), 13.0)
|
||||
self.assertEqual(dist((14, 1), (2, -4)), 13)
|
||||
self.assertEqual(dist((D(14), D(1)), (D(2), D(-4))), D(13))
|
||||
self.assertEqual(dist((F(14, 32), F(1, 32)), (F(2, 32), F(-4, 32))),
|
||||
F(13, 32))
|
||||
self.assertEqual(dist((True, True, False, True, False),
|
||||
(True, False, True, True, False)),
|
||||
sqrt(2.0))
|
||||
|
||||
# Test corner cases
|
||||
self.assertEqual(dist((13.25, 12.5, -3.25),
|
||||
(13.25, 12.5, -3.25)),
|
||||
0.0) # Distance with self is zero
|
||||
self.assertEqual(dist((), ()), 0.0) # Zero-dimensional case
|
||||
self.assertEqual(1.0, # Convert negative zero to positive zero
|
||||
math.copysign(1.0, dist((-0.0,), (0.0,)))
|
||||
)
|
||||
self.assertEqual(1.0, # Convert negative zero to positive zero
|
||||
math.copysign(1.0, dist((0.0,), (-0.0,)))
|
||||
)
|
||||
|
||||
# Verify tuple subclasses are allowed
|
||||
class T(tuple): # tuple subclas
|
||||
pass
|
||||
self.assertEqual(dist(T((1, 2, 3)), ((4, 2, -1))), 5.0)
|
||||
|
||||
# Test handling of bad arguments
|
||||
with self.assertRaises(TypeError): # Reject keyword args
|
||||
dist(p=(1, 2, 3), q=(4, 5, 6))
|
||||
with self.assertRaises(TypeError): # Too few args
|
||||
dist((1, 2, 3))
|
||||
with self.assertRaises(TypeError): # Too many args
|
||||
dist((1, 2, 3), (4, 5, 6), (7, 8, 9))
|
||||
with self.assertRaises(TypeError): # Scalars not allowed
|
||||
dist(1, 2)
|
||||
with self.assertRaises(TypeError): # Lists not allowed
|
||||
dist([1, 2, 3], [4, 5, 6])
|
||||
with self.assertRaises(TypeError): # Reject values without __float__
|
||||
dist((1.1, 'string', 2.2), (1, 2, 3))
|
||||
with self.assertRaises(ValueError): # Check dimension agree
|
||||
dist((1, 2, 3, 4), (5, 6, 7))
|
||||
with self.assertRaises(ValueError): # Check dimension agree
|
||||
dist((1, 2, 3), (4, 5, 6, 7))
|
||||
|
||||
|
||||
# Verify that the one dimensional case equivalent to abs()
|
||||
for i in range(20):
|
||||
p, q = random.random(), random.random()
|
||||
self.assertEqual(dist((p,), (q,)), abs(p - q))
|
||||
|
||||
# Test special values
|
||||
values = [NINF, -10.5, -0.0, 0.0, 10.5, INF, NAN]
|
||||
for p in itertools.product(values, repeat=3):
|
||||
for q in itertools.product(values, repeat=3):
|
||||
diffs = [px - qx for px, qx in zip(p, q)]
|
||||
if any(map(math.isinf, diffs)):
|
||||
# Any infinite difference gives positive infinity.
|
||||
self.assertEqual(dist(p, q), INF)
|
||||
elif any(map(math.isnan, diffs)):
|
||||
# If no infinity, any NaN gives a Nan.
|
||||
self.assertTrue(math.isnan(dist(p, q)))
|
||||
|
||||
# Verify scaling for extremely large values
|
||||
fourthmax = FLOAT_MAX / 4.0
|
||||
for n in range(32):
|
||||
p = (fourthmax,) * n
|
||||
q = (0.0,) * n
|
||||
self.assertEqual(dist(p, q), fourthmax * math.sqrt(n))
|
||||
self.assertEqual(dist(q, p), fourthmax * math.sqrt(n))
|
||||
|
||||
# Verify scaling for extremely small values
|
||||
for exp in range(32):
|
||||
scale = FLOAT_MIN / 2.0 ** exp
|
||||
p = (4*scale, 3*scale)
|
||||
q = (0.0, 0.0)
|
||||
self.assertEqual(math.dist(p, q), 5*scale)
|
||||
self.assertEqual(math.dist(q, p), 5*scale)
|
||||
|
||||
|
||||
def testLdexp(self):
|
||||
self.assertRaises(TypeError, math.ldexp)
|
||||
self.ftest('ldexp(0,1)', math.ldexp(0,1), 0)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue