292 lines
12 KiB
Python
292 lines
12 KiB
Python
import decimal
|
|
import random
|
|
from decimal import Decimal
|
|
from fractions import Fraction
|
|
|
|
import pytest
|
|
|
|
from mpmath import inf, isnan, iv, mp, mpc, mpf, mpi, mpmathify, sqrt
|
|
from mpmath.libmp import (fhalf, from_float, from_rational, from_str,
|
|
round_ceiling, round_floor, round_nearest,
|
|
to_rational, to_str)
|
|
|
|
|
|
def test_basic_string():
|
|
"""
|
|
Test basic string conversion
|
|
"""
|
|
assert mpf('3') == mpf('3.0') == mpf('0003.') == mpf('0.03e2') == mpf(3.0)
|
|
assert mpf('30') == mpf('30.0') == mpf('00030.') == mpf(30.0)
|
|
for i in range(10):
|
|
for j in range(10):
|
|
assert mpf('%ie%i' % (i,j)) == i * 10**j
|
|
assert str(mpf('25000.0')) == '25000.0'
|
|
assert str(mpf('2500.0')) == '2500.0'
|
|
assert str(mpf('250.0')) == '250.0'
|
|
assert str(mpf('25.0')) == '25.0'
|
|
assert str(mpf('2.5')) == '2.5'
|
|
assert str(mpf('0.25')) == '0.25'
|
|
assert str(mpf('0.025')) == '0.025'
|
|
assert str(mpf('0.0025')) == '0.0025'
|
|
assert str(mpf('0.00025')) == '0.00025'
|
|
assert str(mpf('0.000025')) == '2.5e-5'
|
|
assert str(mpf(0)) == '0.0'
|
|
assert str(mpf('2.5e1000000000000000000000')) == '2.5e+1000000000000000000000'
|
|
assert str(mpf('2.6e-1000000000000000000000')) == '2.6e-1000000000000000000000'
|
|
assert str(mpf(1.23402834e-15)) == '1.23402834e-15'
|
|
assert str(mpf(-1.23402834e-15)) == '-1.23402834e-15'
|
|
assert str(mpf(-1.2344e-15)) == '-1.2344e-15'
|
|
assert repr(mpf(-1.2344e-15)) == "mpf('-1.2343999999999999e-15')"
|
|
assert str(mpf("2163048125L")) == '2163048125.0'
|
|
assert str(mpf("-2163048125l")) == '-2163048125.0'
|
|
assert str(mpf("-2163048125L/1088391168")) == '-1.98738118113799'
|
|
assert str(mpf("2163048125/1088391168l")) == '1.98738118113799'
|
|
assert str(mpf('inf')) == 'inf'
|
|
|
|
# issue 613
|
|
assert str(mpf('2_5_0_0.0')) == '2500.0'
|
|
# issue 377
|
|
assert to_str(from_str('1_234.567891', 80), 24) == '1234.567891'
|
|
assert to_str(from_str('1_234.567_891', 80), 24) == '1234.567891'
|
|
assert to_str(from_str('1_234.567_8_9_1', 80), 24) == '1234.567891'
|
|
assert to_str(from_str('1.0_0', 80), 24) == '1.0'
|
|
assert to_str(from_str('.000', 80), 24) == '0.0'
|
|
|
|
def test_from_str():
|
|
assert mpf(from_str('ABC.ABC', base=16)) == mpf(float.fromhex('ABC.ABC'))
|
|
assert mpf(from_str('0xABC.ABC')) == mpf(float.fromhex('ABC.ABC'))
|
|
assert mpf(from_str('0x3.a7p10')) == mpf(float.fromhex('0x3.a7p10'))
|
|
assert mpf(from_str('0x1.4ace478p+33')) == mpf(float.fromhex('0x1.4ace478p+33'))
|
|
assert mpf(from_str('0x1.4ace478@+33')) == mpf('7.0354608312666732e+39')
|
|
assert mpf(from_str('0b1101.100101')) == mpf('13.578125')
|
|
assert mpf(from_str('0o1101.100101')) == mpf('577.12524795532227')
|
|
assert mpf(from_str('1.99999999', prec=0)) == mpf('1.9999999901046976')
|
|
|
|
def test_eps_repr():
|
|
mp.dps = 24
|
|
assert repr(mp.eps) == '<epsilon of working precision: 2.06795e-25~>'
|
|
|
|
def test_to_str():
|
|
assert to_str(from_str('ABC.ABC', base=16), 6, base=16) == '0xabc.abc'
|
|
assert to_str(from_str('0x3.a7p10', base=16), 3, base=16) == '0xe9c.0'
|
|
assert to_str(from_str('0x1.4ace478p+33'), 7, base=16) == '0x2.959c8f@+8'
|
|
assert to_str(from_str('0o1101.100101'), 8, base=8) == '0o1101.1001'
|
|
assert to_str(from_str('0b1101.100101'), 10, base=2) == '0b1101.100101'
|
|
assert to_str(from_str('0x1.4ace478p+33'), 8, base=16, binary_exp=True) == '0x1.4ace478p+33'
|
|
assert to_str(from_str('0x1.4ace478p+33'), 7, base=16, binary_exp=True) == '0x1.4ace48p+33'
|
|
assert to_str(from_str('0x1.4ace478p+33'), 5, base=16, binary_exp=True) == '0x1.4acep+33'
|
|
assert to_str(from_str('1', base=16), 6, base=16, binary_exp=True) == '0x1.0'
|
|
x = mpf('1234.567891')._mpf_
|
|
pytest.raises(ValueError, lambda: to_str(x, 6, binary_exp=True))
|
|
pytest.raises(ValueError, lambda: to_str(x, 6, rnd='Y'))
|
|
pytest.raises(ValueError, lambda: to_str('1e400e2', 6))
|
|
assert to_str(x, 5, rnd='n') == '1234.6'
|
|
assert to_str(x, 5, rnd='d') == '1234.5'
|
|
assert to_str(x, 5, rnd='u') == '1234.6'
|
|
|
|
def test_pretty():
|
|
mp.pretty = True
|
|
assert repr(mpf(2.5)) == '2.5'
|
|
assert repr(mpc(2.5,3.5)) == '(2.5 + 3.5j)'
|
|
iv.pretty = True
|
|
assert repr(mpi(2.5,3.5)) == '[2.5, 3.5]'
|
|
|
|
def test_str_whitespace():
|
|
assert mpf('1.26 ') == 1.26
|
|
|
|
def test_str_format():
|
|
assert to_str(from_float(0.1),15,strip_zeros=False) == '0.100000000000000'
|
|
assert to_str(from_float(0.0),15,show_zero_exponent=True) == '0.0e+0'
|
|
assert to_str(from_float(0.0),0,show_zero_exponent=True) == '.0e+0'
|
|
assert to_str(from_float(0.0),0,show_zero_exponent=False) == '.0'
|
|
assert to_str(from_float(0.0),1,show_zero_exponent=True) == '0.0e+0'
|
|
assert to_str(from_float(0.0),1,show_zero_exponent=False) == '0.0'
|
|
assert to_str(from_float(1.23),3,show_zero_exponent=True) == '1.23e+0'
|
|
assert to_str(from_float(1.23456789000000e-2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e-2'
|
|
assert to_str(from_float(1.23456789000000e+2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e+2'
|
|
assert to_str(from_float(2.1287e14), 15, max_fixed=1000) == '212870000000000.0'
|
|
assert to_str(from_float(2.1287e15), 15, max_fixed=1000) == '2128700000000000.0'
|
|
assert to_str(from_float(2.1287e16), 15, max_fixed=1000) == '21287000000000000.0'
|
|
assert to_str(from_float(2.1287e30), 15, max_fixed=1000) == '2128700000000000000000000000000.0'
|
|
|
|
def test_tight_string_conversion():
|
|
# In an old version, '0.5' wasn't recognized as representing
|
|
# an exact binary number and was erroneously rounded up or down
|
|
assert from_str('0.5', 10, round_floor) == fhalf
|
|
assert from_str('0.5', 10, round_ceiling) == fhalf
|
|
|
|
def test_eval_repr_invariant():
|
|
"""Test that eval(repr(x)) == x"""
|
|
random.seed(123)
|
|
for dps in [10, 15, 20, 50, 100]:
|
|
mp.dps = dps
|
|
for i in range(1000):
|
|
a = mpf(random.random())**0.5 * 10**random.randint(-100, 100)
|
|
assert eval(repr(a)) == a
|
|
|
|
def test_str_bugs():
|
|
# Decimal rounding used to give the wrong exponent in some cases
|
|
assert str(mpf('1e600')) == '1.0e+600'
|
|
assert str(mpf('1e10000')) == '1.0e+10000'
|
|
|
|
def test_str_prec0():
|
|
assert to_str(from_float(1.234), 0) == '.0e+0'
|
|
assert to_str(from_float(1e-15), 0) == '.0e-15'
|
|
assert to_str(from_float(1e+15), 0) == '.0e+15'
|
|
assert to_str(from_float(-1e-15), 0) == '-.0e-15'
|
|
assert to_str(from_float(-1e+15), 0) == '-.0e+15'
|
|
|
|
def test_convert_rational():
|
|
assert from_rational(30, 5, 53, round_nearest)[:3] == (0, 3, 1)
|
|
assert from_rational(-7, 4, 53, round_nearest)[:3] == (1, 7, -2)
|
|
assert to_rational(mpf('0.5')._mpf_) == (1, 2)
|
|
assert to_rational(mpf('1')._mpf_) == (1, 1)
|
|
pytest.raises(ValueError, lambda: to_rational(mpf('nan')._mpf_))
|
|
pytest.raises(OverflowError, lambda: to_rational(mpf('inf')._mpf_))
|
|
pytest.raises(OverflowError, lambda: to_rational(mpf('-inf')._mpf_))
|
|
|
|
def test_custom_class():
|
|
class mympf:
|
|
@property
|
|
def _mpf_(self):
|
|
return mpf(3.5)._mpf_
|
|
class mympc:
|
|
@property
|
|
def _mpc_(self):
|
|
return mpf(3.5)._mpf_, mpf(2.5)._mpf_
|
|
assert mpf(2) + mympf() == 5.5
|
|
assert mympf() + mpf(2) == 5.5
|
|
assert mpf(mympf()) == 3.5
|
|
assert mympc() + mpc(2) == mpc(5.5, 2.5)
|
|
assert mpc(2) + mympc() == mpc(5.5, 2.5)
|
|
assert mpc(mympc()) == (3.5+2.5j)
|
|
assert mpmathify(mympf()) == mpf(3.5)
|
|
assert mpmathify(mympc()) == mpc(3.5, 2.5)
|
|
|
|
def test_conversion_methods():
|
|
class SomethingRandom:
|
|
pass
|
|
class SomethingReal:
|
|
def _mpmath_(self, prec, rounding):
|
|
return mp.make_mpf(from_str('1.3', prec, rounding))
|
|
class SomethingComplex:
|
|
def _mpmath_(self, prec, rounding):
|
|
return mp.make_mpc((from_str('1.3', prec, rounding), \
|
|
from_str('1.7', prec, rounding)))
|
|
x = mpf(3)
|
|
z = mpc(3)
|
|
a = SomethingRandom()
|
|
y = SomethingReal()
|
|
w = SomethingComplex()
|
|
for d in [15, 45]:
|
|
mp.dps = d
|
|
assert (x+y).ae(mpf('4.3'))
|
|
assert (y+x).ae(mpf('4.3'))
|
|
assert (x+w).ae(mpc('4.3', '1.7'))
|
|
assert (w+x).ae(mpc('4.3', '1.7'))
|
|
assert (z+y).ae(mpc('4.3'))
|
|
assert (y+z).ae(mpc('4.3'))
|
|
assert (z+w).ae(mpc('4.3', '1.7'))
|
|
assert (w+z).ae(mpc('4.3', '1.7'))
|
|
x-y; y-x; x-w; w-x; z-y; y-z; z-w; w-z
|
|
x*y; y*x; x*w; w*x; z*y; y*z; z*w; w*z
|
|
x/y; y/x; x/w; w/x; z/y; y/z; z/w; w/z
|
|
x**y; y**x; x**w; w**x; z**y; y**z; z**w; w**z
|
|
x==y; y==x; x==w; w==x; z==y; y==z; z==w; w==z
|
|
mp.dps = 15
|
|
assert x.__add__(a) is NotImplemented
|
|
assert x.__radd__(a) is NotImplemented
|
|
assert x.__lt__(a) is NotImplemented
|
|
assert x.__gt__(a) is NotImplemented
|
|
assert x.__le__(a) is NotImplemented
|
|
assert x.__ge__(a) is NotImplemented
|
|
assert x.__eq__(a) is NotImplemented
|
|
assert x.__ne__(a) is NotImplemented
|
|
assert x.__sub__(a) is NotImplemented
|
|
assert x.__rsub__(a) is NotImplemented
|
|
assert x.__mul__(a) is NotImplemented
|
|
assert x.__rmul__(a) is NotImplemented
|
|
assert x.__truediv__(a) is NotImplemented
|
|
assert x.__rtruediv__(a) is NotImplemented
|
|
assert x.__mod__(a) is NotImplemented
|
|
assert x.__rmod__(a) is NotImplemented
|
|
assert x.__pow__(a) is NotImplemented
|
|
assert x.__rpow__(a) is NotImplemented
|
|
assert z.__add__(a) is NotImplemented
|
|
assert z.__radd__(a) is NotImplemented
|
|
assert z.__eq__(a) is NotImplemented
|
|
assert z.__ne__(a) is NotImplemented
|
|
assert z.__sub__(a) is NotImplemented
|
|
assert z.__rsub__(a) is NotImplemented
|
|
assert z.__mul__(a) is NotImplemented
|
|
assert z.__rmul__(a) is NotImplemented
|
|
assert z.__truediv__(a) is NotImplemented
|
|
assert z.__rtruediv__(a) is NotImplemented
|
|
assert z.__pow__(a) is NotImplemented
|
|
assert z.__rpow__(a) is NotImplemented
|
|
|
|
def test_mpmathify():
|
|
assert mpmathify('1/2') == 0.5
|
|
assert mpmathify('(1.0+1.0j)') == mpc(1, 1)
|
|
assert mpmathify('(1.2e-10 - 3.4e5j)') == mpc('1.2e-10', '-3.4e5')
|
|
assert mpmathify('1j') == mpc(1j)
|
|
assert mpmathify('oo') == mpf('inf')
|
|
assert mpmathify('+oo') == mpf('inf')
|
|
assert mpmathify('-oo') == mpf('-inf')
|
|
assert mpmathify('2+3*I') == mpc(2, 3)
|
|
assert mpmathify('2+3I') == mpc(2, 3)
|
|
assert mpmathify('2/3 + 4/5j') == mpc(2/3, 4/5)
|
|
|
|
def test_issue548():
|
|
try:
|
|
# This expression is invalid, but may trigger the ReDOS vulnerability
|
|
# in the regular expression for parsing complex numbers.
|
|
mpmathify('(' + '1' * 5000 + '!j')
|
|
except:
|
|
return
|
|
# The expression is invalid and should raise an exception.
|
|
assert False
|
|
|
|
def test_compatibility():
|
|
from packaging.version import Version, parse
|
|
np = pytest.importorskip("numpy")
|
|
# numpy types
|
|
for typecode in (np.typecodes['AllInteger']
|
|
+ np.typecodes['Float']
|
|
+ np.typecodes['Complex']):
|
|
nptype = np.dtype(typecode).type
|
|
if issubclass(nptype, np.complexfloating):
|
|
x = nptype(complex(0.5, -0.5))
|
|
elif issubclass(nptype, np.floating):
|
|
x = nptype(0.5)
|
|
elif issubclass(nptype, np.integer):
|
|
x = nptype(2)
|
|
# Handle the weird types
|
|
try: diff = np.abs(type(np.sqrt(x))(sqrt(x)) - np.sqrt(x))
|
|
except: continue
|
|
assert diff < np.float64(2.0**-53)
|
|
assert mpf(np.float64('inf')) == inf
|
|
assert isnan(mp.npconvert(np.float64('nan')))
|
|
if hasattr(np, "float128"):
|
|
mp.prec = 64
|
|
assert (mp.npconvert(np.float128('0.841470984807896506652502321630298954')) ==
|
|
mpf('0.841470984807896506653'))
|
|
mp.prec = 53
|
|
# issues 382 and 539
|
|
assert mp.sqrt(np.int64(1)) == mpf('1.0')
|
|
assert mpf(np.int64(1)) == mpf('1.0')
|
|
#Fraction and Decimal
|
|
oldprec = mp.prec
|
|
mp.prec = 1000
|
|
decimal.getcontext().prec = mp.dps
|
|
assert sqrt(Fraction(2, 3)).ae(sqrt(mpf('2/3')))
|
|
assert sqrt(Decimal(2)/Decimal(3)).ae(sqrt(mpf('2/3')))
|
|
mp.prec = oldprec
|
|
assert mpmathify(np.array(123)) == mpf(123)
|
|
assert mpmathify(np.array(1.25)) == mpf(1.25)
|
|
assert mpmathify(np.array(0.5+1j)) == mpc(0.5+1j)
|
|
pytest.raises(TypeError, lambda: mpmathify(np.array([1])))
|
|
|
|
def test_issue465():
|
|
assert mpf(Fraction(1, 3)) == mpf('0.33333333333333331')
|