Support rounding modes in mpf.__format__ (#823)
Adds support for rounding modes 'U', 'D', 'Z' and 'N', following the gmpy2 library.
This commit is contained in:
+45
-12
@@ -1103,28 +1103,39 @@ def to_digits_exp(s, dps, base=10):
|
||||
exponent += len(digits) - fixdps - 1
|
||||
return sign, digits, exponent
|
||||
|
||||
def round_digits(digits, dps, base):
|
||||
def round_digits(digits, dps, base, rounding=round_nearest):
|
||||
'''
|
||||
Returns the rounded digits, and the number of places the decimal point was
|
||||
shifted.
|
||||
|
||||
Supports three kinds of rounding: up, down, or nearest.
|
||||
'''
|
||||
|
||||
assert len(digits) > dps
|
||||
assert rounding in (round_nearest, round_up, round_down)
|
||||
|
||||
exponent = 0
|
||||
|
||||
rnd_digs = stddigits[(base//2 + base % 2):base]
|
||||
if rounding == round_down:
|
||||
return digits[:dps], 0
|
||||
elif rounding == round_nearest:
|
||||
rnd_digs = stddigits[(base//2 + base % 2):base]
|
||||
else:
|
||||
rnd_digs = stddigits[:base]
|
||||
|
||||
tie_up = False
|
||||
|
||||
round_up = False
|
||||
# The first digit after dps is a 5.
|
||||
if digits[dps] == rnd_digs[0]:
|
||||
for i in range(dps+1, len(digits)):
|
||||
if digits[i] != '0':
|
||||
round_up = True
|
||||
tie_up = True
|
||||
break
|
||||
if digits[dps-1] in stddigits[1:base:2]:
|
||||
round_up = True
|
||||
tie_up = True
|
||||
|
||||
if not tie_up:
|
||||
|
||||
if not round_up:
|
||||
digits = digits[:dps] + stddigits[int(digits[dps], base) - 1]
|
||||
|
||||
# Rounding up kills some instances of "...99999"
|
||||
@@ -1375,9 +1386,16 @@ _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
|
||||
(?P<width>0|[1-9][0-9]*)?
|
||||
(?P<thousands_separators>[,_])?
|
||||
(?:\.(?P<precision>0|[1-9][0-9]*))?
|
||||
(?P<rounding>[UDZN])?
|
||||
(?P<type>[eEfFgG])
|
||||
""", re.DOTALL | re.VERBOSE).fullmatch
|
||||
|
||||
_GMPY_ROUND_CHAR_DICT = {
|
||||
'U': round_ceiling,
|
||||
'D': round_floor,
|
||||
'Z': round_down,
|
||||
'N': round_nearest
|
||||
}
|
||||
|
||||
def calc_padding(nchars, width, align):
|
||||
'''
|
||||
@@ -1415,6 +1433,7 @@ def read_format_spec(format_spec):
|
||||
'thousands_separators': '',
|
||||
'width': -1,
|
||||
'precision': 6,
|
||||
'rounding': round_nearest,
|
||||
'type': 'f'
|
||||
}
|
||||
|
||||
@@ -1429,8 +1448,12 @@ def read_format_spec(format_spec):
|
||||
or format_dict['thousands_separators']
|
||||
format_dict['width'] = int(match['width'] or format_dict['width'])
|
||||
format_dict['precision'] = int(match['precision'] or format_dict['precision'])
|
||||
rounding_char = match['rounding']
|
||||
format_dict['type'] = match['type'] or format_dict['type']
|
||||
|
||||
if rounding_char is not None:
|
||||
format_dict['rounding'] = _GMPY_ROUND_CHAR_DICT[rounding_char]
|
||||
|
||||
if match['zeropad'] and match['fill_char']:
|
||||
raise ValueError('Cannot specify both 0-padding and a fill '
|
||||
'character')
|
||||
@@ -1453,7 +1476,8 @@ def format_fixed(s,
|
||||
sep_range=3,
|
||||
base=10,
|
||||
alternate=False,
|
||||
no_neg_0=False):
|
||||
no_neg_0=False,
|
||||
rounding=round_nearest):
|
||||
'''
|
||||
Format a number into fixed point.
|
||||
Returns the sign character, and the string that represents the number in
|
||||
@@ -1490,7 +1514,7 @@ def format_fixed(s,
|
||||
if no_neg_0:
|
||||
sign = '' if sign_spec == '-' else sign_spec
|
||||
else:
|
||||
digits, exp_add = round_digits(digits, dps, base)
|
||||
digits, exp_add = round_digits(digits, dps, base, rounding)
|
||||
exponent += exp_add
|
||||
|
||||
# Here we prepend the corresponding 0s to the digits string, according
|
||||
@@ -1543,7 +1567,8 @@ def format_scientific(s,
|
||||
base=10,
|
||||
capitalize=False,
|
||||
alternate=False,
|
||||
no_neg_0=False):
|
||||
rounding=round_nearest):
|
||||
|
||||
|
||||
sep = 'E' if capitalize else 'e'
|
||||
|
||||
@@ -1555,7 +1580,7 @@ def format_scientific(s,
|
||||
if sign != '-' and sign_spec != '-':
|
||||
sign = sign_spec
|
||||
|
||||
digits, exp_add = round_digits(digits, dps, base)
|
||||
digits, exp_add = round_digits(digits, dps, base, rounding)
|
||||
exponent += exp_add
|
||||
|
||||
if strip_zeros:
|
||||
@@ -1593,6 +1618,13 @@ def format_mpf(num, format_spec):
|
||||
strip_last_zero = False
|
||||
strip_zeros = False
|
||||
|
||||
if format_dict['rounding'] == round_ceiling:
|
||||
rounding = round_down if num[0] else round_up
|
||||
elif format_dict['rounding'] == round_floor:
|
||||
rounding = round_up if num[0] else round_down
|
||||
else:
|
||||
rounding = format_dict['rounding']
|
||||
|
||||
if fmt_type == 'g':
|
||||
if not format_dict['alternate']:
|
||||
strip_last_zero = True
|
||||
@@ -1620,7 +1652,8 @@ def format_mpf(num, format_spec):
|
||||
sep_range=3,
|
||||
base=10,
|
||||
alternate=format_dict['alternate'],
|
||||
no_neg_0=format_dict['no_neg_0']
|
||||
no_neg_0=format_dict['no_neg_0'],
|
||||
rounding=rounding
|
||||
)
|
||||
else: # The format type is scientific
|
||||
sign, digits = format_scientific(
|
||||
@@ -1631,7 +1664,7 @@ def format_mpf(num, format_spec):
|
||||
base=10,
|
||||
capitalize=capitalize,
|
||||
alternate=format_dict['alternate'],
|
||||
no_neg_0=format_dict['no_neg_0']
|
||||
rounding=rounding
|
||||
)
|
||||
|
||||
nchars = len(digits) + len(sign)
|
||||
|
||||
@@ -559,6 +559,91 @@ def test_mpf_fmt():
|
||||
assert f"{mp.mpf('0.24'):=+020.2e}" == '+000000000002.40e-01'
|
||||
assert f"{mp.mpf('0.24'):=+020.2g}" == '+0000000000000000.24'
|
||||
|
||||
# Tests for different kinds of rounding
|
||||
num = mp.mpf('-1.23456789999901234567')
|
||||
assert f"{num:=.2Uf}" == "-1.23"
|
||||
assert f"{num:=.2Df}" == "-1.24"
|
||||
assert f"{num:=.2Zf}" == "-1.23"
|
||||
assert f"{num:=.2Nf}" == "-1.23"
|
||||
|
||||
assert f"{num:=.3Uf}" == "-1.234"
|
||||
assert f"{num:=.3Df}" == "-1.235"
|
||||
assert f"{num:=.3Zf}" == "-1.234"
|
||||
assert f"{num:=.3Nf}" == "-1.235"
|
||||
|
||||
assert f"{num:=.10Uf}" == "-1.2345678999"
|
||||
assert f"{num:=.10Df}" == "-1.2345679000"
|
||||
assert f"{num:=.10Zf}" == "-1.2345678999"
|
||||
assert f"{num:=.10Nf}" == "-1.2345679000"
|
||||
|
||||
num = mp.mpf('1.23456789999901234567')
|
||||
assert f"{num:=.2Uf}" == "1.24"
|
||||
assert f"{num:=.2Df}" == "1.23"
|
||||
assert f"{num:=.2Zf}" == "1.23"
|
||||
assert f"{num:=.2Nf}" == "1.23"
|
||||
|
||||
assert f"{num:=.3Uf}" == "1.235"
|
||||
assert f"{num:=.3Df}" == "1.234"
|
||||
assert f"{num:=.3Zf}" == "1.234"
|
||||
assert f"{num:=.3Nf}" == "1.235"
|
||||
|
||||
assert f"{num:=.10Uf}" == "1.2345679000"
|
||||
assert f"{num:=.10Df}" == "1.2345678999"
|
||||
assert f"{num:=.10Zf}" == "1.2345678999"
|
||||
assert f"{num:=.10Nf}" == "1.2345679000"
|
||||
|
||||
num = mp.mpf('-123.456789999901234567')
|
||||
assert f"{num:=.2Ue}" == "-1.23e+02"
|
||||
assert f"{num:=.2De}" == "-1.24e+02"
|
||||
assert f"{num:=.2Ze}" == "-1.23e+02"
|
||||
assert f"{num:=.2Ne}" == "-1.23e+02"
|
||||
|
||||
assert f"{num:=.3Ue}" == "-1.234e+02"
|
||||
assert f"{num:=.3De}" == "-1.235e+02"
|
||||
assert f"{num:=.3Ze}" == "-1.234e+02"
|
||||
assert f"{num:=.3Ne}" == "-1.235e+02"
|
||||
|
||||
assert f"{num:=.10Ue}" == "-1.2345678999e+02"
|
||||
assert f"{num:=.10De}" == "-1.2345679000e+02"
|
||||
assert f"{num:=.10Ze}" == "-1.2345678999e+02"
|
||||
assert f"{num:=.10Ne}" == "-1.2345679000e+02"
|
||||
|
||||
num = mp.mpf('123456.789999901234567')
|
||||
assert f"{num:=.2Ue}" == "1.24e+05"
|
||||
assert f"{num:=.2De}" == "1.23e+05"
|
||||
assert f"{num:=.2Ze}" == "1.23e+05"
|
||||
assert f"{num:=.2Ne}" == "1.23e+05"
|
||||
|
||||
assert f"{num:=.3Ue}" == "1.235e+05"
|
||||
assert f"{num:=.3De}" == "1.234e+05"
|
||||
assert f"{num:=.3Ze}" == "1.234e+05"
|
||||
assert f"{num:=.3Ne}" == "1.235e+05"
|
||||
|
||||
assert f"{num:=.10Ue}" == "1.2345679000e+05"
|
||||
assert f"{num:=.10De}" == "1.2345678999e+05"
|
||||
assert f"{num:=.10Ze}" == "1.2345678999e+05"
|
||||
assert f"{num:=.10Ne}" == "1.2345679000e+05"
|
||||
|
||||
assert f"{mp.mpf('123.456'):.2Ug}" == "1.3e+02"
|
||||
assert f"{mp.mpf('123.456'):.2Dg}" == "1.2e+02"
|
||||
assert f"{mp.mpf('123.456'):.2Zg}" == "1.2e+02"
|
||||
assert f"{mp.mpf('123.456'):.2Ng}" == "1.2e+02"
|
||||
|
||||
assert f"{mp.mpf('-123.456'):.2Ug}" == "-1.2e+02"
|
||||
assert f"{mp.mpf('-123.456'):.2Dg}" == "-1.3e+02"
|
||||
assert f"{mp.mpf('-123.456'):.2Zg}" == "-1.2e+02"
|
||||
assert f"{mp.mpf('-123.456'):.2Ng}" == "-1.2e+02"
|
||||
|
||||
assert f"{mp.mpf('123.456'):.5Ug}" == "123.46"
|
||||
assert f"{mp.mpf('123.456'):.5Dg}" == "123.45"
|
||||
assert f"{mp.mpf('123.456'):.5Zg}" == "123.45"
|
||||
assert f"{mp.mpf('123.456'):.5Ng}" == "123.46"
|
||||
|
||||
assert f"{mp.mpf('-123.456'):.5Ug}" == "-123.45"
|
||||
assert f"{mp.mpf('-123.456'):.5Dg}" == "-123.46"
|
||||
assert f"{mp.mpf('-123.456'):.5Zg}" == "-123.45"
|
||||
assert f"{mp.mpf('-123.456'):.5Ng}" == "-123.46"
|
||||
|
||||
|
||||
def test_errors():
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
Reference in New Issue
Block a user