Repository: mpmath/mpmath (python)

Issue:
Invalid handling of zeropad option in mpf formatting
As [docs says](https://docs.python.org/3.14/library/string.html#format-specification-mini-language): "When no explicit alignment is given, preceding the width field by a zero ('0') character enables sign-aware zero-padding for numeric types, excluding [complex](https://docs.python.org/3.14/library/functions.html#complex). This is equivalent to a fill character of '0' with an alignment type of '='."

Example:
```pycon
>>> format(1.1, 'z=13,f')
'zzzzz1.100000'
>>> format(1.1, '=13,f')
'     1.100000'
>>> format(1.1, '0=13,f')
'00,001.100000'
>>> format(1.1, '013,f')
'00,001.100000'
>>> format(mpf(1.1), '0=13,f')
'000001.100000'
>>> format(mpf(1.1), '013,f')
'000001.100000'
```

So, thousands separation is missing.

Pull request description:
Fix handling of zeropad flag
Closes #917

Patch:
diff --git a/mpmath/libmp/libmpf.py b/mpmath/libmp/libmpf.py
index 459e571..1d42b70 100644
--- a/mpmath/libmp/libmpf.py
+++ b/mpmath/libmp/libmpf.py
@@ -1578,6 +1578,10 @@ def format_fixed(s,
     return sign, digits
 
 
+_MAP_FMT_EXP = {'E': 'E', 'e': 'e', 'G': 'E', 'g': 'e',
+                'A': 'P', 'a': 'p', 'B': 'p', 'b': 'p', '': 'e'}
+
+
 def format_scientific(s,
                       precision=6,
                       strip_zeros=False,
@@ -1587,7 +1591,6 @@ def format_scientific(s,
                       alternate=False,
                       rounding=round_nearest):
 
-
     sep = 'E' if capitalize else 'e'
 
     # First, get the exponent to know how many digits we will need
@@ -1792,18 +1795,44 @@ def format_digits(num, format_dict, prec):
             if digits[-1] == '.':
                 digits += '0'
         if percent:
-            digits = digits + '%'
+            digits += '%'
 
     return sign, digits
 
 
 def format_mpf(num, format_spec, prec):
     format_dict = read_format_spec(format_spec)
-
     sign, digits = format_digits(num, format_dict, prec)
-
     nchars = len(digits) + len(sign)
+    sep_range = 3
+    width = format_dict['width']
+    sep = format_dict['thousands_separators']
+
+    if (num not in _MAP_SPEC_STR and format_dict['fill_char'] == '0'
+            and format_dict['align'] == '=' and sep and width > nchars):
+        e = _MAP_FMT_EXP.get(format_dict['type'])
+        if '.' in digits:
+            i, digits = digits.split('.')
+            digits = '.' + digits
+        elif e and e in digits:
+            i, digits = digits.split(e)
+            digits = e + digits
+        elif '%' in digits:
+            i = digits[:-1]
+            digits = '%'
+        else:
+            i = digits
+            digits = ''
+        i = i.replace(sep, '')
+        min_leading = format_dict['width'] - len(digits) - len(sign)
+        i = i.zfill(sep_range * min_leading // (sep_range + 1) + 1
+                    if sep else min_leading)
+        first_pos = 1 + (len(i) - 1) % sep_range
+        i = i[:first_pos] + "".join(sep + i[pos : pos + sep_range]
+                                    for pos in range(first_pos, len(i), sep_range))
+        digits = i + digits
 
+    nchars = len(digits) + len(sign)
     lpad, rpad = calc_padding(
             nchars, format_dict['width'], format_dict['align'])
 


Test patch:
diff --git a/mpmath/tests/test_format.py b/mpmath/tests/test_format.py
index 3aeb69d..fd10947 100644
--- a/mpmath/tests/test_format.py
+++ b/mpmath/tests/test_format.py
@@ -473,6 +473,9 @@ def test_mpf_fmt_cpython():
 @example(fmt='.0g', x=9.995074823339339e-05)  # issue 880
 @example(fmt='.016f', x=0.1)  # issue 915
 @example(fmt='0030f', x=0.3)
+@example(fmt='0=13,f', x=1.1)  # issue 917
+@example(fmt='013,f', x=1.1)
+@example(fmt='013,.0%', x=1.1)
 def test_mpf_floats_bulk(fmt, x):
     '''
     These are additional random tests that check that mp.mpf and fp.mpf yield
@@ -482,9 +485,6 @@ 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['width'] > 0 and spec['fill_char'] == '0'
-            and spec['thousands_separators']):
-        return  # issue 917
     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