Repository: sympy/sympy

Issue:
Request for setting in the StrPrinter to control the decimal places of floats
The `StrPrinter` prints floats using mpmath's `mlib_to_str` and the decimal places `dps` is automatically selected based on the sympy float properties. We could allow the `dps` to be user controlled with something like:

```python
import sympy as sm
from mpmath.libmp import prec_to_dps, to_str as mlib_to_str


class MyPrinter(sm.StrPrinter):

    _default_settings = {
        "order": None,
        "full_prec": "auto",
        "sympy_integers": False,
        "abbrev": False,
        "perm_cyclic": True,
        "min": None,
        "max": None,
        "dps": None,
    }

    def _print_Float(self, expr):
        if self._settings['dps']:
            dps = self._settings['dps']
        else:
            dps = 0 if expr._prec < 5 else prec_to_dps(expr._prec)
        if self._settings["full_prec"] is True:
            strip = False
        elif self._settings["full_prec"] is False:
            strip = True
        elif self._settings["full_prec"] == "auto":
            strip = self._print_level > 1
        low = self._settings["min"] if "min" in self._settings else None
        high = self._settings["max"] if "max" in self._settings else None
        rv = mlib_to_str(expr._mpf_, dps, strip_zeros=strip, min_fixed=low, max_fixed=high)
        if rv.startswith('-.0'):
            rv = '-0.' + rv[3:]
        elif rv.startswith('.0'):
            rv = '0.' + rv[2:]
        if rv.startswith('+'):
            # e.g., +inf -> inf
            rv = rv[1:]
        return rv

f = sm.Function('f')
print(MyPrinter(settings={'dps': 3}).doprint(f(1.329294)))
```

```
f(1.33)
```

