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.pretty = False
|
||||||
mpmath.mp.rounding = 'n'
|
mpmath.mp.rounding = 'n'
|
||||||
mpmath.mp.pretty_dps = "str"
|
mpmath.mp.pretty_dps = "str"
|
||||||
|
mpmath.mp.shortest_str = False
|
||||||
mpmath.iv.prec = mpmath.mp.prec
|
mpmath.iv.prec = mpmath.mp.prec
|
||||||
mpmath.iv.pretty = False
|
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.rounding = 'n' [default: 'n']
|
||||||
mp.trap_complex = False [default: False]
|
mp.trap_complex = False [default: False]
|
||||||
mp.pretty_dps = 'str' [default: 'str']
|
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).
|
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.6)
|
||||||
mpf('0.59999999999999998')
|
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
|
to ``"repr"`` (default value is ``"str"``). Same option is used to control
|
||||||
default number of digits in the new-style string formatting *without format
|
default number of digits in the new-style string formatting *without format
|
||||||
specifier*, i.e. ``format(exp(mpf(1)))``.
|
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',
|
parser.add_argument('--int-limits',
|
||||||
help="Enable string conversion length limitation for int's",
|
help="Enable string conversion length limitation for int's",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
parser.add_argument('--shortest-str', help='Use shortest str/repr',
|
||||||
|
action='store_true')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -64,6 +66,8 @@ def main():
|
|||||||
if not args.no_pretty:
|
if not args.no_pretty:
|
||||||
lines.append('mp.pretty = True')
|
lines.append('mp.pretty = True')
|
||||||
lines.append('mp.pretty_dps = "repr"')
|
lines.append('mp.pretty_dps = "repr"')
|
||||||
|
if args.shortest_str:
|
||||||
|
lines.append('mp.shortest_str = True')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import IPython
|
import IPython
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class MPContext(BaseMPContext, StandardBaseContext):
|
|||||||
rounding=round_nearest, trap_complex=False):
|
rounding=round_nearest, trap_complex=False):
|
||||||
BaseMPContext.__init__(ctx)
|
BaseMPContext.__init__(ctx)
|
||||||
ctx.pretty = False
|
ctx.pretty = False
|
||||||
|
ctx.shortest_str = False
|
||||||
ctx.types = [ctx.mpf, ctx.mpc, ctx.constant]
|
ctx.types = [ctx.mpf, ctx.mpc, ctx.constant]
|
||||||
ctx.default()
|
ctx.default()
|
||||||
ctx._set_prec(prec)
|
ctx._set_prec(prec)
|
||||||
@@ -372,6 +373,7 @@ class MPContext(BaseMPContext, StandardBaseContext):
|
|||||||
(" mp.rounding = '%s'" % ctx.rounding).ljust(30) + f"[default: 'n']",
|
(" mp.rounding = '%s'" % ctx.rounding).ljust(30) + f"[default: 'n']",
|
||||||
(" mp.trap_complex = %s" % ctx.trap_complex).ljust(30) + "[default: False]",
|
(" mp.trap_complex = %s" % ctx.trap_complex).ljust(30) + "[default: False]",
|
||||||
(" mp.pretty_dps = '%s'" % ctx.pretty_dps).ljust(30) + "[default: 'str']",
|
(" 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)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|||||||
+19
-2
@@ -136,13 +136,22 @@ class _mpf(mpnumeric):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
ctx = self.context
|
ctx = self.context
|
||||||
if ctx.pretty:
|
if ctx.pretty:
|
||||||
|
if ctx.shortest_str:
|
||||||
|
return str(self)
|
||||||
ndigits = (ctx._repr_digits
|
ndigits = (ctx._repr_digits
|
||||||
if ctx._pretty_repr_dps else ctx._str_digits)
|
if ctx._pretty_repr_dps else ctx._str_digits)
|
||||||
return to_str(self._mpf_, ndigits)
|
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})"
|
return f"mpf({to_str(self._mpf_, ctx._repr_digits)!r})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ctx = self.context
|
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)
|
return to_str(self._mpf_, ctx._str_digits)
|
||||||
|
|
||||||
def __hash__(self): return mpf_hash(self._mpf_)
|
def __hash__(self): return mpf_hash(self._mpf_)
|
||||||
@@ -450,7 +459,8 @@ class _mpf(mpnumeric):
|
|||||||
_, _, (prec, rounding) = self._ctxdata
|
_, _, (prec, rounding) = self._ctxdata
|
||||||
ctx = self.context
|
ctx = self.context
|
||||||
return format_mpf(self._mpf_, format_spec, prec, rounding,
|
return format_mpf(self._mpf_, format_spec, prec, rounding,
|
||||||
ctx._pretty_repr_dps)
|
ctx._pretty_repr_dps,
|
||||||
|
ctx.shortest_str)
|
||||||
|
|
||||||
def sqrt(self):
|
def sqrt(self):
|
||||||
ctx = self.context
|
ctx = self.context
|
||||||
@@ -547,6 +557,8 @@ class _mpc(mpnumeric):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
ctx = self.context
|
ctx = self.context
|
||||||
if ctx.pretty:
|
if ctx.pretty:
|
||||||
|
if ctx.shortest_str:
|
||||||
|
return str(self)
|
||||||
ndigits = (ctx._repr_digits
|
ndigits = (ctx._repr_digits
|
||||||
if ctx._pretty_repr_dps else ctx._str_digits)
|
if ctx._pretty_repr_dps else ctx._str_digits)
|
||||||
return f"({mpc_to_str(self._mpc_, ndigits)})"
|
return f"({mpc_to_str(self._mpc_, ndigits)})"
|
||||||
@@ -556,6 +568,10 @@ class _mpc(mpnumeric):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ctx = self.context
|
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)})"
|
return f"({mpc_to_str(self._mpc_, ctx._str_digits)})"
|
||||||
|
|
||||||
def __complex__(self):
|
def __complex__(self):
|
||||||
@@ -761,7 +777,8 @@ class _mpc(mpnumeric):
|
|||||||
ctx = self.context
|
ctx = self.context
|
||||||
_, _, (prec, rounding) = self._ctxdata
|
_, _, (prec, rounding) = self._ctxdata
|
||||||
return format_mpc(self._mpc_, format_spec, prec, rounding,
|
return format_mpc(self._mpc_, format_spec, prec, rounding,
|
||||||
ctx._pretty_repr_dps)
|
ctx._pretty_repr_dps,
|
||||||
|
ctx.shortest_str)
|
||||||
|
|
||||||
|
|
||||||
complex_types = (complex, _mpc)
|
complex_types = (complex, _mpc)
|
||||||
|
|||||||
+160
-10
@@ -3,6 +3,7 @@ Low-level functions for arbitrary-precision floating-point arithmetic.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
import operator
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -1025,6 +1026,97 @@ def mpf_perturb(x, eps_sign, prec, rnd):
|
|||||||
# Radix conversion #
|
# 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):
|
def to_digits_exp(s, dps, base=10):
|
||||||
"""Helper function for representing the floating-point number s as
|
"""Helper function for representing the floating-point number s as
|
||||||
a string with dps digits. Returns (sign, string, exponent) where
|
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))
|
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
|
capitalize = False
|
||||||
if format_dict['type'] in list('AFGE'):
|
if format_dict['type'] in list('AFGE'):
|
||||||
capitalize = True
|
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)
|
num = mpf_mul(num, from_int(100), prec, rnd=round_nearest)
|
||||||
|
|
||||||
dps = format_dict['precision']
|
dps = format_dict['precision']
|
||||||
|
if dps >= 0 or fmt_type:
|
||||||
|
unique = False
|
||||||
|
|
||||||
int_part = ''
|
int_part = ''
|
||||||
exponent = ''
|
exponent = ''
|
||||||
sign = ''
|
sign = '-' if num[0] else ''
|
||||||
|
|
||||||
# Now the general case
|
# Now the general case
|
||||||
strip_last_zero = False
|
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)
|
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']:
|
if not format_dict['alternate']:
|
||||||
strip_zeros = True
|
strip_zeros = True
|
||||||
if fmt_type == 'g':
|
if fmt_type == 'g':
|
||||||
@@ -1625,6 +1719,51 @@ def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps):
|
|||||||
if capitalize:
|
if capitalize:
|
||||||
frac_part = frac_part.upper()
|
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':
|
elif fmt_type == 'e':
|
||||||
int_part, frac_part, exponent = format_scientific(num, dps, rnd=rnd)
|
int_part, frac_part, exponent = format_scientific(num, dps, rnd=rnd)
|
||||||
if strip_zeros:
|
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)
|
frac_part = fill_sep(frac_part, sep, frac_part[0], 1, sep_range)
|
||||||
digits = frac_part + exponent
|
digits = frac_part + exponent
|
||||||
|
|
||||||
sign = '-' if num[0] else ''
|
|
||||||
if sign != '-' and format_dict['sign'] != '-':
|
if sign != '-' and format_dict['sign'] != '-':
|
||||||
sign = format_dict['sign']
|
sign = format_dict['sign']
|
||||||
if fmt_type == 'f' and format_dict['no_neg_0']:
|
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
|
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)
|
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)
|
nchars = len(digits) + len(sign)
|
||||||
lpad, rpad = calc_padding(
|
lpad, rpad = calc_padding(
|
||||||
nchars, format_dict['width'], format_dict['align'])
|
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']
|
+ 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)
|
format_dict = read_format_spec(format_spec)
|
||||||
|
|
||||||
if format_dict['fill_char'] == '0':
|
if format_dict['fill_char'] == '0':
|
||||||
@@ -1733,12 +1872,23 @@ def format_mpc(num, format_spec, prec, rnd, _pretty_repr_dps):
|
|||||||
"format specifier.")
|
"format specifier.")
|
||||||
|
|
||||||
fmt_type = format_dict['type'].lower()
|
fmt_type = format_dict['type'].lower()
|
||||||
if not fmt_type:
|
if not fmt_type and format_dict['precision'] >= 0:
|
||||||
format_dict['type'] = 'g'
|
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']
|
fmt_sign = format_dict['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'
|
digits_im += 'j'
|
||||||
|
|
||||||
if not fmt_type:
|
if not fmt_type:
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ def test_bare_console_bare_division():
|
|||||||
assert c.expect_exact('0.5\r\n>>> ') == 0
|
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():
|
def test_bare_console_without_ipython():
|
||||||
try:
|
try:
|
||||||
import IPython
|
import IPython
|
||||||
|
|||||||
@@ -511,18 +511,29 @@ def test_mpf_floats_bulk(fmt, x):
|
|||||||
assert format(x, fmt) == format(mp.mpf(x), fmt)
|
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),
|
@given(fmt_str(types=list('gGfFeE') + [''], for_complex=True),
|
||||||
st.complex_numbers(allow_nan=True,
|
st.complex_numbers(allow_nan=True,
|
||||||
allow_infinity=True,
|
allow_infinity=True,
|
||||||
allow_subnormal=True))
|
allow_subnormal=True))
|
||||||
def test_mpc_complexes(fmt, z):
|
def test_mpc_complexes_bulk(fmt, z):
|
||||||
mp.pretty_dps = "repr"
|
mp.pretty_dps = "repr"
|
||||||
if ((not z.real and math.copysign(1, z.real) == -1)
|
if ((not z.real and math.copysign(1, z.real) == -1)
|
||||||
or (not z.imag and math.copysign(1, z.imag) == -1)):
|
or (not z.imag and math.copysign(1, z.imag) == -1)):
|
||||||
return # skip negative zero
|
return # skip negative zero
|
||||||
spec = read_format_spec(fmt)
|
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]):
|
if spec['precision'] < 0 and any(math.isfinite(_) for _ in [z.real, z.imag]):
|
||||||
# The mpmath could choose a different decimal
|
# The mpmath could choose a different decimal
|
||||||
# representative (wrt CPython) for same binary
|
# 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)
|
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():
|
def test_mpc_fmt():
|
||||||
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):=10f}')
|
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):=10f}')
|
||||||
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):010f}')
|
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):010f}')
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import math
|
||||||
|
import random
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import example, given
|
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([])
|
A1 = matrix([])
|
||||||
@@ -62,8 +65,95 @@ def test_matrix_str():
|
|||||||
@example(x=6.170920920537087e+17, rnd='f')
|
@example(x=6.170920920537087e+17, rnd='f')
|
||||||
def test_eval_repr_roundtrip(x, rnd):
|
def test_eval_repr_roundtrip(x, rnd):
|
||||||
mp.rounding = rnd
|
mp.rounding = rnd
|
||||||
|
mp.shortest_str = False
|
||||||
mp.pretty = True
|
mp.pretty = True
|
||||||
mp.pretty_dps = 'repr'
|
mp.pretty_dps = 'repr'
|
||||||
mx = mp.mpf(x)
|
mx = mp.mpf(x)
|
||||||
smx = repr(mx)
|
smx = repr(mx)
|
||||||
assert mx == mp.mpf(smx)
|
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