Repository: mpmath/mpmath (python)

Issue:
format(mpf(3.14), '') raises ValueError
Reproducer:
```pycon
>>> from mpmath import mpf
>>> format(mpf(3.14), '')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/sk/src/mpmath/mpmath/ctx_mp_python.py", line 359, in __format__
    return format_mpf(s._mpf_, format_spec)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/sk/src/mpmath/mpmath/libmp/libmpf.py", line 1601, in format_mpf
    format_dict = read_format_spec(format_spec)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/sk/src/mpmath/mpmath/libmp/libmpf.py", line 1465, in read_format_spec
    raise ValueError("Invalid format specifier '{}'".format(format_spec))
ValueError: Invalid format specifier ''
```

Instead, this should use default settings, like:
```pycon
>>> format(3.14, '')
'3.14'
```

Pull request description:
Misc fixes
* use ellipsis in fp.spherharm doctest
* drop empty mpmath/tests/\_\_init\_\_.py
* amend aa6690b: add 'Y' rounding mode
* make format type optional and default to 'g', closes #833

Patch:
diff --git a/mpmath/function_docs.py b/mpmath/function_docs.py
index 5963b33..3f3cce2 100644
--- a/mpmath/function_docs.py
+++ b/mpmath/function_docs.py
@@ -9850,7 +9850,7 @@
     >>> Y2 = lambda t,p: fp.conj(fp.spherharm(l2,m2,t,p))
     >>> l1 = l2 = 3; m1 = m2 = 2
     >>> fp.chop(fp.quad(lambda t,p: Y1(t,p)*Y2(t,p)*dS(t,p), *sphere))
-    1.0000000000000007
+    1.000000000000000...
     >>> m2 = 1    # m1 != m2
     >>> print(fp.chop(fp.quad(lambda t,p: Y1(t,p)*Y2(t,p)*dS(t,p), *sphere)))
     0.0
diff --git a/mpmath/libmp/libmpf.py b/mpmath/libmp/libmpf.py
index ddf81f4..bb6905e 100644
--- a/mpmath/libmp/libmpf.py
+++ b/mpmath/libmp/libmpf.py
@@ -1386,13 +1386,14 @@ def from_str(x, prec=0, rnd=round_fast, base=0):
     (?P<width>0|[1-9][0-9]*)?
     (?P<thousands_separators>[,_])?
     (?:\.(?P<precision>0|[1-9][0-9]*))?
-    (?P<rounding>[UDZN])?
-    (?P<type>[eEfFgG])
+    (?P<rounding>[UDYZN])?
+    (?P<type>[eEfFgG])?
 """, re.DOTALL | re.VERBOSE).fullmatch
 
 _GMPY_ROUND_CHAR_DICT = {
         'U': round_ceiling,
         'D': round_floor,
+        'Y': round_up,
         'Z': round_down,
         'N': round_nearest
         }
@@ -1434,7 +1435,7 @@ def read_format_spec(format_spec):
         'width': -1,
         'precision': 6,
         'rounding': round_nearest,
-        'type': 'f'
+        'type': 'g'
         }
 
     if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec):


Test patch:
diff --git a/mpmath/tests/__init__.py b/mpmath/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/mpmath/tests/test_format.py b/mpmath/tests/test_format.py
index 4379296..b670508 100644
--- a/mpmath/tests/test_format.py
+++ b/mpmath/tests/test_format.py
@@ -539,6 +539,8 @@ def test_mpf_fmt():
         assert f"{mp.mpf('1e-50'):.50g}" == '1e-50'
         assert f"{mp.mpf('1e-50'):.50G}" == '1E-50'
 
+        assert f"{mp.mpf('1e-50'):}" == '1e-50'
+
         # thousands separator
         assert f"{mp.mpf('1e9'):,.0f}" == '1,000,000,000'
         assert f"{mp.mpf('123456789.0123456'):,.4f}" == '123,456,789.0123'
@@ -565,84 +567,104 @@ def test_mpf_fmt():
         assert f"{num:=.2Df}" == "-1.24"
         assert f"{num:=.2Zf}" == "-1.23"
         assert f"{num:=.2Nf}" == "-1.23"
+        assert f"{num:=.2Yf}" == "-1.24"
 
         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:=.3Yf}" == "-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"
+        assert f"{num:=.10Yf}" == "-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:=.2Yf}" == "1.24"
 
         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:=.3Yf}" == "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"
+        assert f"{num:=.10Yf}" == "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:=.2Ye}" == "-1.24e+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:=.3Ye}" == "-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"
+        assert f"{num:=.10Ye}" == "-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:=.2Ye}" == "1.24e+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:=.3Ye}" == "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"{num:=.10Ye}" == "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'):.2Yg}" == "1.3e+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'):.2Yg}" == "-1.3e+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'):.5Yg}" == "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"
+        assert f"{mp.mpf('-123.456'):.5Yg}" == "-123.46"
+
+        # Special cases were tying is relevant (cases involve exact floats)
+        assert f"{mp.mpf('0.25'):.1Nf}" == "0.2"
+        assert f"{mp.mpf('0.75'):.1Nf}" == "0.8"
 
 
 def test_errors():