mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
* 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
544 lines
16 KiB
Python
544 lines
16 KiB
Python
import itertools
|
|
import math
|
|
|
|
from testutils import assert_raises, skip_if_unsupported
|
|
|
|
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))
|
|
#
|
|
# class Conversible():
|
|
# def __float__(self):
|
|
# print("Converting to float now!")
|
|
# return 1.1111
|
|
#
|
|
# assert math.log(1.1111) == math.log(Conversible())
|
|
|
|
# A logarithm can fit in a float even when its integer base cannot.
|
|
assert math.isclose(math.log(2**4000, 2**2000), 2.0)
|
|
assert math.isclose(math.log(2.0, 2**2000), 1.0 / 2000)
|
|
|
|
for value in (1, 8.0, INF, NAN):
|
|
assert_raises(ZeroDivisionError, math.log, value, 1)
|
|
assert_raises(TypeError, math.log, object(), 1)
|
|
assert_raises(ValueError, math.log, 8, -(2**2000))
|
|
|
|
log_conversions = []
|
|
|
|
|
|
class LogFloat:
|
|
def __init__(self, name, value):
|
|
self.name = name
|
|
self.value = value
|
|
|
|
def __float__(self):
|
|
log_conversions.append(self.name)
|
|
return self.value
|
|
|
|
|
|
assert math.isclose(math.log(LogFloat("x", 8.0), LogFloat("base", 2.0)), 3.0)
|
|
assert log_conversions == ["x", "base"]
|
|
log_conversions.clear()
|
|
assert_raises(ValueError, math.log, -1, LogFloat("base", 2.0))
|
|
assert log_conversions == []
|
|
|
|
|
|
class LogFloatOverflow:
|
|
def __float__(self):
|
|
log_conversions.append("float")
|
|
raise OverflowError("float conversion failed")
|
|
|
|
def __index__(self):
|
|
log_conversions.append("index")
|
|
return 2**2000
|
|
|
|
|
|
class LogIndexOnly:
|
|
def __index__(self):
|
|
return 2**2000
|
|
|
|
|
|
for logarithm in (math.log, math.log2, math.log10):
|
|
log_conversions.clear()
|
|
assert math.isclose(logarithm(LogFloatOverflow()), logarithm(2**2000))
|
|
assert log_conversions == ["float", "index"]
|
|
assert math.isclose(logarithm(LogIndexOnly()), logarithm(2**2000))
|
|
|
|
for error_type in (TypeError, ValueError, RuntimeError):
|
|
error = error_type("float conversion failed")
|
|
|
|
class FailedFloat(LogFloatOverflow):
|
|
def __float__(self):
|
|
raise error
|
|
|
|
with assert_raises(error_type) as caught:
|
|
logarithm(FailedFloat())
|
|
assert caught.exception is error
|
|
|
|
error = RuntimeError("index conversion failed")
|
|
|
|
class FailedIndex(LogFloatOverflow):
|
|
def __index__(self):
|
|
raise error
|
|
|
|
with assert_raises(RuntimeError) as caught:
|
|
logarithm(FailedIndex())
|
|
assert caught.exception is error
|
|
|
|
class NegativeIndex(LogFloatOverflow):
|
|
def __index__(self):
|
|
return -1
|
|
|
|
assert_raises(ValueError, logarithm, NegativeIndex())
|
|
|
|
class InvalidIndex(LogFloatOverflow):
|
|
def __index__(self):
|
|
return 1.0
|
|
|
|
assert_raises(TypeError, logarithm, InvalidIndex())
|
|
|
|
class NoIndex:
|
|
__float__ = LogFloatOverflow.__float__
|
|
|
|
assert_raises(OverflowError, logarithm, NoIndex())
|
|
|
|
class SuccessfulFloat:
|
|
def __float__(self):
|
|
return 8.0
|
|
|
|
def __index__(self):
|
|
raise AssertionError("successful float conversion must not retry index")
|
|
|
|
assert logarithm(SuccessfulFloat()) == logarithm(8.0)
|
|
|
|
assert math.isclose(math.log(8, LogFloatOverflow()), 3 / 2000)
|
|
assert math.isclose(math.log(8, LogIndexOnly()), 3 / 2000)
|
|
|
|
|
|
class LogInt(int):
|
|
def __float__(self):
|
|
raise AssertionError("integer logarithms use the native integer value")
|
|
|
|
|
|
assert math.isclose(math.log(LogInt(2**4000), LogInt(2**2000)), 2.0)
|
|
|
|
# roundings
|
|
assert int.__trunc__
|
|
assert int.__floor__
|
|
assert int.__ceil__
|
|
|
|
# assert float.__trunc__
|
|
|
|
|
|
def float_floor_exists():
|
|
assert float.__floor__
|
|
|
|
|
|
def float_ceil_exists():
|
|
assert float.__ceil__
|
|
|
|
|
|
skip_if_unsupported(3, 9, float_floor_exists)
|
|
skip_if_unsupported(3, 9, float_ceil_exists)
|
|
|
|
assert math.trunc(2) == 2
|
|
assert math.ceil(3) == 3
|
|
assert math.floor(4) == 4
|
|
|
|
assert math.trunc(2.2) == 2
|
|
assert math.ceil(3.3) == 4
|
|
assert math.floor(4.4) == 4
|
|
|
|
assert isinstance(math.trunc(2.2), int)
|
|
assert isinstance(math.ceil(3.3), int)
|
|
assert isinstance(math.floor(4.4), int)
|
|
|
|
|
|
class A(object):
|
|
def __trunc__(self):
|
|
return 2
|
|
|
|
def __ceil__(self):
|
|
return 3
|
|
|
|
def __floor__(self):
|
|
return 4
|
|
|
|
|
|
assert math.trunc(A()) == 2
|
|
assert math.ceil(A()) == 3
|
|
assert math.floor(A()) == 4
|
|
|
|
|
|
class A(object):
|
|
def __trunc__(self):
|
|
return 2.2
|
|
|
|
def __ceil__(self):
|
|
return 3.3
|
|
|
|
def __floor__(self):
|
|
return 4.4
|
|
|
|
|
|
assert math.trunc(A()) == 2.2
|
|
assert math.ceil(A()) == 3.3
|
|
assert math.floor(A()) == 4.4
|
|
|
|
|
|
class A(object):
|
|
def __trunc__(self):
|
|
return "trunc"
|
|
|
|
def __ceil__(self):
|
|
return "ceil"
|
|
|
|
def __floor__(self):
|
|
return "floor"
|
|
|
|
|
|
assert math.trunc(A()) == "trunc"
|
|
assert math.ceil(A()) == "ceil"
|
|
assert math.floor(A()) == "floor"
|
|
|
|
with assert_raises(TypeError):
|
|
math.trunc(object())
|
|
with assert_raises(TypeError):
|
|
math.ceil(object())
|
|
with assert_raises(TypeError):
|
|
math.floor(object())
|
|
with assert_raises(TypeError) as cm:
|
|
math.floor("1.5")
|
|
assert str(cm.exception) == "must be real number, not str"
|
|
with assert_raises(TypeError) as cm:
|
|
math.ceil(None)
|
|
assert str(cm.exception) == "must be real number, not NoneType"
|
|
|
|
assert math.floor(-0.5) == -1 and math.ceil(-0.5) == 0
|
|
with assert_raises(OverflowError):
|
|
math.floor(INF)
|
|
with assert_raises(ValueError):
|
|
math.ceil(NAN)
|
|
|
|
|
|
class FloatFloor(float):
|
|
def __floor__(self):
|
|
return "sub floor"
|
|
|
|
def __ceil__(self):
|
|
return "sub ceil"
|
|
|
|
|
|
assert math.floor(FloatFloor(1.5)) == "sub floor"
|
|
assert math.ceil(FloatFloor(1.5)) == "sub ceil"
|
|
|
|
isclose = math.isclose
|
|
|
|
|
|
def assertIsClose(a, b, *args, **kwargs):
|
|
assert isclose(a, b, *args, **kwargs) == True, "%s and %s should be close!" % (a, b)
|
|
|
|
|
|
def assertIsNotClose(a, b, *args, **kwargs):
|
|
assert isclose(a, b, *args, **kwargs) == False, "%s and %s should not be close!" % (
|
|
a,
|
|
b,
|
|
)
|
|
|
|
|
|
def assertAllClose(examples, *args, **kwargs):
|
|
for a, b in examples:
|
|
assertIsClose(a, b, *args, **kwargs)
|
|
|
|
|
|
def assertAllNotClose(examples, *args, **kwargs):
|
|
for a, b in examples:
|
|
assertIsNotClose(a, b, *args, **kwargs)
|
|
|
|
|
|
# test_negative_tolerances: ValueError should be raised if either tolerance is less than zero
|
|
assert_raises(ValueError, lambda: isclose(1, 1, rel_tol=-1e-100))
|
|
assert_raises(ValueError, lambda: isclose(1, 1, rel_tol=1e-100, abs_tol=-1e10))
|
|
|
|
# test_identical: identical values must test as close
|
|
identical_examples = [
|
|
(2.0, 2.0),
|
|
(0.1e200, 0.1e200),
|
|
(1.123e-300, 1.123e-300),
|
|
(12345, 12345.0),
|
|
(0.0, -0.0),
|
|
(345678, 345678),
|
|
]
|
|
assertAllClose(identical_examples, rel_tol=0.0, abs_tol=0.0)
|
|
|
|
# test_eight_decimal_places: examples that are close to 1e-8, but not 1e-9
|
|
eight_decimal_places_examples = [
|
|
(1e8, 1e8 + 1),
|
|
(-1e-8, -1.000000009e-8),
|
|
(1.12345678, 1.12345679),
|
|
]
|
|
assertAllClose(eight_decimal_places_examples, rel_tol=1e-08)
|
|
assertAllNotClose(eight_decimal_places_examples, rel_tol=1e-09)
|
|
|
|
# test_near_zero: values close to zero
|
|
near_zero_examples = [(1e-9, 0.0), (-1e-9, 0.0), (-1e-150, 0.0)]
|
|
# these should not be close to any rel_tol
|
|
assertAllNotClose(near_zero_examples, rel_tol=0.9)
|
|
# these should be close to abs_tol=1e-8
|
|
assertAllClose(near_zero_examples, abs_tol=1e-8)
|
|
|
|
# test_identical_infinite: these are close regardless of tolerance -- i.e. they are equal
|
|
assertIsClose(INF, INF)
|
|
assertIsClose(INF, INF, abs_tol=0.0)
|
|
assertIsClose(NINF, NINF)
|
|
assertIsClose(NINF, NINF, abs_tol=0.0)
|
|
|
|
# test_inf_ninf_nan(self): these should never be close (following IEEE 754 rules for equality)
|
|
not_close_examples = [
|
|
(NAN, NAN),
|
|
(NAN, 1e-100),
|
|
(1e-100, NAN),
|
|
(INF, NAN),
|
|
(NAN, INF),
|
|
(INF, NINF),
|
|
(INF, 1.0),
|
|
(1.0, INF),
|
|
(INF, 1e308),
|
|
(1e308, INF),
|
|
]
|
|
# use largest reasonable tolerance
|
|
assertAllNotClose(not_close_examples, abs_tol=0.999999999999999)
|
|
|
|
# test_zero_tolerance: test with zero tolerance
|
|
zero_tolerance_close_examples = [(1.0, 1.0), (-3.4, -3.4), (-1e-300, -1e-300)]
|
|
assertAllClose(zero_tolerance_close_examples, rel_tol=0.0)
|
|
zero_tolerance_not_close_examples = [
|
|
(1.0, 1.000000000000001),
|
|
(0.99999999999999, 1.0),
|
|
(1.0e200, 0.999999999999999e200),
|
|
]
|
|
assertAllNotClose(zero_tolerance_not_close_examples, rel_tol=0.0)
|
|
|
|
# test_asymmetry: test the asymmetry example from PEP 485
|
|
assertAllClose([(9, 10), (10, 9)], rel_tol=0.1)
|
|
|
|
# test_integers: test with integer values
|
|
integer_examples = [(100000001, 100000000), (123456789, 123456788)]
|
|
|
|
assertAllClose(integer_examples, rel_tol=1e-8)
|
|
assertAllNotClose(integer_examples, rel_tol=1e-9)
|
|
|
|
# test_decimals: test with Decimal values
|
|
# test_fractions: test with Fraction values
|
|
|
|
assert math.copysign(1, 42) == 1.0
|
|
assert math.copysign(0.0, 42) == 0.0
|
|
assert math.copysign(1.0, -42) == -1.0
|
|
assert math.copysign(3, 0.0) == 3.0
|
|
assert math.copysign(4.0, -0.0) == -4.0
|
|
assert_raises(TypeError, math.copysign)
|
|
# copysign should let us distinguish signs of zeros
|
|
assert math.copysign(1.0, 0.0) == 1.0
|
|
assert math.copysign(1.0, -0.0) == -1.0
|
|
assert math.copysign(INF, 0.0) == INF
|
|
assert math.copysign(INF, -0.0) == NINF
|
|
assert math.copysign(NINF, 0.0) == INF
|
|
assert math.copysign(NINF, -0.0) == NINF
|
|
# and of infinities
|
|
assert math.copysign(1.0, INF) == 1.0
|
|
assert math.copysign(1.0, NINF) == -1.0
|
|
assert math.copysign(INF, INF) == INF
|
|
assert math.copysign(INF, NINF) == NINF
|
|
assert math.copysign(NINF, INF) == INF
|
|
assert math.copysign(NINF, NINF) == NINF
|
|
assert math.isnan(math.copysign(NAN, 1.0))
|
|
assert math.isnan(math.copysign(NAN, INF))
|
|
assert math.isnan(math.copysign(NAN, NINF))
|
|
assert math.isnan(math.copysign(NAN, NAN))
|
|
# copysign(INF, NAN) may be INF or it may be NINF, since
|
|
# we don't know whether the sign bit of NAN is set on any
|
|
# given platform.
|
|
assert math.isinf(math.copysign(INF, NAN))
|
|
# similarly, copysign(2., NAN) could be 2. or -2.
|
|
assert abs(math.copysign(2.0, NAN)) == 2.0
|
|
|
|
assert str(math.frexp(0.0)) == str((+0.0, 0))
|
|
assert str(math.frexp(-0.0)) == str((-0.0, 0))
|
|
assert math.frexp(1) == (0.5, 1)
|
|
assert math.frexp(1.5) == (0.75, 1)
|
|
assert_raises(TypeError, lambda: math.frexp(None))
|
|
|
|
assert str(math.ldexp(+0.0, 0)) == str(0.0)
|
|
assert str(math.ldexp(-0.0, 0)) == str(-0.0)
|
|
assert math.ldexp(0.5, 1) == 1
|
|
assert math.ldexp(0.75, 1) == 1.5
|
|
assert_raises(TypeError, lambda: math.ldexp(None, None))
|
|
|
|
assert math.frexp(INF) == (INF, 0)
|
|
assert str(math.frexp(NAN)) == str((NAN, 0))
|
|
assert_raises(TypeError, lambda: math.frexp(None))
|
|
|
|
assert math.gcd(0, 0) == 0
|
|
assert math.gcd(1, 0) == 1
|
|
assert math.gcd(0, 1) == 1
|
|
assert math.gcd(1, 1) == 1
|
|
assert math.gcd(-1, 1) == 1
|
|
assert math.gcd(1, -1) == 1
|
|
assert math.gcd(-1, -1) == 1
|
|
assert math.gcd(125, -255) == 5
|
|
assert_raises(TypeError, lambda: math.gcd(1.1, 2))
|
|
|
|
assert math.factorial(0) == 1
|
|
assert math.factorial(1) == 1
|
|
assert math.factorial(2) == 2
|
|
assert math.factorial(3) == 6
|
|
assert math.factorial(10) == 3628800
|
|
assert math.factorial(20) == 2432902008176640000
|
|
assert_raises(ValueError, lambda: math.factorial(-1))
|
|
|
|
if hasattr(math, "nextafter"):
|
|
try:
|
|
assert math.nextafter(4503599627370496.0, -INF) == 4503599627370495.5
|
|
assert math.nextafter(4503599627370496.0, INF) == 4503599627370497.0
|
|
assert math.nextafter(9223372036854775808.0, 0.0) == 9223372036854774784.0
|
|
assert math.nextafter(-9223372036854775808.0, 0.0) == -9223372036854774784.0
|
|
assert math.nextafter(4503599627370496, -INF) == 4503599627370495.5
|
|
assert math.nextafter(2.0, 2.0) == 2.0
|
|
assert math.isnan(math.nextafter(NAN, 1.0))
|
|
except NotImplementedError:
|
|
# WASM
|
|
pass
|
|
|
|
assert math.modf(1.25) == (0.25, 1.0)
|
|
assert math.modf(-1.25) == (-0.25, -1.0)
|
|
assert math.modf(2.56) == (0.56, 2.0)
|
|
assert math.modf(-2.56) == (-0.56, -2.0)
|
|
assert math.modf(1) == (0.0, 1.0)
|
|
assert math.modf(INF) == (0.0, INF)
|
|
assert math.modf(NINF) == (-0.0, NINF)
|
|
modf_nan = math.modf(NAN)
|
|
assert math.isnan(modf_nan[0])
|
|
assert math.isnan(modf_nan[1])
|
|
|
|
assert math.fmod(10, 1) == 0.0
|
|
assert math.fmod(10, 0.5) == 0.0
|
|
assert math.fmod(10, 1.5) == 1.0
|
|
assert math.fmod(-10, 1) == -0.0
|
|
assert math.fmod(-10, 0.5) == -0.0
|
|
assert math.fmod(-10, 1.5) == -1.0
|
|
assert math.isnan(math.fmod(NAN, 1.0)) == True
|
|
assert math.isnan(math.fmod(1.0, NAN)) == True
|
|
assert math.isnan(math.fmod(NAN, NAN)) == True
|
|
assert_raises(ValueError, lambda: math.fmod(1.0, 0.0))
|
|
assert_raises(ValueError, lambda: math.fmod(INF, 1.0))
|
|
assert_raises(ValueError, lambda: math.fmod(NINF, 1.0))
|
|
assert_raises(ValueError, lambda: math.fmod(INF, 0.0))
|
|
assert math.fmod(3.0, INF) == 3.0
|
|
assert math.fmod(-3.0, INF) == -3.0
|
|
assert math.fmod(3.0, NINF) == 3.0
|
|
assert math.fmod(-3.0, NINF) == -3.0
|
|
assert math.fmod(0.0, 3.0) == 0.0
|
|
assert math.fmod(0.0, NINF) == 0.0
|
|
|
|
assert math.gamma(1) == 1.0
|
|
|
|
# sumprod compares the two lengths as it goes; it must not drain either
|
|
# argument first.
|
|
assert_raises(ValueError, lambda: math.sumprod(itertools.count(), [1, 2, 3]))
|
|
assert_raises(ValueError, lambda: math.sumprod([1, 2, 3], itertools.count()))
|
|
assert math.sumprod(iter([1, 2, 3]), iter([4, 5, 6])) == 32
|
|
|
|
# perm/comb reject a k above 2**63 - 1 instead of computing, and perm(n) is factorial(n).
|
|
for f in [
|
|
lambda: math.perm(2**63),
|
|
lambda: math.perm(2**64 + 1, 2**63),
|
|
lambda: math.comb(2**64 + 1, 2**63),
|
|
]:
|
|
assert_raises(OverflowError, f)
|
|
assert_raises(
|
|
ValueError,
|
|
lambda: math.perm(-1),
|
|
_msg="factorial() not defined for negative values",
|
|
)
|
|
assert math.perm(5) == math.perm(5, None) == 120
|
|
assert math.perm(2**64, 0) == 1
|
|
assert math.comb(2**64, 2**64 - 1) == 2**64
|