mpf.__round__() returns mpf

Probably this will look non-intuitive, but underlying floating-point
arithmetic is binary, not decimal.  Thus, round(x, n) sometimes will pick
up a decimal representation with more than n digits.  This is something
common for CPython < 2.7 and < 3.1:

    Python 2.6.6 (r266:84292, Aug 12 2014, 07:57:07)
    [GCC 4.4.5] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> round(0.42754287856598971, 2)
    0.42999999999999999

And with this patch as well:

    Python 3.12.5+ (heads/3.12:0181aa2e3e, Aug 29 2024, 14:55:08) [GCC 12.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from mpmath import *
    >>> round(mp.mpf(0.42754287856598971), 2)
    mpf('0.42999999999999999')
    >>> mp.pretty = True
    >>> round(mp.mpf(0.42754287856598971), 2)
    0.43

See also python/cpython#45921

Closes #455
This commit is contained in:
Sergey B Kirpichev
2024-08-01 10:30:29 +03:00
parent f36220e6c4
commit a545ecbd9e
2 changed files with 40 additions and 3 deletions
+7 -2
View File
@@ -412,8 +412,13 @@ class _mpf(mpnumeric):
def to_fixed(self, prec):
return to_fixed(self._mpf_, prec)
def __round__(self, *args):
return round(float(self), *args)
def __round__(self, ndigits=0):
ctx = self.context
if ctx.isfinite(self):
frac = MPQ(*self.as_integer_ratio())
res = round(frac, ndigits)
return ctx.convert(res)
return self
class _constant(_mpf):
+33 -1
View File
@@ -1,13 +1,16 @@
import decimal
import math
import operator
import random
import pytest
from hypothesis import example, given, settings
from hypothesis import strategies as st
import mpmath
from mpmath import (ceil, fadd, fdiv, floor, fmul, fneg, fp, frac, fsub, inf,
isinf, isint, isnan, isnormal, iv, monitor, mp, mpc, mpf,
mpi, nan, ninf, nint, nint_distance, pi, workprec)
mpi, nan, ninf, nint, nint_distance, nstr, pi, workprec)
from mpmath.libmp import (MPQ, finf, fnan, fninf, fnone, fone, from_float,
from_int, from_pickable, from_str, mpf_add, mpf_mul,
mpf_sub, round_down, round_nearest, round_up, to_int,
@@ -607,3 +610,32 @@ def test_rand_precision():
def test_issue_260():
assert mpc(str(mpc(1j))) == mpc(1j)
@settings(max_examples=10000)
@given(st.floats(allow_nan=True,
allow_infinity=True,
allow_subnormal=True),
st.integers(min_value=0, max_value=15))
@example(0.5, 0)
@example(-0.5, 0)
@example(1.5, 0)
@example(-1.5, 0)
@example(2.675, 2)
@example(math.inf, 3)
@example(-math.inf, 1)
def test_round_bulk(x, n):
mp.prec = fp.prec
m = mpf(x)
mr = round(m, n)
xr = round(x, n)
if isnan(x):
assert isnan(mr)
assert isnan(xr)
else:
assert float(mr) == xr
# mp context doesn't support negative zero
if not xr and math.copysign(1., xr) == -1.:
return
assert nstr(mr, n=14, base=16, strip_zeros=False,
show_zero_exponent=True, binary_exp=True) == xr.hex()