mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Propagate dialect lookup errors except AttributeError, retry integer logarithms after float overflow, and update the decimal conversion diagnostic. Exercise the final reference contracts without implementation-specific branches. Assisted-by: OpenAI Codex:model identifier unavailable
469 lines
13 KiB
Python
469 lines
13 KiB
Python
import itertools
|
|
import math
|
|
|
|
from testutils import assert_raises, skip_if_unsupported
|
|
|
|
NAN = float("nan")
|
|
INF = float("inf")
|
|
NINF = float("-inf")
|
|
|
|
# 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
|