Support thousands separators for formatting of fractional part

Closes #918
This commit is contained in:
Sergey B Kirpichev
2025-03-04 18:15:15 +03:00
parent 8f68766f75
commit 950d654a22
2 changed files with 21 additions and 3 deletions
+13 -3
View File
@@ -1400,7 +1400,7 @@ _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
(?P<zeropad>0(?=0*[1-9]))?
(?P<width>[0-9]+)?
(?P<thousands_separators>[,_])?
(?:\.(?P<precision>[0-9]+))?
(?:\.(?P<precision>[0-9]+)(?P<frac_separators>[,_])?)?
(?P<rounding>[UDYZN])?
(?P<type>[aAbeEfFgG%])?
""", re.DOTALL | re.VERBOSE).fullmatch
@@ -1447,6 +1447,7 @@ def read_format_spec(format_spec):
'no_neg_0': False,
'alternate': False,
'thousands_separators': '',
'frac_separators': '',
'width': -1,
'precision': -1,
'rounding': round_nearest,
@@ -1464,6 +1465,8 @@ 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'])
format_dict['frac_separators'] = match['frac_separators'] \
or format_dict['frac_separators']
rounding_char = match['rounding']
format_dict['type'] = match['type'] or format_dict['type']
@@ -1681,13 +1684,21 @@ def format_digits(num, format_dict, prec):
or (precision and not strip_last_zero)):
frac_part = '.' + frac_part
sep_range = 3
frac_sep = format_dict['frac_separators']
if frac_sep and frac_part:
frac_part = frac_part[:sep_range + 1] + "".join(frac_sep + frac_part[pos:pos + sep_range]
for pos in range(sep_range + 1,
len(frac_part),
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']:
if int_part == "0" and all(_ in ['0', '.'] for _ in digits):
if int_part == "0" and all(_ in ['0', '.', '_', ',']
for _ in digits):
if format_dict['sign'] == '-':
sign = ''
else:
@@ -1697,7 +1708,6 @@ def format_digits(num, format_dict, prec):
digits += '%'
sep = format_dict['thousands_separators']
sep_range = 3
width = format_dict['width']
if (int_part and fmt_type in ['%', 'f', 'e', 'g', '']
and format_dict['fill_char'] == '0' and format_dict['align'] == '='
+8
View File
@@ -68,6 +68,9 @@ def fmt_str(draw, types='fFeE', for_complex=False):
+ ['0' + str(_) for _ in range(40)]))
if prec:
res += '.' + prec
# if sys.version_info >= (3, 14):
# gchar = draw(st.sampled_from([''] + list(',_')))
# res += gchar
# Type
res += draw(st.sampled_from(types))
@@ -178,6 +181,9 @@ def test_mpf_fmt_cpython():
assert f'{mp.mpf(0.0000005001):f}' == '0.000001'
assert f'{mp.mpf(0.0000004999):f}' == '0.000000'
# grouping in fractional part
assert f'{mp.mpf(0.0000004999):.9_f}' == '0.000_000_500'
# 'e' code formatting with explicit precision (>= 0). Output should
# always have exactly the number of places after the point that were
# requested.
@@ -485,6 +491,8 @@ def test_mpf_floats_bulk(fmt, x):
if not x and math.copysign(1, x) == -1:
return # skip negative zero
spec = read_format_spec(fmt)
if spec['frac_separators'] and spec['fill_char'] == '0':
return # XXX: python/cpython#130860
if not spec['type'] and spec['precision'] < 0 and math.isfinite(x):
# The mpmath could choose a different decimal
# representative (wrt CPython) for same binary