mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-81313: Add the math.integer module (PEP-791) (GH-133909)
This commit is contained in:
parent
680a5d070f
commit
dcf3cc5796
22 changed files with 2091 additions and 1748 deletions
|
|
@ -55,56 +55,6 @@ def to_ulps(x):
|
|||
return n
|
||||
|
||||
|
||||
# Here's a pure Python version of the math.factorial algorithm, for
|
||||
# documentation and comparison purposes.
|
||||
#
|
||||
# Formula:
|
||||
#
|
||||
# factorial(n) = factorial_odd_part(n) << (n - count_set_bits(n))
|
||||
#
|
||||
# where
|
||||
#
|
||||
# factorial_odd_part(n) = product_{i >= 0} product_{0 < j <= n >> i; j odd} j
|
||||
#
|
||||
# The outer product above is an infinite product, but once i >= n.bit_length,
|
||||
# (n >> i) < 1 and the corresponding term of the product is empty. So only the
|
||||
# finitely many terms for 0 <= i < n.bit_length() contribute anything.
|
||||
#
|
||||
# We iterate downwards from i == n.bit_length() - 1 to i == 0. The inner
|
||||
# product in the formula above starts at 1 for i == n.bit_length(); for each i
|
||||
# < n.bit_length() we get the inner product for i from that for i + 1 by
|
||||
# multiplying by all j in {n >> i+1 < j <= n >> i; j odd}. In Python terms,
|
||||
# this set is range((n >> i+1) + 1 | 1, (n >> i) + 1 | 1, 2).
|
||||
|
||||
def count_set_bits(n):
|
||||
"""Number of '1' bits in binary expansion of a nonnnegative integer."""
|
||||
return 1 + count_set_bits(n & n - 1) if n else 0
|
||||
|
||||
def partial_product(start, stop):
|
||||
"""Product of integers in range(start, stop, 2), computed recursively.
|
||||
start and stop should both be odd, with start <= stop.
|
||||
|
||||
"""
|
||||
numfactors = (stop - start) >> 1
|
||||
if not numfactors:
|
||||
return 1
|
||||
elif numfactors == 1:
|
||||
return start
|
||||
else:
|
||||
mid = (start + numfactors) | 1
|
||||
return partial_product(start, mid) * partial_product(mid, stop)
|
||||
|
||||
def py_factorial(n):
|
||||
"""Factorial of nonnegative integer n, via "Binary Split Factorial Formula"
|
||||
described at http://www.luschny.de/math/factorial/binarysplitfact.html
|
||||
|
||||
"""
|
||||
inner = outer = 1
|
||||
for i in reversed(range(n.bit_length())):
|
||||
inner *= partial_product((n >> i + 1) + 1 | 1, (n >> i) + 1 | 1)
|
||||
outer *= inner
|
||||
return outer << (n - count_set_bits(n))
|
||||
|
||||
def ulp_abs_check(expected, got, ulp_tol, abs_tol):
|
||||
"""Given finite floats `expected` and `got`, check that they're
|
||||
approximately equal to within the given number of ulps or the
|
||||
|
|
@ -547,33 +497,6 @@ def testFabs(self):
|
|||
self.ftest('fabs(0)', math.fabs(0), 0)
|
||||
self.ftest('fabs(1)', math.fabs(1), 1)
|
||||
|
||||
def testFactorial(self):
|
||||
self.assertEqual(math.factorial(0), 1)
|
||||
total = 1
|
||||
for i in range(1, 1000):
|
||||
total *= i
|
||||
self.assertEqual(math.factorial(i), total)
|
||||
self.assertEqual(math.factorial(i), py_factorial(i))
|
||||
self.assertRaises(ValueError, math.factorial, -1)
|
||||
self.assertRaises(ValueError, math.factorial, -10**100)
|
||||
|
||||
def testFactorialNonIntegers(self):
|
||||
self.assertRaises(TypeError, math.factorial, 5.0)
|
||||
self.assertRaises(TypeError, math.factorial, 5.2)
|
||||
self.assertRaises(TypeError, math.factorial, -1.0)
|
||||
self.assertRaises(TypeError, math.factorial, -1e100)
|
||||
self.assertRaises(TypeError, math.factorial, decimal.Decimal('5'))
|
||||
self.assertRaises(TypeError, math.factorial, decimal.Decimal('5.2'))
|
||||
self.assertRaises(TypeError, math.factorial, "5")
|
||||
|
||||
# Other implementations may place different upper bounds.
|
||||
@support.cpython_only
|
||||
def testFactorialHugeInputs(self):
|
||||
# Currently raises OverflowError for inputs that are too large
|
||||
# to fit into a C long.
|
||||
self.assertRaises(OverflowError, math.factorial, 10**100)
|
||||
self.assertRaises(TypeError, math.factorial, 1e100)
|
||||
|
||||
def testFloor(self):
|
||||
self.assertRaises(TypeError, math.floor)
|
||||
self.assertEqual(int, type(math.floor(0.5)))
|
||||
|
|
@ -1175,68 +1098,6 @@ def test_math_dist_leak(self):
|
|||
with self.assertRaises(ValueError):
|
||||
math.dist([1, 2], [3, 4, 5])
|
||||
|
||||
def testIsqrt(self):
|
||||
# Test a variety of inputs, large and small.
|
||||
test_values = (
|
||||
list(range(1000))
|
||||
+ list(range(10**6 - 1000, 10**6 + 1000))
|
||||
+ [2**e + i for e in range(60, 200) for i in range(-40, 40)]
|
||||
+ [3**9999, 10**5001]
|
||||
)
|
||||
|
||||
for value in test_values:
|
||||
with self.subTest(value=value):
|
||||
s = math.isqrt(value)
|
||||
self.assertIs(type(s), int)
|
||||
self.assertLessEqual(s*s, value)
|
||||
self.assertLess(value, (s+1)*(s+1))
|
||||
|
||||
# Negative values
|
||||
with self.assertRaises(ValueError):
|
||||
math.isqrt(-1)
|
||||
|
||||
# Integer-like things
|
||||
s = math.isqrt(True)
|
||||
self.assertIs(type(s), int)
|
||||
self.assertEqual(s, 1)
|
||||
|
||||
s = math.isqrt(False)
|
||||
self.assertIs(type(s), int)
|
||||
self.assertEqual(s, 0)
|
||||
|
||||
class IntegerLike(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __index__(self):
|
||||
return self.value
|
||||
|
||||
s = math.isqrt(IntegerLike(1729))
|
||||
self.assertIs(type(s), int)
|
||||
self.assertEqual(s, 41)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
math.isqrt(IntegerLike(-3))
|
||||
|
||||
# Non-integer-like things
|
||||
bad_values = [
|
||||
3.5, "a string", decimal.Decimal("3.5"), 3.5j,
|
||||
100.0, -4.0,
|
||||
]
|
||||
for value in bad_values:
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(TypeError):
|
||||
math.isqrt(value)
|
||||
|
||||
@support.bigmemtest(2**32, memuse=0.85)
|
||||
def test_isqrt_huge(self, size):
|
||||
if size & 1:
|
||||
size += 1
|
||||
v = 1 << size
|
||||
w = math.isqrt(v)
|
||||
self.assertEqual(w.bit_length(), size // 2 + 1)
|
||||
self.assertEqual(w.bit_count(), 1)
|
||||
|
||||
def test_lcm(self):
|
||||
lcm = math.lcm
|
||||
self.assertEqual(lcm(0, 0), 0)
|
||||
|
|
@ -2392,140 +2253,6 @@ def _naive_prod(iterable, start=1):
|
|||
self.assertEqual(type(prod([1, decimal.Decimal(2.0), 3, 4, 5, 6])),
|
||||
decimal.Decimal)
|
||||
|
||||
def testPerm(self):
|
||||
perm = math.perm
|
||||
factorial = math.factorial
|
||||
# Test if factorial definition is satisfied
|
||||
for n in range(500):
|
||||
for k in (range(n + 1) if n < 100 else range(30) if n < 200 else range(10)):
|
||||
self.assertEqual(perm(n, k),
|
||||
factorial(n) // factorial(n - k))
|
||||
|
||||
# Test for Pascal's identity
|
||||
for n in range(1, 100):
|
||||
for k in range(1, n):
|
||||
self.assertEqual(perm(n, k), perm(n - 1, k - 1) * k + perm(n - 1, k))
|
||||
|
||||
# Test corner cases
|
||||
for n in range(1, 100):
|
||||
self.assertEqual(perm(n, 0), 1)
|
||||
self.assertEqual(perm(n, 1), n)
|
||||
self.assertEqual(perm(n, n), factorial(n))
|
||||
|
||||
# Test one argument form
|
||||
for n in range(20):
|
||||
self.assertEqual(perm(n), factorial(n))
|
||||
self.assertEqual(perm(n, None), factorial(n))
|
||||
|
||||
# Raises TypeError if any argument is non-integer or argument count is
|
||||
# not 1 or 2
|
||||
self.assertRaises(TypeError, perm, 10, 1.0)
|
||||
self.assertRaises(TypeError, perm, 10, decimal.Decimal(1.0))
|
||||
self.assertRaises(TypeError, perm, 10, "1")
|
||||
self.assertRaises(TypeError, perm, 10.0, 1)
|
||||
self.assertRaises(TypeError, perm, decimal.Decimal(10.0), 1)
|
||||
self.assertRaises(TypeError, perm, "10", 1)
|
||||
|
||||
self.assertRaises(TypeError, perm)
|
||||
self.assertRaises(TypeError, perm, 10, 1, 3)
|
||||
self.assertRaises(TypeError, perm)
|
||||
|
||||
# Raises Value error if not k or n are negative numbers
|
||||
self.assertRaises(ValueError, perm, -1, 1)
|
||||
self.assertRaises(ValueError, perm, -2**1000, 1)
|
||||
self.assertRaises(ValueError, perm, 1, -1)
|
||||
self.assertRaises(ValueError, perm, 1, -2**1000)
|
||||
|
||||
# Returns zero if k is greater than n
|
||||
self.assertEqual(perm(1, 2), 0)
|
||||
self.assertEqual(perm(1, 2**1000), 0)
|
||||
|
||||
n = 2**1000
|
||||
self.assertEqual(perm(n, 0), 1)
|
||||
self.assertEqual(perm(n, 1), n)
|
||||
self.assertEqual(perm(n, 2), n * (n-1))
|
||||
if support.check_impl_detail(cpython=True):
|
||||
self.assertRaises(OverflowError, perm, n, n)
|
||||
|
||||
for n, k in (True, True), (True, False), (False, False):
|
||||
self.assertEqual(perm(n, k), 1)
|
||||
self.assertIs(type(perm(n, k)), int)
|
||||
self.assertEqual(perm(IntSubclass(5), IntSubclass(2)), 20)
|
||||
self.assertEqual(perm(MyIndexable(5), MyIndexable(2)), 20)
|
||||
for k in range(3):
|
||||
self.assertIs(type(perm(IntSubclass(5), IntSubclass(k))), int)
|
||||
self.assertIs(type(perm(MyIndexable(5), MyIndexable(k))), int)
|
||||
|
||||
def testComb(self):
|
||||
comb = math.comb
|
||||
factorial = math.factorial
|
||||
# Test if factorial definition is satisfied
|
||||
for n in range(500):
|
||||
for k in (range(n + 1) if n < 100 else range(30) if n < 200 else range(10)):
|
||||
self.assertEqual(comb(n, k), factorial(n)
|
||||
// (factorial(k) * factorial(n - k)))
|
||||
|
||||
# Test for Pascal's identity
|
||||
for n in range(1, 100):
|
||||
for k in range(1, n):
|
||||
self.assertEqual(comb(n, k), comb(n - 1, k - 1) + comb(n - 1, k))
|
||||
|
||||
# Test corner cases
|
||||
for n in range(100):
|
||||
self.assertEqual(comb(n, 0), 1)
|
||||
self.assertEqual(comb(n, n), 1)
|
||||
|
||||
for n in range(1, 100):
|
||||
self.assertEqual(comb(n, 1), n)
|
||||
self.assertEqual(comb(n, n - 1), n)
|
||||
|
||||
# Test Symmetry
|
||||
for n in range(100):
|
||||
for k in range(n // 2):
|
||||
self.assertEqual(comb(n, k), comb(n, n - k))
|
||||
|
||||
# Raises TypeError if any argument is non-integer or argument count is
|
||||
# not 2
|
||||
self.assertRaises(TypeError, comb, 10, 1.0)
|
||||
self.assertRaises(TypeError, comb, 10, decimal.Decimal(1.0))
|
||||
self.assertRaises(TypeError, comb, 10, "1")
|
||||
self.assertRaises(TypeError, comb, 10.0, 1)
|
||||
self.assertRaises(TypeError, comb, decimal.Decimal(10.0), 1)
|
||||
self.assertRaises(TypeError, comb, "10", 1)
|
||||
|
||||
self.assertRaises(TypeError, comb, 10)
|
||||
self.assertRaises(TypeError, comb, 10, 1, 3)
|
||||
self.assertRaises(TypeError, comb)
|
||||
|
||||
# Raises Value error if not k or n are negative numbers
|
||||
self.assertRaises(ValueError, comb, -1, 1)
|
||||
self.assertRaises(ValueError, comb, -2**1000, 1)
|
||||
self.assertRaises(ValueError, comb, 1, -1)
|
||||
self.assertRaises(ValueError, comb, 1, -2**1000)
|
||||
|
||||
# Returns zero if k is greater than n
|
||||
self.assertEqual(comb(1, 2), 0)
|
||||
self.assertEqual(comb(1, 2**1000), 0)
|
||||
|
||||
n = 2**1000
|
||||
self.assertEqual(comb(n, 0), 1)
|
||||
self.assertEqual(comb(n, 1), n)
|
||||
self.assertEqual(comb(n, 2), n * (n-1) // 2)
|
||||
self.assertEqual(comb(n, n), 1)
|
||||
self.assertEqual(comb(n, n-1), n)
|
||||
self.assertEqual(comb(n, n-2), n * (n-1) // 2)
|
||||
if support.check_impl_detail(cpython=True):
|
||||
self.assertRaises(OverflowError, comb, n, n//2)
|
||||
|
||||
for n, k in (True, True), (True, False), (False, False):
|
||||
self.assertEqual(comb(n, k), 1)
|
||||
self.assertIs(type(comb(n, k)), int)
|
||||
self.assertEqual(comb(IntSubclass(5), IntSubclass(2)), 10)
|
||||
self.assertEqual(comb(MyIndexable(5), MyIndexable(2)), 10)
|
||||
for k in range(3):
|
||||
self.assertIs(type(comb(IntSubclass(5), IntSubclass(k))), int)
|
||||
self.assertIs(type(comb(MyIndexable(5), MyIndexable(k))), int)
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_nextafter(self):
|
||||
# around 2^52 and 2^63
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue