Repository: sympy/sympy

Issue:
Printing of UnevaluatedExpr needs parenthesis around Add terms
Floating point "add" is non-associative, i.e. `(x+y)+z` often differs from `x+(y+z)`. In SymPy `Add` (re)orders its arguments (for good reasons), but for representing e.g. an algorithm in finite precision this creates a problem (see e.g. [Kahan summation on wikipedia](https://en.wikipedia.org/wiki/Kahan_summation_algorithm)).

Typically we use `UnevaluatedExpr` for situations like this:
```python
>>> from sympy import UnevaluatedExpr as ue
>>> from sympy.abc import k,l,m
>>> ue(k+m)+l
l + k + m
>>> ue(k+m)+l + k
k + l + k + m
```
but, as you see the printer is not emitting the required parenthesis to make this useful for representing non-associative add.
 
Note that SymEngine gets this right:
```python
>>> from symengine import Symbol as S, UnevaluatedExpr as UE
>>> x,y,z = map(S, 'xyz')
>>> UE(y + x) + z
z + (x + y)
```

I propose that we fix this in the printers (I suspect updating precedence for `UnevaluatedExpr` might be enough, I haven't tried yet though). At the very least, the code printers should output parenthesis, but I think it's reasonable for the `StrPrinter` to do so as well?

Discussion:

Patch:
diff --git a/doc/src/tutorial/manipulation.rst b/doc/src/tutorial/manipulation.rst
index adb7d729bd..3bb0262a91 100644
--- a/doc/src/tutorial/manipulation.rst
+++ b/doc/src/tutorial/manipulation.rst
@@ -546,7 +546,7 @@ expression. Combine both of the methods to prevent both inside and outside
 evaluations:
 
     >>> UnevaluatedExpr(sympify("x + x", evaluate=False)) + y
-    y + x + x
+    y + (x + x)
 
 ``UnevalutedExpr`` is supported by SymPy printers and can be used to print the
 result in different output forms. For example
diff --git a/sympy/printing/precedence.py b/sympy/printing/precedence.py
index a54f2ffce2..47ff463faf 100644
--- a/sympy/printing/precedence.py
+++ b/sympy/printing/precedence.py
@@ -101,7 +101,7 @@ def precedence_FracElement(item):
 
 
 def precedence_UnevaluatedExpr(item):
-    return precedence(item.args[0])
+    return precedence(item.args[0]) - 0.5
 
 
 PRECEDENCE_FUNCTIONS = {


Test patch:
diff --git a/sympy/printing/tests/test_c.py b/sympy/printing/tests/test_c.py
index 49bc6c52f0..fc7562bd0e 100644
--- a/sympy/printing/tests/test_c.py
+++ b/sympy/printing/tests/test_c.py
@@ -1,6 +1,6 @@
 from sympy.core import (
     S, pi, oo, symbols, Rational, Integer, Float, Mod, GoldenRatio, EulerGamma, Catalan,
-    Lambda, Dummy, Eq, nan, Mul, Pow
+    Lambda, Dummy, Eq, nan, Mul, Pow, UnevaluatedExpr
 )
 from sympy.functions import (
     Abs, acos, acosh, asin, asinh, atan, atanh, atan2, ceiling, cos, cosh, erf,
@@ -840,3 +840,10 @@ def test_ccode_submodule():
     # Test the compatibility sympy.printing.ccode module imports
     with warns_deprecated_sympy():
         import sympy.printing.ccode # noqa:F401
+
+
+def test_ccode_UnevaluatedExpr():
+    assert ccode(UnevaluatedExpr(y * x) + z) == "z + x*y"
+    assert ccode(UnevaluatedExpr(y + x) + z) == "z + (x + y)"  # gh-21955
+    w = symbols('w')
+    assert ccode(UnevaluatedExpr(y + x) + UnevaluatedExpr(z + w)) == "(w + z) + (x + y)"