Files
RustPython/extra_tests/snippets/stdlib_math.py
T
youknowdot 0f127b07e9 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
2026-10-15 10:02:44 +09:00

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