mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Add Python 3.15 math APIs using pymath (#9122)
* Add Python 3.15 math APIs using pymath Pin pymath to merged dd1e9a4cf26d68caf3b549c09476f52e356e16a3 and expose isnormal, issubnormal, signbit, fmax and fmin through thin wrappers. Share the existing integer functions through math.integer and _math_integer, matching CPython module identity and metadata. Migrate test_math.py and test_math_integer.py via scripts/update_lib from CPython d220409a13b77de7a6c30abe16ea90db01528edc, preserving the existing Windows remainder marker. Add bounded Python snippet coverage. AI assistance: OpenAI Codex implemented and source-reviewed this change, ran update_lib, and performed focused automated validation. No human manual testing is claimed. Assisted-by: OpenAI Codex:model version unavailable * Track integer math tests in library updates Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
1 parent
796f522306
commit
0f127b07e9
9 files changed
+759
-309
No files matched your search
@@ -127,6 +127,7 @@ isabstractmethod
|
||||
isbytes
|
||||
iscased
|
||||
isfinal
|
||||
issubnormal
|
||||
istext
|
||||
itemiterator
|
||||
itemsize
|
||||
|
||||
Generated
+1
-2
@@ -3169,8 +3169,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pymath"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc10e50b7a1f2cc3887e983721cb51fc7574be0066c84bff3ef9e5c096e8d6d5"
|
||||
source = "git+https://github.com/RustPython/pymath.git?rev=dd1e9a4cf26d68caf3b549c09476f52e356e16a3#dd1e9a4cf26d68caf3b549c09476f52e356e16a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libm",
|
||||
|
||||
+1
-1
@@ -266,7 +266,7 @@ pem-rfc7468 = "1.0"
|
||||
pkcs8 = "0.11"
|
||||
proc-macro2 = "1.0.105"
|
||||
psm = "0.1"
|
||||
pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
|
||||
pymath = { git = "https://github.com/RustPython/pymath.git", rev = "dd1e9a4cf26d68caf3b549c09476f52e356e16a3", features = ["mul_add", "malachite-bigint", "complex"] }
|
||||
pyo3 = "0.29"
|
||||
quote = "1.0.45"
|
||||
radium = "1.1.1"
|
||||
|
||||
Vendored
+181
-301
@@ -17,6 +17,7 @@ import sys
|
||||
|
||||
eps = 1E-05
|
||||
NAN = float('nan')
|
||||
NNAN = float('-nan')
|
||||
INF = float('inf')
|
||||
NINF = float('-inf')
|
||||
FLOAT_MAX = sys.float_info.max
|
||||
@@ -54,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
|
||||
@@ -238,18 +189,21 @@ class MyIndexable(object):
|
||||
def __index__(self):
|
||||
return self.value
|
||||
|
||||
# int subclass with broken arithmetic operators; implementations must
|
||||
# convert their arguments to exact ints instead of using these.
|
||||
class BadIntSubclass(int):
|
||||
def _binop(self, other='ignored', mod=None):
|
||||
return 42
|
||||
__add__ = __radd__ = __sub__ = __rsub__ = _binop
|
||||
__mul__ = __rmul__ = __mod__ = __rmod__ = _binop
|
||||
__divmod__ = __rdivmod__ = __pow__ = __rpow__ = _binop
|
||||
__floordiv__ = __rfloordiv__ = _binop
|
||||
__lshift__ = __rlshift__ = __rshift__ = __rrshift__ = _binop
|
||||
__and__ = __rand__ = __or__ = __ror__ = __xor__ = __rxor__ = _binop
|
||||
__lt__ = __le__ = __gt__ = __ge__ = _binop
|
||||
class IndexableFloatLike:
|
||||
def __init__(self, float_value, index_value):
|
||||
self.float_value = float_value
|
||||
self.index_value = index_value
|
||||
|
||||
def __float__(self):
|
||||
if isinstance(self.float_value, BaseException):
|
||||
raise self.float_value
|
||||
return self.float_value
|
||||
|
||||
def __index__(self):
|
||||
if isinstance(self.index_value, BaseException):
|
||||
raise self.index_value
|
||||
return self.index_value
|
||||
|
||||
|
||||
class BadDescr:
|
||||
def __get__(self, obj, objtype=None):
|
||||
@@ -490,6 +444,19 @@ class MathTests(unittest.TestCase):
|
||||
# similarly, copysign(2., NAN) could be 2. or -2.
|
||||
self.assertEqual(abs(math.copysign(2., NAN)), 2.)
|
||||
|
||||
def test_signbit(self):
|
||||
self.assertRaises(TypeError, math.signbit)
|
||||
self.assertRaises(TypeError, math.signbit, '1.0')
|
||||
|
||||
# C11, §7.12.3.6 requires signbit() to return a nonzero value
|
||||
# if and only if the sign of its argument value is negative,
|
||||
# but in practice, we are only interested in a boolean value.
|
||||
self.assertIsInstance(math.signbit(1.0), bool)
|
||||
|
||||
for arg in [0., 1., INF, NAN]:
|
||||
self.assertFalse(math.signbit(arg))
|
||||
self.assertTrue(math.signbit(-arg))
|
||||
|
||||
def testCos(self):
|
||||
self.assertRaises(TypeError, math.cos)
|
||||
self.ftest('cos(-pi/2)', math.cos(-math.pi/2), 0, abs_tol=math.ulp(1))
|
||||
@@ -548,33 +515,6 @@ class MathTests(unittest.TestCase):
|
||||
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)))
|
||||
@@ -638,6 +578,92 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(math.fmod(0.0, NINF), 0.0)
|
||||
self.assertRaises(ValueError, math.fmod, INF, INF)
|
||||
|
||||
def test_fmax(self):
|
||||
self.assertRaises(TypeError, math.fmax)
|
||||
self.assertRaises(TypeError, math.fmax, 'x', 'y')
|
||||
|
||||
self.assertEqual(math.fmax(0., 0.), 0.)
|
||||
self.assertEqual(math.fmax(1., 2.), 2.)
|
||||
self.assertEqual(math.fmax(2., 1.), 2.)
|
||||
|
||||
self.assertEqual(math.fmax(+1., +0.), 1.)
|
||||
self.assertEqual(math.fmax(+0., +1.), 1.)
|
||||
self.assertEqual(math.fmax(+1., -0.), 1.)
|
||||
self.assertEqual(math.fmax(-0., +1.), 1.)
|
||||
|
||||
self.assertEqual(math.fmax(-1., +0.), 0.)
|
||||
self.assertEqual(math.fmax(+0., -1.), 0.)
|
||||
self.assertEqual(math.fmax(-1., -0.), 0.)
|
||||
self.assertEqual(math.fmax(-0., -1.), 0.)
|
||||
|
||||
for x in [NINF, -1., -0., 0., 1., INF]:
|
||||
self.assertFalse(math.isnan(x))
|
||||
|
||||
with self.subTest(x=x, is_negative=math.copysign(1, x) < 0):
|
||||
self.assertEqual(math.fmax(INF, x), INF)
|
||||
self.assertEqual(math.fmax(x, INF), INF)
|
||||
self.assertEqual(math.fmax(NINF, x), x)
|
||||
self.assertEqual(math.fmax(x, NINF), x)
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_fmax_nans(self):
|
||||
# When exactly one operand is NaN, the other is returned.
|
||||
for x in [NINF, -1., -0., 0., 1., INF]:
|
||||
with self.subTest(x=x, is_negative=math.copysign(1, x) < 0):
|
||||
self.assertFalse(math.isnan(math.fmax(NAN, x)))
|
||||
self.assertFalse(math.isnan(math.fmax(x, NAN)))
|
||||
self.assertFalse(math.isnan(math.fmax(NNAN, x)))
|
||||
self.assertFalse(math.isnan(math.fmax(x, NNAN)))
|
||||
# When both operands are NaNs, fmax() returns NaN (see C11, F.10.9.2)
|
||||
# whose sign is implementation-defined (see C11, F.10.0.3).
|
||||
self.assertTrue(math.isnan(math.fmax(NAN, NAN)))
|
||||
self.assertTrue(math.isnan(math.fmax(NNAN, NNAN)))
|
||||
self.assertTrue(math.isnan(math.fmax(NAN, NNAN)))
|
||||
self.assertTrue(math.isnan(math.fmax(NNAN, NAN)))
|
||||
|
||||
def test_fmin(self):
|
||||
self.assertRaises(TypeError, math.fmin)
|
||||
self.assertRaises(TypeError, math.fmin, 'x', 'y')
|
||||
|
||||
self.assertEqual(math.fmin(0., 0.), 0.)
|
||||
self.assertEqual(math.fmin(1., 2.), 1.)
|
||||
self.assertEqual(math.fmin(2., 1.), 1.)
|
||||
|
||||
self.assertEqual(math.fmin(+1., +0.), 0.)
|
||||
self.assertEqual(math.fmin(+0., +1.), 0.)
|
||||
self.assertEqual(math.fmin(+1., -0.), 0.)
|
||||
self.assertEqual(math.fmin(-0., +1.), 0.)
|
||||
|
||||
self.assertEqual(math.fmin(-1., +0.), -1.)
|
||||
self.assertEqual(math.fmin(+0., -1.), -1.)
|
||||
self.assertEqual(math.fmin(-1., -0.), -1.)
|
||||
self.assertEqual(math.fmin(-0., -1.), -1.)
|
||||
|
||||
for x in [NINF, -1., -0., 0., 1., INF]:
|
||||
self.assertFalse(math.isnan(x))
|
||||
|
||||
with self.subTest(x=x, is_negative=math.copysign(1, x) < 0):
|
||||
self.assertEqual(math.fmin(INF, x), x)
|
||||
self.assertEqual(math.fmin(x, INF), x)
|
||||
self.assertEqual(math.fmin(NINF, x), NINF)
|
||||
self.assertEqual(math.fmin(x, NINF), NINF)
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_fmin_nans(self):
|
||||
# When exactly one operand is NaN, the other is returned.
|
||||
for x in [NINF, -1., -0., 0., 1., INF]:
|
||||
with self.subTest(x=x, is_negative=math.copysign(1, x) < 0):
|
||||
self.assertFalse(math.isnan(math.fmin(NAN, x)))
|
||||
self.assertFalse(math.isnan(math.fmin(x, NAN)))
|
||||
self.assertFalse(math.isnan(math.fmin(NNAN, x)))
|
||||
self.assertFalse(math.isnan(math.fmin(x, NNAN)))
|
||||
# When both operands are NaNs, fmin() returns NaN (see C11, F.10.9.3)
|
||||
# whose sign is implementation-defined (see C11, F.10.0.3).
|
||||
self.assertTrue(math.isnan(math.fmin(NAN, NAN)))
|
||||
self.assertTrue(math.isnan(math.fmin(NNAN, NNAN)))
|
||||
self.assertTrue(math.isnan(math.fmin(NAN, NNAN)))
|
||||
self.assertTrue(math.isnan(math.fmin(NNAN, NAN)))
|
||||
|
||||
def testFrexp(self):
|
||||
self.assertRaises(TypeError, math.frexp)
|
||||
|
||||
@@ -1090,78 +1116,6 @@ class MathTests(unittest.TestCase):
|
||||
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)
|
||||
|
||||
# Overridden operators of an int subclass must not affect the
|
||||
# result.
|
||||
s = math.isqrt(BadIntSubclass(10**20))
|
||||
self.assertIs(type(s), int)
|
||||
self.assertEqual(s, 10**10)
|
||||
|
||||
s = math.isqrt(BadIntSubclass(10**20 - 1))
|
||||
self.assertIs(type(s), int)
|
||||
self.assertEqual(s, 10**10 - 1)
|
||||
|
||||
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)
|
||||
@@ -1256,13 +1210,32 @@ class MathTests(unittest.TestCase):
|
||||
self.ftest('log(10**40, 10**20)', math.log(10**40, 10**20), 2)
|
||||
self.ftest('log(10**1000)', math.log(10**1000),
|
||||
2302.5850929940457)
|
||||
self.ftest('log(10**2000, 10**1000)', math.log(10**2000, 10**1000), 2)
|
||||
self.ftest('log(MyIndexable(32), MyIndexable(2))',
|
||||
math.log(MyIndexable(32), MyIndexable(2)), 5)
|
||||
self.ftest('log(MyIndexable(10**1000))',
|
||||
math.log(MyIndexable(10**1000)),
|
||||
2302.5850929940457)
|
||||
self.ftest('log(MyIndexable(10**2000), MyIndexable(10**1000))',
|
||||
math.log(MyIndexable(10**2000), MyIndexable(10**1000)),
|
||||
2)
|
||||
self.assertRaises(ValueError, math.log, 0.0)
|
||||
self.assertRaises(ValueError, math.log, 0)
|
||||
self.assertRaises(ValueError, math.log, MyIndexable(0))
|
||||
self.assertRaises(ValueError, math.log, -1.5)
|
||||
self.assertRaises(ValueError, math.log, -1)
|
||||
self.assertRaises(ValueError, math.log, MyIndexable(-1))
|
||||
self.assertRaises(ValueError, math.log, -10**1000)
|
||||
self.assertRaises(ValueError, math.log, MyIndexable(-10**1000))
|
||||
self.assertRaises(ValueError, math.log, 10, -10)
|
||||
self.assertRaises(ValueError, math.log, NINF)
|
||||
self.assertEqual(math.log(INF), INF)
|
||||
self.assertTrue(math.isnan(math.log(NAN)))
|
||||
|
||||
self.assertEqual(math.log(IndexableFloatLike(math.e, 10**1000)), 1.0)
|
||||
self.assertAlmostEqual(math.log(IndexableFloatLike(OverflowError(), 10**1000)),
|
||||
2302.5850929940457)
|
||||
|
||||
def testLog1p(self):
|
||||
self.assertRaises(TypeError, math.log1p)
|
||||
for n in [2, 2**90, 2**300]:
|
||||
@@ -1278,16 +1251,28 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(math.log2(1), 0.0)
|
||||
self.assertEqual(math.log2(2), 1.0)
|
||||
self.assertEqual(math.log2(4), 2.0)
|
||||
self.assertEqual(math.log2(MyIndexable(4)), 2.0)
|
||||
|
||||
# Large integer values
|
||||
self.assertEqual(math.log2(2**1023), 1023.0)
|
||||
self.assertEqual(math.log2(2**1024), 1024.0)
|
||||
self.assertEqual(math.log2(2**2000), 2000.0)
|
||||
self.assertEqual(math.log2(MyIndexable(2**2000)), 2000.0)
|
||||
|
||||
self.assertRaises(ValueError, math.log2, 0.0)
|
||||
self.assertRaises(ValueError, math.log2, 0)
|
||||
self.assertRaises(ValueError, math.log2, MyIndexable(0))
|
||||
self.assertRaises(ValueError, math.log2, -1.5)
|
||||
self.assertRaises(ValueError, math.log2, -1)
|
||||
self.assertRaises(ValueError, math.log2, MyIndexable(-1))
|
||||
self.assertRaises(ValueError, math.log2, -2**2000)
|
||||
self.assertRaises(ValueError, math.log2, MyIndexable(-2**2000))
|
||||
self.assertRaises(ValueError, math.log2, NINF)
|
||||
self.assertTrue(math.isnan(math.log2(NAN)))
|
||||
|
||||
self.assertEqual(math.log2(IndexableFloatLike(8.0, 2**2000)), 3.0)
|
||||
self.assertEqual(math.log2(IndexableFloatLike(OverflowError(), 2**2000)), 2000.0)
|
||||
|
||||
@requires_IEEE_754
|
||||
# log2() is not accurate enough on Mac OS X Tiger (10.4)
|
||||
@support.requires_mac_ver(10, 5)
|
||||
@@ -1303,12 +1288,24 @@ class MathTests(unittest.TestCase):
|
||||
self.ftest('log10(1)', math.log10(1), 0)
|
||||
self.ftest('log10(10)', math.log10(10), 1)
|
||||
self.ftest('log10(10**1000)', math.log10(10**1000), 1000.0)
|
||||
self.ftest('log10(MyIndexable(10))', math.log10(MyIndexable(10)), 1)
|
||||
self.ftest('log10(MyIndexable(10**1000))',
|
||||
math.log10(MyIndexable(10**1000)), 1000.0)
|
||||
self.assertRaises(ValueError, math.log10, 0.0)
|
||||
self.assertRaises(ValueError, math.log10, 0)
|
||||
self.assertRaises(ValueError, math.log10, MyIndexable(0))
|
||||
self.assertRaises(ValueError, math.log10, -1.5)
|
||||
self.assertRaises(ValueError, math.log10, -1)
|
||||
self.assertRaises(ValueError, math.log10, MyIndexable(-1))
|
||||
self.assertRaises(ValueError, math.log10, -10**1000)
|
||||
self.assertRaises(ValueError, math.log10, MyIndexable(-10**1000))
|
||||
self.assertRaises(ValueError, math.log10, NINF)
|
||||
self.assertEqual(math.log(INF), INF)
|
||||
self.assertTrue(math.isnan(math.log10(NAN)))
|
||||
|
||||
self.assertEqual(math.log10(IndexableFloatLike(100.0, 10**1000)), 2.0)
|
||||
self.assertEqual(math.log10(IndexableFloatLike(OverflowError(), 10**1000)), 1000.0)
|
||||
|
||||
@support.bigmemtest(2**32, memuse=0.2)
|
||||
def test_log_huge_integer(self, size):
|
||||
v = 1 << size
|
||||
@@ -1412,7 +1409,6 @@ class MathTests(unittest.TestCase):
|
||||
args = ((-5, -5, 10), (1.5, 4611686018427387904, 2305843009213693952))
|
||||
self.assertEqual(sumprod(*args), 0.0)
|
||||
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"sumprod() accuracy not guaranteed on machines with double rounding")
|
||||
@@ -1999,6 +1995,28 @@ class MathTests(unittest.TestCase):
|
||||
self.assertFalse(math.isfinite(float("inf")))
|
||||
self.assertFalse(math.isfinite(float("-inf")))
|
||||
|
||||
def testIsnormal(self):
|
||||
self.assertTrue(math.isnormal(1.25))
|
||||
self.assertTrue(math.isnormal(-1.0))
|
||||
self.assertFalse(math.isnormal(0.0))
|
||||
self.assertFalse(math.isnormal(-0.0))
|
||||
self.assertFalse(math.isnormal(INF))
|
||||
self.assertFalse(math.isnormal(NINF))
|
||||
self.assertFalse(math.isnormal(NAN))
|
||||
self.assertFalse(math.isnormal(FLOAT_MIN/2))
|
||||
self.assertFalse(math.isnormal(-FLOAT_MIN/2))
|
||||
|
||||
def testIssubnormal(self):
|
||||
self.assertFalse(math.issubnormal(1.25))
|
||||
self.assertFalse(math.issubnormal(-1.0))
|
||||
self.assertFalse(math.issubnormal(0.0))
|
||||
self.assertFalse(math.issubnormal(-0.0))
|
||||
self.assertFalse(math.issubnormal(INF))
|
||||
self.assertFalse(math.issubnormal(NINF))
|
||||
self.assertFalse(math.issubnormal(NAN))
|
||||
self.assertTrue(math.issubnormal(FLOAT_MIN/2))
|
||||
self.assertTrue(math.issubnormal(-FLOAT_MIN/2))
|
||||
|
||||
def testIsnan(self):
|
||||
self.assertTrue(math.isnan(float("nan")))
|
||||
self.assertTrue(math.isnan(float("-nan")))
|
||||
@@ -2297,140 +2315,6 @@ class MathTests(unittest.TestCase):
|
||||
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
|
||||
@@ -2490,7 +2374,6 @@ class MathTests(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
math.nextafter(1.0, INF, steps=-1)
|
||||
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_ulp(self):
|
||||
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
|
||||
@@ -2805,11 +2688,8 @@ class FMATests(unittest.TestCase):
|
||||
@unittest.skipIf(
|
||||
sys.platform.startswith(("freebsd", "wasi", "netbsd", "emscripten"))
|
||||
or (sys.platform == "android" and platform.machine() == "x86_64")
|
||||
or support.linked_to_musl(), # gh-131032
|
||||
or (support.linked_to_musl() and support.linked_to_musl() < (1, 2, 6)), # gh-131032
|
||||
f"this platform doesn't implement IEE 754-2008 properly")
|
||||
# gh-131032: musl is fixed but the fix is not yet released; when the fixed
|
||||
# version is known change this to:
|
||||
# or support.linked_to_musl() < (1, <m>, <p>)
|
||||
def test_fma_zero_result(self):
|
||||
nonnegative_finites = [0.0, 1e-300, 2.3, 1e300]
|
||||
|
||||
|
||||
Vendored
+421
@@ -0,0 +1,421 @@
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
import unittest
|
||||
from test import support
|
||||
|
||||
|
||||
class IntSubclass(int):
|
||||
pass
|
||||
|
||||
# Class providing an __index__ method.
|
||||
class MyIndexable(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __index__(self):
|
||||
return self.value
|
||||
|
||||
# int subclass with broken arithmetic operators; implementations must
|
||||
# convert their arguments to exact ints instead of using these.
|
||||
class BadIntSubclass(int):
|
||||
def _binop(self, other='ignored', mod=None):
|
||||
return 42
|
||||
__add__ = __radd__ = __sub__ = __rsub__ = _binop
|
||||
__mul__ = __rmul__ = __mod__ = __rmod__ = _binop
|
||||
__divmod__ = __rdivmod__ = __pow__ = __rpow__ = _binop
|
||||
__floordiv__ = __rfloordiv__ = _binop
|
||||
__lshift__ = __rlshift__ = __rshift__ = __rrshift__ = _binop
|
||||
__and__ = __rand__ = __or__ = __ror__ = __xor__ = __rxor__ = _binop
|
||||
__lt__ = __le__ = __gt__ = __ge__ = _binop
|
||||
|
||||
# Here's a pure Python version of the math.integer.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))
|
||||
|
||||
|
||||
class IntMathTests(unittest.TestCase):
|
||||
import math.integer as module
|
||||
|
||||
def assertIntEqual(self, actual, expected):
|
||||
self.assertEqual(actual, expected)
|
||||
self.assertIs(type(actual), int)
|
||||
|
||||
def test_factorial(self):
|
||||
factorial = self.module.factorial
|
||||
self.assertEqual(factorial(0), 1)
|
||||
total = 1
|
||||
for i in range(1, 1000):
|
||||
total *= i
|
||||
self.assertEqual(factorial(i), total)
|
||||
self.assertEqual(factorial(i), py_factorial(i))
|
||||
|
||||
self.assertIntEqual(factorial(False), 1)
|
||||
self.assertIntEqual(factorial(True), 1)
|
||||
for i in range(3):
|
||||
expected = factorial(i)
|
||||
self.assertIntEqual(factorial(IntSubclass(i)), expected)
|
||||
self.assertIntEqual(factorial(MyIndexable(i)), expected)
|
||||
|
||||
self.assertRaises(ValueError, factorial, -1)
|
||||
self.assertRaises(ValueError, factorial, -10**1000)
|
||||
|
||||
def test_factorial_non_integers(self):
|
||||
factorial = self.module.factorial
|
||||
self.assertRaises(TypeError, factorial, 5.0)
|
||||
self.assertRaises(TypeError, factorial, 5.2)
|
||||
self.assertRaises(TypeError, factorial, -1.0)
|
||||
self.assertRaises(TypeError, factorial, -1e100)
|
||||
self.assertRaises(TypeError, factorial, Decimal('5'))
|
||||
self.assertRaises(TypeError, factorial, Decimal('5.2'))
|
||||
self.assertRaises(TypeError, factorial, Fraction(5, 1))
|
||||
self.assertRaises(TypeError, factorial, "5")
|
||||
|
||||
# Other implementations may place different upper bounds.
|
||||
@support.cpython_only
|
||||
def test_factorial_huge_inputs(self):
|
||||
factorial = self.module.factorial
|
||||
# Currently raises OverflowError for inputs that are too large
|
||||
# to fit into a C long.
|
||||
self.assertRaises(OverflowError, factorial, 10**100)
|
||||
self.assertRaises(TypeError, factorial, 1e100)
|
||||
|
||||
def test_gcd(self):
|
||||
gcd = self.module.gcd
|
||||
self.assertEqual(gcd(0, 0), 0)
|
||||
self.assertEqual(gcd(1, 0), 1)
|
||||
self.assertEqual(gcd(-1, 0), 1)
|
||||
self.assertEqual(gcd(0, 1), 1)
|
||||
self.assertEqual(gcd(0, -1), 1)
|
||||
self.assertEqual(gcd(7, 1), 1)
|
||||
self.assertEqual(gcd(7, -1), 1)
|
||||
self.assertEqual(gcd(-23, 15), 1)
|
||||
self.assertEqual(gcd(120, 84), 12)
|
||||
self.assertEqual(gcd(84, -120), 12)
|
||||
self.assertEqual(gcd(1216342683557601535506311712,
|
||||
436522681849110124616458784), 32)
|
||||
c = 652560
|
||||
x = 434610456570399902378880679233098819019853229470286994367836600566
|
||||
y = 1064502245825115327754847244914921553977
|
||||
a = x * c
|
||||
b = y * c
|
||||
self.assertEqual(gcd(a, b), c)
|
||||
self.assertEqual(gcd(b, a), c)
|
||||
self.assertEqual(gcd(-a, b), c)
|
||||
self.assertEqual(gcd(b, -a), c)
|
||||
self.assertEqual(gcd(a, -b), c)
|
||||
self.assertEqual(gcd(-b, a), c)
|
||||
self.assertEqual(gcd(-a, -b), c)
|
||||
self.assertEqual(gcd(-b, -a), c)
|
||||
c = 576559230871654959816130551884856912003141446781646602790216406874
|
||||
a = x * c
|
||||
b = y * c
|
||||
self.assertEqual(gcd(a, b), c)
|
||||
self.assertEqual(gcd(b, a), c)
|
||||
self.assertEqual(gcd(-a, b), c)
|
||||
self.assertEqual(gcd(b, -a), c)
|
||||
self.assertEqual(gcd(a, -b), c)
|
||||
self.assertEqual(gcd(-b, a), c)
|
||||
self.assertEqual(gcd(-a, -b), c)
|
||||
self.assertEqual(gcd(-b, -a), c)
|
||||
|
||||
self.assertRaises(TypeError, gcd, 120.0, 84)
|
||||
self.assertRaises(TypeError, gcd, 120, 84.0)
|
||||
self.assertIntEqual(gcd(IntSubclass(120), IntSubclass(84)), 12)
|
||||
self.assertIntEqual(gcd(MyIndexable(120), MyIndexable(84)), 12)
|
||||
|
||||
def test_lcm(self):
|
||||
lcm = self.module.lcm
|
||||
self.assertEqual(lcm(0, 0), 0)
|
||||
self.assertEqual(lcm(1, 0), 0)
|
||||
self.assertEqual(lcm(-1, 0), 0)
|
||||
self.assertEqual(lcm(0, 1), 0)
|
||||
self.assertEqual(lcm(0, -1), 0)
|
||||
self.assertEqual(lcm(7, 1), 7)
|
||||
self.assertEqual(lcm(7, -1), 7)
|
||||
self.assertEqual(lcm(-23, 15), 345)
|
||||
self.assertEqual(lcm(120, 84), 840)
|
||||
self.assertEqual(lcm(84, -120), 840)
|
||||
self.assertEqual(lcm(1216342683557601535506311712,
|
||||
436522681849110124616458784),
|
||||
16592536571065866494401400422922201534178938447014944)
|
||||
|
||||
x = 43461045657039990237
|
||||
y = 10645022458251153277
|
||||
for c in (652560,
|
||||
57655923087165495981):
|
||||
a = x * c
|
||||
b = y * c
|
||||
d = x * y * c
|
||||
self.assertEqual(lcm(a, b), d)
|
||||
self.assertEqual(lcm(b, a), d)
|
||||
self.assertEqual(lcm(-a, b), d)
|
||||
self.assertEqual(lcm(b, -a), d)
|
||||
self.assertEqual(lcm(a, -b), d)
|
||||
self.assertEqual(lcm(-b, a), d)
|
||||
self.assertEqual(lcm(-a, -b), d)
|
||||
self.assertEqual(lcm(-b, -a), d)
|
||||
|
||||
self.assertEqual(lcm(), 1)
|
||||
self.assertEqual(lcm(120), 120)
|
||||
self.assertEqual(lcm(-120), 120)
|
||||
self.assertEqual(lcm(120, 84, 102), 14280)
|
||||
self.assertEqual(lcm(120, 0, 84), 0)
|
||||
|
||||
self.assertRaises(TypeError, lcm, 120.0)
|
||||
self.assertRaises(TypeError, lcm, 120.0, 84)
|
||||
self.assertRaises(TypeError, lcm, 120, 84.0)
|
||||
self.assertRaises(TypeError, lcm, 120, 0, 84.0)
|
||||
self.assertEqual(lcm(MyIndexable(120), MyIndexable(84)), 840)
|
||||
|
||||
def test_isqrt(self):
|
||||
isqrt = self.module.isqrt
|
||||
# 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 = 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):
|
||||
isqrt(-1)
|
||||
|
||||
# Integer-like things
|
||||
self.assertIntEqual(isqrt(True), 1)
|
||||
self.assertIntEqual(isqrt(False), 0)
|
||||
self.assertIntEqual(isqrt(MyIndexable(1729)), 41)
|
||||
|
||||
# Overridden operators of an int subclass must not affect the
|
||||
# result.
|
||||
self.assertIntEqual(isqrt(BadIntSubclass(10**20)), 10**10)
|
||||
self.assertIntEqual(isqrt(BadIntSubclass(10**20 - 1)), 10**10 - 1)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
isqrt(MyIndexable(-3))
|
||||
|
||||
# Non-integer-like things
|
||||
bad_values = [
|
||||
3.5, "a string", Decimal("3.5"), 3.5j,
|
||||
100.0, -4.0,
|
||||
]
|
||||
for value in bad_values:
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(TypeError):
|
||||
isqrt(value)
|
||||
|
||||
@support.bigmemtest(2**32, memuse=0.85)
|
||||
def test_isqrt_huge(self, size):
|
||||
isqrt = self.module.isqrt
|
||||
if size & 1:
|
||||
size += 1
|
||||
v = 1 << size
|
||||
w = isqrt(v)
|
||||
self.assertEqual(w.bit_length(), size // 2 + 1)
|
||||
self.assertEqual(w.bit_count(), 1)
|
||||
|
||||
def test_perm(self):
|
||||
perm = self.module.perm
|
||||
factorial = self.module.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(1.0))
|
||||
self.assertRaises(TypeError, perm, 10, Fraction(1, 1))
|
||||
self.assertRaises(TypeError, perm, 10, "1")
|
||||
self.assertRaises(TypeError, perm, 10.0, 1)
|
||||
self.assertRaises(TypeError, perm, Decimal(10.0), 1)
|
||||
self.assertRaises(TypeError, perm, Fraction(10, 1), 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.assertIntEqual(perm(n, k), 1)
|
||||
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 test_comb(self):
|
||||
comb = self.module.comb
|
||||
factorial = self.module.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(1.0))
|
||||
self.assertRaises(TypeError, comb, 10, "1")
|
||||
self.assertRaises(TypeError, comb, 10.0, 1)
|
||||
self.assertRaises(TypeError, comb, 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.assertIntEqual(comb(n, k), 1)
|
||||
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)
|
||||
|
||||
|
||||
class MathTests(IntMathTests):
|
||||
import math as module
|
||||
|
||||
|
||||
class MiscTests(unittest.TestCase):
|
||||
|
||||
def test_module_name(self):
|
||||
import math.integer
|
||||
self.assertEqual(math.integer.__name__, 'math.integer')
|
||||
for name in dir(math.integer):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(math.integer, name)
|
||||
self.assertEqual(obj.__module__, 'math.integer')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -232,6 +232,7 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
|
||||
locale::module_def(ctx),
|
||||
lzma::module_def(ctx),
|
||||
math::module_def(ctx),
|
||||
math::integer_module_def(ctx),
|
||||
md5::module_def(ctx),
|
||||
#[cfg(all(feature = "host_env", any(unix, windows)))]
|
||||
mmap::module_def(ctx),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub(crate) use _math_integer::module_def as integer_module_def;
|
||||
pub(crate) use math::module_def;
|
||||
|
||||
use crate::vm::{VirtualMachine, builtins::PyBaseExceptionRef};
|
||||
@@ -5,19 +6,31 @@ use crate::vm::{VirtualMachine, builtins::PyBaseExceptionRef};
|
||||
#[pymodule]
|
||||
mod math {
|
||||
use crate::vm::{
|
||||
AsObject, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyFloat, PyInt, PyIntRef, PyStrInterned, try_bigint_to_f64, try_f64_to_bigint},
|
||||
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{
|
||||
PyFloat, PyInt, PyIntRef, PyModule, PyStrInterned, try_bigint_to_f64, try_f64_to_bigint,
|
||||
},
|
||||
function::{
|
||||
ArgIndex, ArgIntoFloat, ArgIterable, Either, NameCoordinates, NameIntegers,
|
||||
OptionalArg, PosArgs,
|
||||
ArgIndex, ArgIntoFloat, ArgIterable, Either, NameCoordinates, OptionalArg, PosArgs,
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
use malachite_bigint::BigInt;
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
|
||||
use super::{float_repr, pymath_exception};
|
||||
|
||||
pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
|
||||
__module_exec(vm, module);
|
||||
let integer = vm.import("_math_integer", 0)?;
|
||||
for name in super::INTEGER_FUNCTIONS {
|
||||
module.set_attr(name, integer.get_attr(name, vm)?, vm)?;
|
||||
}
|
||||
vm.sys_module
|
||||
.get_attr("modules", vm)?
|
||||
.set_item("math.integer", integer.clone(), vm)?;
|
||||
module.set_attr("integer", integer, vm)
|
||||
}
|
||||
|
||||
// Constants
|
||||
#[pyattr]
|
||||
use core::f64::consts::{E as e, PI as pi, TAU as tau};
|
||||
@@ -48,6 +61,36 @@ mod math {
|
||||
pymath::math::isnan(x.into_float())
|
||||
}
|
||||
|
||||
/// Return True if x is normal, and False otherwise.
|
||||
#[pyfunction]
|
||||
fn isnormal(x: ArgIntoFloat) -> bool {
|
||||
pymath::math::isnormal(x.into_float())
|
||||
}
|
||||
|
||||
/// Return True if x is subnormal, and False otherwise.
|
||||
#[pyfunction]
|
||||
fn issubnormal(x: ArgIntoFloat) -> bool {
|
||||
pymath::math::issubnormal(x.into_float())
|
||||
}
|
||||
|
||||
/// Return True if the sign of x is negative and False otherwise.
|
||||
#[pyfunction]
|
||||
fn signbit(x: ArgIntoFloat) -> bool {
|
||||
pymath::math::signbit(x.into_float())
|
||||
}
|
||||
|
||||
/// Return the larger of two floating-point arguments.
|
||||
#[pyfunction]
|
||||
fn fmax(x: ArgIntoFloat, y: ArgIntoFloat) -> f64 {
|
||||
pymath::math::fmax(x.into_float(), y.into_float())
|
||||
}
|
||||
|
||||
/// Return the smaller of two floating-point arguments.
|
||||
#[pyfunction]
|
||||
fn fmin(x: ArgIntoFloat, y: ArgIntoFloat) -> f64 {
|
||||
pymath::math::fmin(x.into_float(), y.into_float())
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct IsCloseArgs {
|
||||
#[pyarg(any)]
|
||||
@@ -773,7 +816,18 @@ mod math {
|
||||
pymath::Error::ERANGE => vm.new_overflow_error("overflow in fma"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const INTEGER_FUNCTIONS: [&str; 6] = ["comb", "factorial", "gcd", "isqrt", "lcm", "perm"];
|
||||
|
||||
#[pymodule(sub, name = "math")]
|
||||
mod integer_functions {
|
||||
use crate::vm::{
|
||||
PyResult, VirtualMachine,
|
||||
function::{ArgIndex, NameIntegers, PosArgs},
|
||||
};
|
||||
use malachite_bigint::BigInt;
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
// Integer functions:
|
||||
|
||||
#[pyfunction]
|
||||
@@ -960,3 +1014,21 @@ fn float_repr(value: f64) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This module provides access to integer related mathematical functions.
|
||||
#[pymodule(with(integer_functions))]
|
||||
mod _math_integer {
|
||||
use crate::vm::{Py, PyResult, VirtualMachine, builtins::PyModule};
|
||||
|
||||
pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
|
||||
__module_exec(vm, module);
|
||||
let name = vm.ctx.new_str("math.integer");
|
||||
module.set_attr("__name__", name.clone(), vm)?;
|
||||
for function_name in super::INTEGER_FUNCTIONS {
|
||||
module
|
||||
.get_attr(function_name, vm)?
|
||||
.set_attr("__module__", name.clone(), vm)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,81 @@ NAN = float("nan")
|
||||
INF = float("inf")
|
||||
NINF = float("-inf")
|
||||
|
||||
|
||||
def test_math_315_apis():
|
||||
import math.integer
|
||||
import sys
|
||||
|
||||
import _math_integer
|
||||
|
||||
assert math.integer is _math_integer
|
||||
assert sys.modules["math.integer"] is math.integer
|
||||
assert math.integer.__name__ == "math.integer"
|
||||
for name, args, expected in (
|
||||
("comb", (5, 2), 10),
|
||||
("factorial", (5,), 120),
|
||||
("gcd", (12, 18), 6),
|
||||
("isqrt", (17,), 4),
|
||||
("lcm", (4, 6), 12),
|
||||
("perm", (5, 2), 20),
|
||||
):
|
||||
function = getattr(math, name)
|
||||
assert function is getattr(math.integer, name)
|
||||
assert function.__module__ == "math.integer"
|
||||
assert function(*args) == expected
|
||||
|
||||
for value in (1.0, -1.0, sys.float_info.min, sys.float_info.max):
|
||||
assert math.isnormal(value) is True
|
||||
assert math.issubnormal(value) is False
|
||||
for value in (sys.float_info.min / 2, -sys.float_info.min / 2):
|
||||
assert math.isnormal(value) is False
|
||||
assert math.issubnormal(value) is True
|
||||
for value in (0.0, -0.0, INF, NINF, NAN):
|
||||
assert math.isnormal(value) is False
|
||||
assert math.issubnormal(value) is False
|
||||
for value in (0.0, 1.0, INF, NAN):
|
||||
assert math.signbit(math.copysign(value, 1.0)) is False
|
||||
assert math.signbit(math.copysign(value, -1.0)) is True
|
||||
|
||||
class FloatArgument:
|
||||
def __float__(self):
|
||||
return -2.0
|
||||
|
||||
class IndexArgument:
|
||||
def __index__(self):
|
||||
return 2
|
||||
|
||||
for function in (math.isnormal, math.issubnormal, math.signbit):
|
||||
assert function(FloatArgument()) == function(-2.0)
|
||||
assert function(IndexArgument()) == function(2.0)
|
||||
assert_raises(TypeError, function, object())
|
||||
assert_raises(TypeError, function, x=1.0)
|
||||
assert_raises(TypeError, function)
|
||||
assert_raises(TypeError, function, 1.0, 2.0)
|
||||
|
||||
for function, expected in ((math.fmax, 2.0), (math.fmin, -2.0)):
|
||||
assert function(-2.0, 2.0) == expected
|
||||
assert function(2.0, -2.0) == expected
|
||||
assert type(function(1, 2)) is float
|
||||
assert function(FloatArgument(), IndexArgument()) == expected
|
||||
assert math.isnan(function(NAN, NAN))
|
||||
for value in (0.0, -0.0, 1.0, -1.0, INF, NINF):
|
||||
for result in (function(NAN, value), function(value, NAN)):
|
||||
assert result == value
|
||||
assert math.signbit(result) == math.signbit(value)
|
||||
assert function(0.0, -0.0) == 0.0
|
||||
assert function(-0.0, 0.0) == 0.0
|
||||
assert_raises(TypeError, function, object(), 1.0)
|
||||
assert_raises(TypeError, function, 1.0, object())
|
||||
assert_raises(TypeError, function, x=1.0, y=2.0)
|
||||
assert_raises(TypeError, function, 1.0)
|
||||
assert_raises(TypeError, function, 1.0, 2.0, 3.0)
|
||||
assert math.fmax(NINF, INF) == INF
|
||||
assert math.fmin(NINF, INF) == NINF
|
||||
|
||||
|
||||
test_math_315_apis()
|
||||
|
||||
# assert(math.exp(2) == math.exp(2.0))
|
||||
# assert(math.exp(True) == math.exp(1.0))
|
||||
#
|
||||
|
||||
@@ -651,6 +651,7 @@ DEPENDENCIES = {
|
||||
"lib": [],
|
||||
"test": [
|
||||
"test_math.py",
|
||||
"test_math_integer.py",
|
||||
"test_math_property.py",
|
||||
],
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user