This would allow floats to print more compactly.

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* printing
  * In str.py , Added a {"dps" : None} in _default_settings in StrPrinter . ([#27043](https://github.com/sympy/sympy/pull/27043) by [@VectorNd](https://github.com/VectorNd))

  * In str.py , Added an if condition in _print_Float method  . ([#27043](https://github.com/sympy/sympy/pull/27043) by [@VectorNd](https://github.com/VectorNd))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.14.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    #### References to other Issues or PRs
    Fixes #27033

    #### Brief description of what is fixed or changed
    Added a {"dps" : None} pair in _default_settings in StrPrinter and accordingly made some change in the _print_Float method . 

    #### Release Notes

    <!-- BEGIN RELEASE NOTES -->
    * printing 
      * In str.py , Added a {"dps" : None} in _default_settings in StrPrinter . 
      * In str.py , Added an if condition in _print_Float method  . 
    <!-- END RELEASE NOTES -->


</details><p>

oscarbenjamin: You don't need to close the pull request
oscarbenjamin: The code quality test has failed because of trailing whitespace:
https://github.com/sympy/sympy/actions/runs/10746113244/job/29806427330?pr=27043

The error message tells you which file and which line:

> FAILED sympy/testing/tests/test_code_quality.py::test_files - AssertionError: File contains trailing whitespace: /home/runner/work/sympy/sympy/sympy/printing/str.py, line 741.

You can delete the spaces at the end of the line or run the `bin/strip_whitespace` script. Once that is done you can push another commit to your branch which will update the pull request.

You do *not* need to close the pull request and open a new one.
oscarbenjamin: The new failures are not caused by your changes here. They are because of an update to ruff.

I have a fix in gh-27040 but I am waiting for someone to merge it. Once that happens the failures seen here in this pull request will be resolved as well.
VectorNd: @oscarbenjamin  can you please tell me why is it failing now i have not made any change in any other file than str.py
moorepants: There are few things this will need:

1. unit tests
2. docstring explanation of the new setting'
3. figuring out what happens when this new setting is used alongside any of the other settings and documenting/testing for that
github-actions[bot]: Benchmark results from GitHub Actions

Lower numbers are good, higher numbers are bad. A ratio less than 1
means a speed up and greater than 1 means a slowdown. Green lines
beginning with `+` are slowdowns (the PR is slower then master or
master is slower than the previous release). Red lines beginning
with `-` are speedups.

Significantly changed benchmark results (PR vs master)
```diff

```
Significantly changed benchmark results (master vs previous release)
```diff

```
Full benchmark results can be found as artifacts in GitHub Actions
(click on checks at the top of the PR).
VectorNd: @moorepants  sir can you please merge it  ?

moorepants: > @moorepants sir can you please merge it ?

No, but I am willing to review it when I have time.
VectorNd: okay thankyou 

VectorNd: @asmeurer i have fixed the mistakes . can you please review it 
smichr: It looks ok to me. I'll wait a while to see if anyone else has suggestions.
smichr: If locally you could add a space between the comma and None and then squash all the commits into one and then force push here, that would be helpful. Locally, you have 9 commits -- double check -- so I would proceed like this in git:
```
git reset head~9
# make the edit
git commit -am 'add dps to StrPrinter'
git push -f VectorNd
```

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 2b24c435bc..0000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-version: 2
-updates:
-  # Maintain dependencies for GitHub Actions
-  - package-ecosystem: "github-actions"
-    directory: "/"
-    schedule:
-      interval: "monthly"
-    groups:
-      actions:
-        patterns:
-          - "*"
-    labels:
-      - "Github Actions"
diff --git a/.mailmap b/.mailmap
index d36148d403..28c80cdf45 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1221,6 +1221,7 @@ Riley Britten <nrb1324@hotmail.com>
 Rimi <rimibis@umich.edu>
 Rishabh Daal <rishabhdaal@gmail.com>
 Rishabh Dixit <rishabhdixit11@gmail.com>
+Rishabh Kamboj <111004091+VectorNd@users.noreply.github.com> VectorNd <rishabh.kamboj.eee21@itbhu.ac.in>
 Rishabh Madan <rishabhmadan96@gmail.com>
 Rishat Iskhakov <iskhakov@frtk.ru>
 Rishav Chakraborty <annonymousxyz@outlook.com>
diff --git a/release/compare_tar_against_git.py b/release/compare_tar_against_git.py
index b34efc0542..e0ae8340c2 100755
--- a/release/compare_tar_against_git.py
+++ b/release/compare_tar_against_git.py
@@ -34,7 +34,6 @@ def main(tarname, gitroot):
     '.mailmap',
     # CI
     '.github/PULL_REQUEST_TEMPLATE.md',
-    '.github/dependabot.yml',
     '.github/workflows/runtests.yml',
     '.github/workflows/ci-sage.yml',
     '.github/workflows/comment-on-pr.yml',
diff --git a/sympy/core/mul.py b/sympy/core/mul.py
index 1bd080d0c3..dfd3f40cfb 100644
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -377,10 +377,6 @@ def flatten(cls, seq):
                 coeff = S.ComplexInfinity
                 continue
 
-            elif not coeff and isinstance(o, Add) and o.has(S.ComplexInfinity):
-                # 0 * (x + zoo) = NaN
-                return [S.NaN], [], None
-
             elif o is S.ImaginaryUnit:
                 neg1e += S.Half
                 continue
diff --git a/sympy/integrals/integrals.py b/sympy/integrals/integrals.py
index b882f43c3b..3017c9ba31 100644
--- a/sympy/integrals/integrals.py
+++ b/sympy/integrals/integrals.py
@@ -1568,6 +1568,7 @@ def integrate(*args, meijerg=None, conds='piecewise', risch=None, heurisch=None,
         'heurisch': heurisch,
         'manual': manual
         }
+
     integral = Integral(*args, **kwargs)
 
     if isinstance(integral, Integral):
@@ -1577,7 +1578,6 @@ def integrate(*args, meijerg=None, conds='piecewise', risch=None, heurisch=None,
             for a in integral.args]
         return integral.func(*new_args)
 
-
 def line_integrate(field, curve, vars):
     """line_integrate(field, Curve, variables)
 
diff --git a/sympy/printing/str.py b/sympy/printing/str.py
index bb4e5cb3bc..d6d3f1b4ce 100644
--- a/sympy/printing/str.py
+++ b/sympy/printing/str.py
@@ -27,6 +27,7 @@ class StrPrinter(Printer):
         "perm_cyclic": True,
         "min": None,
         "max": None,
+        "dps" : None
     }
 
     _relationals: dict[str, str] = {}
@@ -736,10 +737,9 @@ def _print_mpq(self, expr):
 
     def _print_Float(self, expr):
         prec = expr._prec
-        if prec < 5:
-            dps = 0
-        else:
-            dps = prec_to_dps(expr._prec)
+        dps = self._settings.get('dps', None)
+        if dps is None:
+            dps = 0 if prec < 5 else prec_to_dps(expr._prec)
         if self._settings["full_prec"] is True:
             strip = False
         elif self._settings["full_prec"] is False:
@@ -1007,7 +1007,6 @@ def _print_Str(self, s):
         # Str does not to be printed same as str here
         return "%s(%s)" % (s.__class__.__name__, self._print(s.name))
 
-
 @print_function(StrReprPrinter)
 def sstrrepr(expr, **settings):
     """return expr in mixed str/repr form
diff --git a/sympy/series/gruntz.py b/sympy/series/gruntz.py
index 3250f2185c..f09caf9b4d 100644
--- a/sympy/series/gruntz.py
+++ b/sympy/series/gruntz.py
@@ -414,7 +414,7 @@ def sign(e, x):
             return 1
         if e.exp.is_Integer:
             return s**e.exp
-    elif isinstance(e, log) and e.args[0].is_positive:
+    elif isinstance(e, log):
         return sign(e.args[0] - 1, x)
 
     # if all else fails, do it the hard way
diff --git a/sympy/series/limits.py b/sympy/series/limits.py
index 9100624963..2d7819f28e 100644
--- a/sympy/series/limits.py
+++ b/sympy/series/limits.py
@@ -274,20 +274,16 @@ def set_signs(expr):
             arg_flag = isinstance(expr, arg)
             sign_flag = isinstance(expr, sign)
             if abs_flag or sign_flag or arg_flag:
-                try:
-                    sig = limit(expr.args[0], z, z0, dir)
-                    if sig.is_zero:
-                        sig = limit(1/expr.args[0], z, z0, dir)
-                except NotImplementedError:
-                    return expr
-                else:
-                    if sig.is_extended_real:
-                        if (sig < 0) == True:
-                            return (-expr.args[0] if abs_flag else
-                                    S.NegativeOne if sign_flag else S.Pi)
-                        elif (sig > 0) == True:
-                            return (expr.args[0] if abs_flag else
-                                    S.One if sign_flag else S.Zero)
+                sig = limit(expr.args[0], z, z0, dir)
+                if sig.is_zero:
+                    sig = limit(1/expr.args[0], z, z0, dir)
+                if sig.is_extended_real:
+                    if (sig < 0) == True:
+                        return (-expr.args[0] if abs_flag else
+                                S.NegativeOne if sign_flag else S.Pi)
+                    elif (sig > 0) == True:
+                        return (expr.args[0] if abs_flag else
+                                S.One if sign_flag else S.Zero)
             return expr
 
         if e.has(Float):
diff --git a/sympy/simplify/simplify.py b/sympy/simplify/simplify.py
index 24064f8190..4e8437c2e0 100644
--- a/sympy/simplify/simplify.py
+++ b/sympy/simplify/simplify.py
@@ -813,7 +813,6 @@ def sum_combine(s_t):
 
     return result
 
-
 def factor_sum(self, limits=None, radical=False, clear=False, fraction=False, sign=True):
     """Return Sum with constant factors extracted.
 
@@ -832,6 +831,7 @@ def factor_sum(self, limits=None, radical=False, clear=False, fraction=False, si
     >>> factor_sum(s.function, s.limits)
     y*Sum(x, (x, 1, 3))
     """
+
     # XXX deprecate in favor of direct call to factor_terms
     kwargs = {"radical": radical, "clear": clear,
         "fraction": fraction, "sign": sign}


Test patch:
diff --git a/sympy/core/tests/test_arit.py b/sympy/core/tests/test_arit.py
index 50c482d1e4..d9848c39eb 100644
--- a/sympy/core/tests/test_arit.py
+++ b/sympy/core/tests/test_arit.py
@@ -2355,10 +2355,6 @@ def test_Mul_does_not_distribute_infinity():
     assert ((1 - I)*z).expand() is oo
 
 
-def test_Mul_does_not_let_0_trump_zoo():
-    assert Mul(*[0, a + zoo]) is S.NaN
-
-
 def test_issue_8247_8354():
     from sympy.functions.elementary.trigonometric import tan
     z = sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) - sqrt(10 + 6*sqrt(3))
diff --git a/sympy/printing/tests/test_str.py b/sympy/printing/tests/test_str.py
index 96ecb66a07..90019b73a4 100644
--- a/sympy/printing/tests/test_str.py
+++ b/sympy/printing/tests/test_str.py
@@ -918,6 +918,11 @@ def test_empty_printer():
     assert str_printer.emptyPrinter(x*y) == "x*y"
     assert str_printer.emptyPrinter(32) == "32"
 
+def test_decimal_printer():
+    dec_printer = StrPrinter(settings={"dps":3})
+    f = Function('f')
+    assert dec_printer.doprint(f(1.329294)) == "f(1.33)"
+
 
 def test_settings():
     raises(TypeError, lambda: sstr(S(4), method="garbage"))
diff --git a/sympy/series/tests/test_limits.py b/sympy/series/tests/test_limits.py
index 369547f360..05a0598833 100644
--- a/sympy/series/tests/test_limits.py
+++ b/sympy/series/tests/test_limits.py
@@ -1424,12 +1424,6 @@ def test_issue_26250():
     assert limit(e1/e2, x, 0) == -S(1)/8
 
 
-def test_issue_26513():
-    assert limit(abs((-x/(x+1))**x), x ,oo) == exp(-1)
-    assert limit((x/(x + 1))**x, x, oo) == exp(-1)
-    raises (NotImplementedError, lambda: limit((-x/(x+1))**x, x, oo))
-
-
 def test_issue_26916():
     assert limit(Ei(x)*exp(-x), x, +oo) == 0
     assert limit(Ei(x)*exp(-x), x, -oo) == 0