Support rounding property for the mp context

This commit is contained in:
Sergey B Kirpichev
2025-06-01 13:19:59 +03:00
parent 1c4bf6db68
commit 4582b13795
7 changed files with 48 additions and 11 deletions
+2 -1
View File
@@ -44,7 +44,7 @@ Features:
* Support 'b' (binary) format type for mpf/mpc, see #867 (Sergey B Kirpichev)
* Implement mpf.__floordiv__() and mpf.__divmod__(), see #873 (Sergey B
Kirpichev)
* Add parameters for MPContext constructor, see #876 (Sergey B Kirpichev)
* Add parameters for MPContext constructor, see #876 and #963 (Sergey B Kirpichev)
* Add MPFR-compatible aliases for rounding modes, see #892 (Sergey B
Kirpichev)
* Support negative indexes in matrix, see #897 (Riccardo Orsi)
@@ -59,6 +59,7 @@ Features:
* Use PyREPL, as fallback (no IPython), see #941 (Sergey B Kirpichev)
* Add exp2() and log2(), see #948 (Sergey B Kirpichev)
* Add isspecial() method for contexts, see #949 (Sergey B Kirpichev)
* Support rounding property for the mp context, see #963 (Sergey B Kirpichev)
Compatibility:
+3 -2
View File
@@ -24,7 +24,8 @@ def pytest_configure(config):
@pytest.fixture(autouse=True)
def reset_mp_globals():
from mpmath import mp, iv
mp.dps = 15
mp.prec = sys.float_info.mant_dig
mp.pretty = False
iv.dps = 15
mp.rounding = 'n'
iv.prec = mp.prec
iv.pretty = False
+12
View File
@@ -80,6 +80,7 @@ Mpmath uses a global working precision; it does not keep track of the precision
Mpmath settings:
mp.prec = 53 [default: 53]
mp.dps = 15 [default: 15]
mp.rounding = 'n' [default: 'n']
mp.trap_complex = 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).
@@ -120,6 +121,17 @@ Or why not 1 googolplex:
The (binary) exponent is stored exactly and is independent of the precision.
The ``rounding`` property control default rounding mode for the context:
>>> mp.rounding # round to nearest
'n'
>>> sin(1)
mpf('0.8414709848078965')
>>> mp.rounding = 'u' # round up
>>> sin(1)
mpf('0.84147098480789662')
>>> mp.rounding = 'n'
Temporarily changing the precision
..................................
+5 -2
View File
@@ -18,7 +18,7 @@ from .libmp import (MPQ, MPZ_ONE, ComplexResult, dps_to_prec, finf, fnan,
mpf_degree, mpf_div, mpf_e, mpf_euler, mpf_glaisher,
mpf_khinchin, mpf_ln2, mpf_ln10, mpf_mertens, mpf_mul,
mpf_neg, mpf_phi, mpf_pi, mpf_rand, mpf_sub, mpf_twinprime,
repr_dps, to_man_exp, to_str)
repr_dps, to_man_exp, to_str, round_nearest)
get_complex = re.compile(r"""
@@ -44,12 +44,14 @@ class MPContext(BaseMPContext, StandardBaseContext):
Context for multiprecision arithmetic with a global precision.
"""
def __init__(ctx, prec=sys.float_info.mant_dig, trap_complex=False):
def __init__(ctx, prec=sys.float_info.mant_dig,
rounding=round_nearest, trap_complex=False):
BaseMPContext.__init__(ctx)
ctx.pretty = False
ctx.types = [ctx.mpf, ctx.mpc, ctx.constant]
ctx.default()
ctx._set_prec(prec)
ctx._set_rounding(rounding)
ctx.trap_complex = trap_complex
StandardBaseContext.__init__(ctx)
@@ -359,6 +361,7 @@ class MPContext(BaseMPContext, StandardBaseContext):
lines = ["Mpmath settings:",
(" mp.prec = %s" % ctx.prec).ljust(30) + f"[default: {sys.float_info.mant_dig}]",
(" mp.dps = %s" % ctx.dps).ljust(30) + f"[default: {sys.float_info.dig}]",
(" mp.rounding = '%s'" % ctx.rounding).ljust(30) + f"[default: 'n']",
(" mp.trap_complex = %s" % ctx.trap_complex).ljust(30) + "[default: False]",
]
return "\n".join(lines)
+13 -3
View File
@@ -130,13 +130,14 @@ class _mpf(mpnumeric):
def __reduce__(self): return _make_mpf, (self._mpf_,)
def __repr__(s):
rounding = s.context._rounding
if s.context.pretty:
ndigits = (s.context._repr_digits
if s.context._pretty_repr_dps else s.context._str_digits)
return to_str(s._mpf_, ndigits)
return "mpf('%s')" % to_str(s._mpf_, s.context._repr_digits)
return to_str(s._mpf_, ndigits, rounding=rounding)
return "mpf('%s')" % to_str(s._mpf_, s.context._repr_digits, rounding=rounding)
def __str__(s): return to_str(s._mpf_, s.context._str_digits)
def __str__(s): return to_str(s._mpf_, s.context._str_digits, rounding=s.context._rounding)
def __hash__(s): return mpf_hash(s._mpf_)
def __int__(s): return int(to_int(s._mpf_))
def __float__(s): return to_float(s._mpf_, rnd=s.context._prec_rounding[1])
@@ -730,6 +731,7 @@ class PythonMPContext:
def default(ctx):
ctx._prec = ctx._prec_rounding[0] = sys.float_info.mant_dig
ctx._rounding = ctx._prec_rounding[1]
ctx._dps = sys.float_info.dig
ctx.trap_complex = False
@@ -741,8 +743,16 @@ class PythonMPContext:
ctx._prec = ctx._prec_rounding[0] = dps_to_prec(n)
ctx._dps = max(1, int(n))
def _set_rounding(ctx, r):
try:
ctx._prec_rounding[1] = ctx._parse_prec({'rounding': r})[1]
ctx._rounding = ctx._prec_rounding[1]
except KeyError:
raise ValueError('invalid rounding mode')
prec = property(lambda ctx: ctx._prec, _set_prec)
dps = property(lambda ctx: ctx._dps, _set_dps)
rounding = property(lambda ctx: ctx._rounding, _set_rounding)
def _set_pretty_dps(ctx, v):
ctx._pretty_repr_dps = True if v == 'repr' else False
+11 -1
View File
@@ -288,11 +288,11 @@ def test_arithmetic_functions():
assert fneg(z1) == -(+z1)
def test_exact_integer_arithmetic():
# XXX: re-fix this so that all operations are tested with all rounding modes
random.seed(0)
for prec in [6, 10, 25, 40, 100, 250, 725]:
for rounding in ['d', 'u', 'f', 'c', 'n']:
mp.dps = prec
mp.rounding = rounding
M = 10**(prec-2)
M2 = 10**(prec//2-2)
for i in range(10):
@@ -665,3 +665,13 @@ def test_round_bulk(x, n):
return
assert nstr(mr, n=14, base=16, strip_zeros=False,
show_zero_exponent=True, binary_exp=True) == xr.hex()
def test_rounding_prop():
assert mp.rounding == 'n'
assert mp.sin(1) == mpf('0x1.aed548f090ceep-1')
mp.rounding = 'u'
assert mp.rounding == 'u'
assert mp.sin(1) == mpf('0x1.aed548f090cefp-1')
with pytest.raises(ValueError):
mp.rounding = 'x'
+2 -2
View File
@@ -653,9 +653,8 @@ def test_root():
r = nthroot(a, -n)
r1 = pow(a, -mpf(1)/n)
assert r.ae(r1)
# XXX: this is broken right now
# tests for nthroot rounding
for rnd in ['nearest', 'up', 'down']:
for rnd in ['n', 'u', 'd']:
mp.rounding = rnd
for n in [-5, -3, 3, 5]:
prec = 50
@@ -667,6 +666,7 @@ def test_root():
mp.prec = prec
r = nthroot(b, n)
assert r == a
mp.rounding = 'n'
mp.dps = 30
for n in range(3, 21):
a = (random.random() + j*random.random())