Merge pull request #1115 from skirpichev/shortest-repr/1028
Use shortest decimal representations for str/repr
This commit is contained in:
@@ -31,5 +31,6 @@ def reset_mp_globals():
|
||||
mpmath.mp.pretty = False
|
||||
mpmath.mp.rounding = 'n'
|
||||
mpmath.mp.pretty_dps = "str"
|
||||
mpmath.mp.shortest_str = False
|
||||
mpmath.iv.prec = mpmath.mp.prec
|
||||
mpmath.iv.pretty = False
|
||||
|
||||
+21
-1
@@ -83,6 +83,7 @@ Mpmath uses a global working precision; it does not keep track of the precision
|
||||
mp.rounding = 'n' [default: 'n']
|
||||
mp.trap_complex = False [default: False]
|
||||
mp.pretty_dps = 'str' [default: 'str']
|
||||
mp.shortest_str = False [default: False]
|
||||
|
||||
The term **prec** denotes the binary precision (measured in bits) while **dps** (short for *decimal places*) is the decimal precision. Binary and decimal precision are related roughly according to the formula ``prec = 3.33*dps``. For example, it takes a precision of roughly 333 bits to hold an approximation of pi that is accurate to 100 decimal places (actually slightly more than 333 bits is used).
|
||||
|
||||
@@ -234,7 +235,26 @@ Setting the ``mp.pretty`` option will use the ``str()``-style output for ``repr(
|
||||
>>> mpf(0.6)
|
||||
mpf('0.59999999999999998')
|
||||
|
||||
To use enough digits to be able recreate value exactly, set ``mp.pretty_dps``
|
||||
To use enough digits to be able recreate value exactly, enable
|
||||
``mp.shortest_str`` option. With this, repr/str and the new-style string
|
||||
formatting *without format specifier* will use *minimal* number of decimal
|
||||
digits that will preserve value on string input, like repr for CPython's
|
||||
builtin floats:
|
||||
|
||||
>>> mp.shortest_str = True
|
||||
>>> mp.pretty = True
|
||||
>>> mpf(10.9) == mpf("10.9")
|
||||
True
|
||||
>>> mpf(10.9)
|
||||
10.9
|
||||
>>> f"{_}"
|
||||
'10.9'
|
||||
>>> mp.pretty = False
|
||||
>>> mpf(10.9)
|
||||
mpf('10.9')
|
||||
>>> mp.shortest_str = False
|
||||
|
||||
Alternatively, set ``mp.pretty_dps``
|
||||
to ``"repr"`` (default value is ``"str"``). Same option is used to control
|
||||
default number of digits in the new-style string formatting *without format
|
||||
specifier*, i.e. ``format(exp(mpf(1)))``.
|
||||
|
||||
@@ -43,6 +43,8 @@ parser.add_argument('--no-pretty', help='Disable pretty-printing',
|
||||
parser.add_argument('--int-limits',
|
||||
help="Enable string conversion length limitation for int's",
|
||||
action='store_true')
|
||||
parser.add_argument('--shortest-str', help='Use shortest str/repr',
|
||||
action='store_true')
|
||||
|
||||
|
||||
def main():
|
||||
@@ -64,6 +66,8 @@ def main():
|
||||
if not args.no_pretty:
|
||||
lines.append('mp.pretty = True')
|
||||
lines.append('mp.pretty_dps = "repr"')
|
||||
if args.shortest_str:
|
||||
lines.append('mp.shortest_str = True')
|
||||
|
||||
try:
|
||||
import IPython
|
||||
|
||||
@@ -52,6 +52,7 @@ class MPContext(BaseMPContext, StandardBaseContext):
|
||||
rounding=round_nearest, trap_complex=False):
|
||||
BaseMPContext.__init__(ctx)
|
||||
ctx.pretty = False
|
||||
ctx.shortest_str = False
|
||||
ctx.types = [ctx.mpf, ctx.mpc, ctx.constant]
|
||||
ctx.default()
|
||||
ctx._set_prec(prec)
|
||||
@@ -372,6 +373,7 @@ class MPContext(BaseMPContext, StandardBaseContext):
|
||||
(" mp.rounding = '%s'" % ctx.rounding).ljust(30) + f"[default: 'n']",
|
||||
(" mp.trap_complex = %s" % ctx.trap_complex).ljust(30) + "[default: False]",
|
||||
(" mp.pretty_dps = '%s'" % ctx.pretty_dps).ljust(30) + "[default: 'str']",
|
||||
(" mp.shortest_str = %s" % ctx.shortest_str).ljust(30) + "[default: False]",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
+19
-2
@@ -136,13 +136,22 @@ class _mpf(mpnumeric):
|
||||
def __repr__(self):
|
||||
ctx = self.context
|
||||
if ctx.pretty:
|
||||
if ctx.shortest_str:
|
||||
return str(self)
|
||||
ndigits = (ctx._repr_digits
|
||||
if ctx._pretty_repr_dps else ctx._str_digits)
|
||||
return to_str(self._mpf_, ndigits)
|
||||
prec, rounding = ctx._prec_rounding
|
||||
if ctx.shortest_str:
|
||||
return f"mpf({format_mpf(self._mpf_, '', prec, rounding, ctx._pretty_repr_dps, True)!r})"
|
||||
return f"mpf({to_str(self._mpf_, ctx._repr_digits)!r})"
|
||||
|
||||
def __str__(self):
|
||||
ctx = self.context
|
||||
if ctx.shortest_str:
|
||||
prec, rounding = ctx._prec_rounding
|
||||
return format_mpf(self._mpf_, '', prec, rounding,
|
||||
ctx._pretty_repr_dps, True)
|
||||
return to_str(self._mpf_, ctx._str_digits)
|
||||
|
||||
def __hash__(self): return mpf_hash(self._mpf_)
|
||||
@@ -450,7 +459,8 @@ class _mpf(mpnumeric):
|
||||
_, _, (prec, rounding) = self._ctxdata
|
||||
ctx = self.context
|
||||
return format_mpf(self._mpf_, format_spec, prec, rounding,
|
||||
ctx._pretty_repr_dps)
|
||||
ctx._pretty_repr_dps,
|
||||
ctx.shortest_str)
|
||||
|
||||
def sqrt(self):
|
||||
ctx = self.context
|
||||
@@ -547,6 +557,8 @@ class _mpc(mpnumeric):
|
||||
def __repr__(self):
|
||||
ctx = self.context
|
||||
if ctx.pretty:
|
||||
if ctx.shortest_str:
|
||||
return str(self)
|
||||
ndigits = (ctx._repr_digits
|
||||
if ctx._pretty_repr_dps else ctx._str_digits)
|
||||
return f"({mpc_to_str(self._mpc_, ndigits)})"
|
||||
@@ -556,6 +568,10 @@ class _mpc(mpnumeric):
|
||||
|
||||
def __str__(self):
|
||||
ctx = self.context
|
||||
if ctx.shortest_str:
|
||||
prec, rounding = ctx._prec_rounding
|
||||
return format_mpc(self._mpc_, '', prec, rounding,
|
||||
ctx._pretty_repr_dps, True)
|
||||
return f"({mpc_to_str(self._mpc_, ctx._str_digits)})"
|
||||
|
||||
def __complex__(self):
|
||||
@@ -761,7 +777,8 @@ class _mpc(mpnumeric):
|
||||
ctx = self.context
|
||||
_, _, (prec, rounding) = self._ctxdata
|
||||
return format_mpc(self._mpc_, format_spec, prec, rounding,
|
||||
ctx._pretty_repr_dps)
|
||||
ctx._pretty_repr_dps,
|
||||
ctx.shortest_str)
|
||||
|
||||
|
||||
complex_types = (complex, _mpc)
|
||||
|
||||
+160
-10
@@ -3,6 +3,7 @@ Low-level functions for arbitrary-precision floating-point arithmetic.
|
||||
"""
|
||||
|
||||
import math
|
||||
import operator
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
@@ -1025,6 +1026,97 @@ def mpf_perturb(x, eps_sign, prec, rnd):
|
||||
# Radix conversion #
|
||||
#----------------------------------------------------------------------------#
|
||||
|
||||
stddigits_as_bytes = bytearray(stddigits.encode('ascii'))
|
||||
|
||||
def fpp2(x, prec=0, base=10):
|
||||
"""
|
||||
(FPP)² algorithm from "How to Print Floating-Point Numbers Accurately"
|
||||
by Steele & White. Assume round_nearest rounding mode.
|
||||
|
||||
The output is correctly rounded. Carry doesn't propagate on rounding. The
|
||||
original x can be recreated, when output submitted to from_str() with
|
||||
round_nearest rounding. No "garbage digits" produced.
|
||||
"""
|
||||
_, man, exp, bc = x
|
||||
if not man:
|
||||
assert not exp
|
||||
return "0", 0
|
||||
prec = prec if prec else bc
|
||||
man <<= prec - bc
|
||||
exp += bc
|
||||
assert 0 < man < 2**prec
|
||||
|
||||
# Original version doesn't implement rounding correctly, we take this
|
||||
# into account, using strict inequatities for low/high conditions,
|
||||
# following the Burger & Dybvig Scheme code from "Printing Floating-Point
|
||||
# Numbers Quickly and Accurately".
|
||||
is_even = man & 1 == 0
|
||||
cmp = operator.le if is_even else operator.lt
|
||||
rev_cmp = operator.lt if is_even else operator.le
|
||||
|
||||
# Step 1. Initialize variables.
|
||||
ep = exp - prec
|
||||
R = man << max(ep, 0) + 1
|
||||
S = 1 << max(-ep, 0) + 1
|
||||
Mminus = Mplus = 1 << max(ep, 0)
|
||||
if man == 1 << (prec - 1):
|
||||
Mplus <<= 1
|
||||
R <<= 1
|
||||
S <<= 1
|
||||
|
||||
# Step 2. Compute ceil(log((R + Mplus)/S, base)).
|
||||
# We use (undocumented) support for computing logarithms of
|
||||
# big integers (that overflows floats). This is available
|
||||
# also on PyPy and GraalPy.
|
||||
k = math.ceil((math.log2(int(R + Mplus)) -
|
||||
math.log2(int(S)))/math.log2(int(base)))
|
||||
if k < 0:
|
||||
bk = base**-k
|
||||
R *= bk
|
||||
Mplus *= bk
|
||||
Mminus *= bk
|
||||
if k > 0:
|
||||
S *= base**k
|
||||
# k might be either exact or by 1 too big.
|
||||
if rev_cmp(R + Mplus, S):
|
||||
k -= 1
|
||||
R *= base
|
||||
Mplus *= base
|
||||
Mminus *= base
|
||||
assert cmp(S, R + Mplus)
|
||||
D = bytearray()
|
||||
|
||||
# Step 3. Generate digits.
|
||||
while True:
|
||||
U, R = divmod(R, S)
|
||||
low = cmp(R, Mminus)
|
||||
high = cmp(S, R + Mplus)
|
||||
D.append(stddigits_as_bytes[U])
|
||||
|
||||
if low or high:
|
||||
# Step 4. Break the loop, round last digit.
|
||||
round_up = high
|
||||
if low and high:
|
||||
round_up = 2*R >= S
|
||||
# Theorem 4 in the Burger & Dybvig article is invalid,
|
||||
# and the algorithm actually depends on how the input
|
||||
# routine break ties. Following code assumes default IEEE
|
||||
# rounding mode, i.e. the mpmath's round_nearest.
|
||||
if round_up and 2*R == S:
|
||||
round_up = U & 1
|
||||
if round_up:
|
||||
# But Theorem 1 is still valid: no carry should
|
||||
# be generated on rounding up.
|
||||
assert ord('0') <= D[-1] < ord(stddigits[base - 1])
|
||||
D[-1] += 1
|
||||
break
|
||||
|
||||
R *= base
|
||||
Mminus *= base
|
||||
Mplus *= base
|
||||
|
||||
return D.decode(), k
|
||||
|
||||
def to_digits_exp(s, dps, base=10):
|
||||
"""Helper function for representing the floating-point number s as
|
||||
a string with dps digits. Returns (sign, string, exponent) where
|
||||
@@ -1573,7 +1665,7 @@ def fill_sep(digits, sep, prev, nmod, sep_range):
|
||||
for pos in range(nmod, len(digits), sep_range))
|
||||
|
||||
|
||||
def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
||||
def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps, unique):
|
||||
capitalize = False
|
||||
if format_dict['type'] in list('AFGE'):
|
||||
capitalize = True
|
||||
@@ -1587,10 +1679,12 @@ def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
||||
num = mpf_mul(num, from_int(100), prec, rnd=round_nearest)
|
||||
|
||||
dps = format_dict['precision']
|
||||
if dps >= 0 or fmt_type:
|
||||
unique = False
|
||||
|
||||
int_part = ''
|
||||
exponent = ''
|
||||
sign = ''
|
||||
sign = '-' if num[0] else ''
|
||||
|
||||
# Now the general case
|
||||
strip_last_zero = False
|
||||
@@ -1598,7 +1692,7 @@ def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
||||
|
||||
rnd = format_dict.get('rounding', rnd)
|
||||
|
||||
if not fmt_type or fmt_type == 'g':
|
||||
if not unique and (not fmt_type or fmt_type == 'g'):
|
||||
if not format_dict['alternate']:
|
||||
strip_zeros = True
|
||||
if fmt_type == 'g':
|
||||
@@ -1625,6 +1719,51 @@ def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
||||
if capitalize:
|
||||
frac_part = frac_part.upper()
|
||||
|
||||
elif unique:
|
||||
if abs(num[2] + num[3] - prec) > 10000:
|
||||
dps = repr_dps(prec)
|
||||
_, digits, exp = to_digits_exp(num, dps, 10)
|
||||
if len(digits) > dps:
|
||||
digits, exp = round_digits(num, digits, exp, dps,
|
||||
10, round_nearest)
|
||||
prev_digits = digits
|
||||
prev_exp = exp
|
||||
while True:
|
||||
dps -= 1
|
||||
new_digits, new_exp = round_digits(num, digits, exp, dps,
|
||||
10, round_down)
|
||||
new_str = f"{sign}{new_digits[0]}.{new_digits[1:]}e{exp}"
|
||||
if from_str(new_str, prec, round_nearest, 10) != num:
|
||||
new_digits, new_exp = round_digits(num, digits, exp, dps,
|
||||
10, round_up)
|
||||
new_str = f"{sign}{new_digits[0]}.{new_digits[1:]}e{exp}"
|
||||
if from_str(new_str, prec, round_nearest, 10) != num:
|
||||
digits = prev_digits
|
||||
exp = prev_exp
|
||||
break
|
||||
prev_digits = new_digits
|
||||
prev_exp = new_exp
|
||||
else:
|
||||
num = mpf_pos(num, prec, rnd) # workaround issue 1158
|
||||
# Here be dragons.
|
||||
digits, exp = fpp2(num, prec, 10)
|
||||
|
||||
split = 1
|
||||
if exp < -4 or exp > prec_to_dps(prec):
|
||||
exponent = f'e{exp:+03d}'
|
||||
else:
|
||||
digits += "0"*(exp + 2 - len(digits))
|
||||
if exp < 0:
|
||||
digits = "0"*(-exp) + digits
|
||||
else:
|
||||
split += exp
|
||||
|
||||
int_part = digits[:split]
|
||||
frac_part = digits[split:]
|
||||
|
||||
if frac_part or format_dict['alternate']:
|
||||
frac_part = '.' + frac_part
|
||||
|
||||
elif fmt_type == 'e':
|
||||
int_part, frac_part, exponent = format_scientific(num, dps, rnd=rnd)
|
||||
if strip_zeros:
|
||||
@@ -1667,7 +1806,6 @@ def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
||||
frac_part = fill_sep(frac_part, sep, frac_part[0], 1, sep_range)
|
||||
digits = frac_part + exponent
|
||||
|
||||
sign = '-' if num[0] else ''
|
||||
if sign != '-' and format_dict['sign'] != '-':
|
||||
sign = format_dict['sign']
|
||||
if fmt_type == 'f' and format_dict['no_neg_0']:
|
||||
@@ -1704,9 +1842,10 @@ def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
||||
return sign, int_part + digits
|
||||
|
||||
|
||||
def format_mpf(num, format_spec, prec, rnd, _pretty_repr_dps):
|
||||
def format_mpf(num, format_spec, prec, rnd, _pretty_repr_dps, unique):
|
||||
format_dict = read_format_spec(format_spec)
|
||||
sign, digits = format_digits(num, format_dict, prec, rnd, _pretty_repr_dps)
|
||||
sign, digits = format_digits(num, format_dict, prec, rnd,
|
||||
_pretty_repr_dps, unique)
|
||||
nchars = len(digits) + len(sign)
|
||||
lpad, rpad = calc_padding(
|
||||
nchars, format_dict['width'], format_dict['align'])
|
||||
@@ -1719,7 +1858,7 @@ def format_mpf(num, format_spec, prec, rnd, _pretty_repr_dps):
|
||||
+ rpad*format_dict['fill_char']
|
||||
|
||||
|
||||
def format_mpc(num, format_spec, prec, rnd, _pretty_repr_dps):
|
||||
def format_mpc(num, format_spec, prec, rnd, _pretty_repr_dps, unique):
|
||||
format_dict = read_format_spec(format_spec)
|
||||
|
||||
if format_dict['fill_char'] == '0':
|
||||
@@ -1733,12 +1872,23 @@ def format_mpc(num, format_spec, prec, rnd, _pretty_repr_dps):
|
||||
"format specifier.")
|
||||
|
||||
fmt_type = format_dict['type'].lower()
|
||||
if not fmt_type:
|
||||
if not fmt_type and format_dict['precision'] >= 0:
|
||||
format_dict['type'] = 'g'
|
||||
sign_re, digits_re = format_digits(num[0], format_dict, prec, rnd, _pretty_repr_dps)
|
||||
sign_re, digits_re = format_digits(num[0], format_dict, prec, rnd,
|
||||
_pretty_repr_dps, unique)
|
||||
fmt_sign = format_dict['sign']
|
||||
format_dict['sign'] = '+'
|
||||
sign_im, digits_im = format_digits(num[1], format_dict, prec, rnd, _pretty_repr_dps)
|
||||
sign_im, digits_im = format_digits(num[1], format_dict, prec, rnd,
|
||||
_pretty_repr_dps, unique)
|
||||
if not format_dict['type']:
|
||||
if format_dict['alternate']:
|
||||
if 'e' not in digits_re:
|
||||
digits_re = digits_re.rstrip('0')
|
||||
if 'e' not in digits_im:
|
||||
digits_im = digits_im.rstrip('0')
|
||||
else:
|
||||
digits_re = digits_re.removesuffix('.0')
|
||||
digits_im = digits_im.removesuffix('.0')
|
||||
digits_im += 'j'
|
||||
|
||||
if not fmt_type:
|
||||
|
||||
@@ -46,6 +46,14 @@ def test_bare_console_bare_division():
|
||||
assert c.expect_exact('0.5\r\n>>> ') == 0
|
||||
|
||||
|
||||
def test_bare_console_shortest_str():
|
||||
c = Console(f'{sys.executable} -m mpmath --no-ipython --shortest-str')
|
||||
|
||||
assert c.expect_exact('>>> ') == 0
|
||||
assert c.send('0.1\r\n') == 5
|
||||
assert c.expect_exact('0.1\r\n>>> ') == 0
|
||||
|
||||
|
||||
def test_bare_console_without_ipython():
|
||||
try:
|
||||
import IPython
|
||||
|
||||
@@ -511,18 +511,29 @@ def test_mpf_floats_bulk(fmt, x):
|
||||
assert format(x, fmt) == format(mp.mpf(x), fmt)
|
||||
|
||||
|
||||
@given(fmt_str(types=['']),
|
||||
st.floats(allow_nan=True,
|
||||
allow_infinity=True,
|
||||
allow_subnormal=False))
|
||||
@example('', 1000000000000000.0)
|
||||
def test_mpf_floats_default_bulk(fmt, x):
|
||||
mp.shortest_str = True
|
||||
if not x and math.copysign(1, x) == -1:
|
||||
return # skip negative zero
|
||||
spec = read_format_spec(fmt)
|
||||
assert format(x, fmt) == format(mp.mpf(x), fmt)
|
||||
|
||||
|
||||
@given(fmt_str(types=list('gGfFeE') + [''], for_complex=True),
|
||||
st.complex_numbers(allow_nan=True,
|
||||
allow_infinity=True,
|
||||
allow_subnormal=True))
|
||||
def test_mpc_complexes(fmt, z):
|
||||
def test_mpc_complexes_bulk(fmt, z):
|
||||
mp.pretty_dps = "repr"
|
||||
if ((not z.real and math.copysign(1, z.real) == -1)
|
||||
or (not z.imag and math.copysign(1, z.imag) == -1)):
|
||||
return # skip negative zero
|
||||
spec = read_format_spec(fmt)
|
||||
if spec['frac_separators'] and sys.version_info < (3, 14):
|
||||
return # see also python/cpython#130860
|
||||
if spec['precision'] < 0 and any(math.isfinite(_) for _ in [z.real, z.imag]):
|
||||
# The mpmath could choose a different decimal
|
||||
# representative (wrt CPython) for same binary
|
||||
@@ -535,6 +546,21 @@ def test_mpc_complexes(fmt, z):
|
||||
assert format(z, fmt) == format(mp.mpc(z), fmt)
|
||||
|
||||
|
||||
@given(fmt_str(types=[''], for_complex=True),
|
||||
st.complex_numbers(allow_nan=True,
|
||||
allow_infinity=True,
|
||||
allow_subnormal=False))
|
||||
@example(fmt='', z=complex(0))
|
||||
@example(fmt='#', z=complex(0))
|
||||
def test_mpc_complexes_default_bulk(fmt, z):
|
||||
mp.shortest_str = True
|
||||
if ((not z.real and math.copysign(1, z.real) == -1)
|
||||
or (not z.imag and math.copysign(1, z.imag) == -1)):
|
||||
return # skip negative zero
|
||||
spec = read_format_spec(fmt)
|
||||
assert format(z, fmt) == format(mp.mpc(z), fmt)
|
||||
|
||||
|
||||
def test_mpc_fmt():
|
||||
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):=10f}')
|
||||
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):010f}')
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import example, given
|
||||
|
||||
from mpmath import inf, matrix, mp, mpc, nstr
|
||||
from mpmath import inf, matrix, mp, mpc, mpf, nstr, rand
|
||||
|
||||
|
||||
A1 = matrix([])
|
||||
@@ -62,8 +65,95 @@ def test_matrix_str():
|
||||
@example(x=6.170920920537087e+17, rnd='f')
|
||||
def test_eval_repr_roundtrip(x, rnd):
|
||||
mp.rounding = rnd
|
||||
mp.shortest_str = False
|
||||
mp.pretty = True
|
||||
mp.pretty_dps = 'repr'
|
||||
mx = mp.mpf(x)
|
||||
smx = repr(mx)
|
||||
assert mx == mp.mpf(smx)
|
||||
mp.pretty_dps = 'str'
|
||||
mp.shortest_str = True
|
||||
smx = repr(mx)
|
||||
assert mx == mp.mpf(smx)
|
||||
|
||||
|
||||
@given(st.floats(allow_subnormal=False,
|
||||
allow_nan=False,
|
||||
allow_infinity=False))
|
||||
@example(1.0)
|
||||
@example(-10.0)
|
||||
@example(3.411330784663857e+16)
|
||||
@example(5.960464477539063e-08)
|
||||
@example(562949953421312.2)
|
||||
def test_float_short_repr(f):
|
||||
mp.shortest_str = True
|
||||
if not f and math.copysign(1, f) == -1:
|
||||
return
|
||||
s = str(f)
|
||||
m = mpf(f)
|
||||
sm = str(m)
|
||||
assert s == sm
|
||||
assert f"mpf('{s}')" == repr(m)
|
||||
assert m == mpf(sm)
|
||||
|
||||
|
||||
@given(st.complex_numbers(allow_subnormal=False,
|
||||
allow_nan=False,
|
||||
allow_infinity=False))
|
||||
@example(1+0.1j)
|
||||
def test_complex_short_repr(z):
|
||||
mp.shortest_str = True
|
||||
mp.pretty = False
|
||||
if ((not z.real and math.copysign(1, z.real) == -1)
|
||||
or (not z.imag and math.copysign(1, z.imag) == -1)):
|
||||
return # skip negative zero
|
||||
s = str(z)
|
||||
mz = mpc(z)
|
||||
smz = str(mz)
|
||||
assert s == smz
|
||||
assert f"mpc(real='{mz.real!s}', imag='{mz.imag!s}')" == repr(mz)
|
||||
assert mz == mpc(smz)
|
||||
mp.pretty = True
|
||||
assert smz == repr(mz)
|
||||
|
||||
|
||||
def test_short_repr_specials():
|
||||
mp.shortest_str = True
|
||||
assert str(mpf(0)) == '0.0'
|
||||
assert str(mpf('inf')) == 'inf'
|
||||
assert str(mpf('-inf')) == '-inf'
|
||||
assert str(mpf('nan')) == 'nan'
|
||||
|
||||
|
||||
def test_short_repr_roundtrip():
|
||||
mp.shortest_str = True
|
||||
for dps in [15, 20, 30, 50, 100, 300]:
|
||||
with mp.workdps(dps):
|
||||
for _ in range(1000):
|
||||
f = random.choice([(rand()-0.5)*2 for _ in range(10)]
|
||||
+ [(rand()-0.5)*2*10**5 for _ in range(5)]
|
||||
+ [(rand()-0.5)*2/10**5 for _ in range(5)]
|
||||
+ [(rand()-0.5)*2*10**100 for _ in range(2)]
|
||||
+ [(rand()-0.5)*2*10**10000 for _ in range(2)]
|
||||
+ [(rand()-0.5)*2/10**10000 for _ in range(2)])
|
||||
s = str(f)
|
||||
b = mpf(s)
|
||||
assert f == b # round-trip
|
||||
|
||||
integer, *frac = s.split('.')
|
||||
if not frac:
|
||||
continue
|
||||
frac = frac[0]
|
||||
if len(frac) < 2:
|
||||
continue
|
||||
frac, *exponent = frac.split('e')
|
||||
exponent = 'e' + exponent[0] if exponent else ''
|
||||
|
||||
# round-trip:
|
||||
assert f == mpf(str(integer + '.' + frac + exponent))
|
||||
|
||||
# test that short repr is really minimal
|
||||
frac = frac[:-1]
|
||||
for d in range(10):
|
||||
frac = frac[:-1] + str(d)
|
||||
assert f != mpf(str(integer + '.' + frac + exponent))
|
||||
|
||||
Reference in New Issue
Block a user