Repository: sympy/sympy
Pull request: added a decimal separator option for floats and lists, sets, and tupl…
…e for latex printing

<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->
Fixes #16565


#### Brief description of what is fixed or changed
Added a comma decimal separator ('{,}' replace '.') when printing floats.
Example: 2{,}5 in place of 2.5.

Added a semicolon list separator(";" in places of ",") for lists, sets, and tuples
Example : [1;2;3] in place of [1,2,3]

#### Other comments
This is an fix from Pull Request #16710

#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->

* printing
  * New option decimal_separator added to LaTeX printer which applies to floats and also affects lists, tuples, and sets.
  * Added a new argument for latex in latex.py for decimal separator and list, set, and tuple element separator
  * print_float in latex.py now has a check to determine what type of decimal separator to print out
  * _print in printer.py now has a check to determine what type of decimal separator to print out
<!-- END RELEASE NOTES -->


Problem statement:
[feature request] LatexPrinter: comma as decimal separator
Hi,

Normally I don't mind the use of a period as decimal separator (not at all in fact), but when using Sympy in LaTeX documents via PythonTeX, this is becoming quite problematic, since the generated documents have to be in French and decimal numbers are usually written with a comma as decimal separator.

Unfortunately LatexPrinter does not seem to have some kind of "decimal_separator" option, hence my request: it'd be very much appreciated if I could switch the decimal separator for latex'ed output.

(If this is implemented, it might be advisable to simultaneously replace the list item separator from comma to semicolon if decimal_separator='comma' is active.)

Hints from issue discussion:
That seems like a reasonable feature request.

Can you give an example of the output that you currently see and what you would like it to be? Also can you show what you mean about the list item separator?
I think he means lists will be like:
``A = [1; 2; 3]``
instead of 
``A = [1, 2, 3]``

when decimal_separatior = comma
Well, currently, I'm mostly printing single float values, i.e. numerical approximations given by .n() or .round(). These show up as 0.134 (wanted: 0,134) or '4.76190476190476 \\cdot 10^{-7}' where I'd like the same output but starting with 4,7619...

But obviously such a setting should also apply to floating point coefficients in a larger symbolic expression.

Do note that in (La)TeX it's necessary to put the comma between braces, otherwise a small but superfluous space is inserted, as if the integer and fractional parts where two unrelated elements of a set or items in a list. Ideally LatexPrinter would either give '3.14' or '3{,}14' as latex-output depending on the value given to the requested decimal_separator option.

As for the list item separator, in regions where a comma is used as a decimal separator it's often customary to use a semicolon for lists of numbers in order to avoid confusion: It's bad style to use a comma both for separating list entries and as a decimal separator, at least if the list items are in decimal notation.
For example, the list [1, 2.718, 3.14] is currently latex()ed as:
'\\left [ 1, \\quad 2.718, \\quad 3.14\\right ]'
What I'm suggesting for lists (at least with numeric content) is:
'\\left [ 1; \\quad 2{,}718; \\quad 3{,}14\\right ]'
But I do realise that with the rather large \quad spaces it's not nearly as bad as it would be with a smaller regular space character.

The main issue I'm having is the hard-coded period character as decimal separator.
This should be easy to fix. 
Thanks for the quick responses.
(The same reasoning would of course also apply to sets and tuples and not only to lists. That part of the request may be debatable, since it may be considered to interfere with Python's look and feel, but personally I consider the latex'ed output to be math rather than some python-syntax.)
Can't ``expr.replace(".", ",")`` work?

you replace the ``.`` with ``,`` in the expression then print it
@gschintgen can you be very clear what you men showing examples of input and output?

The format of an interactive session in IPython helps make this clear e.g. right now we have:
```julia
In [23]: print(latex([1.2, 3.4]))                                                                                                              
\left[ 1.2, \  3.4\right]
```
I guess that you would like to call this like `latex(..., decimal_separator='comma')` so can you show exactly how it *would* look using the hypothetical changed code in IPython? The more examples the better.
Below is the CURRENT output I get on the command line with the current git version.
(Previous output was from an older version running in a docker container with sagemath. I can't mess with it right now.)

```
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from sympy import *
>>> latex([1, 2.3, 4.5])
'\\left[ 1, \\  2.3, \\  4.5\\right]'
>>> latex(FiniteSet(1, 2.3, 4.5))
'\\left\\{1, 2.3, 4.5\\right\\}'
>>> latex((1, 2.3, 4.5))
'\\left( 1, \\  2.3, \\  4.5\\right)'
>>> latex(0.987)
'0.987'
>>> latex(S(0.987))
'0.987'
>>> latex(.3)
'0.3'
>>> latex(S(.3))
'0.3'
>>> latex(5.8*10**(-7))
'5.8e-07'
>>> latex(S(5.7)*10**(-7))
'5.7 \\cdot 10^{-7}'
>>> latex(S(5.7*10**(-7)))
'5.7 \\cdot 10^{-7}'
>>> var('x')
x
>>> latex(1.2*x+3.4)
'1.2 x + 3.4'

```
And here is a FICTITIOUS output of the same session:
```
>>> latex([1, 2.3, 4.5], decimal_separator='comma')
'\\left[ 1; \\  2{,}3; \\  4{,}5\\right]'
>>> latex(FiniteSet(1, 2.3, 4.5), decimal_separator='comma')
'\\left\\{1; 2{,}3; 4{,}5\\right\\}'
>>> latex((1, 2.3, 4.5))
'\\left( 1; \\  2{,}3, \\  4{,}5\\right)'
>>> latex(0.987, decimal_separator='comma')
'0{,}987'
>>> latex(S(0.987), decimal_separator='comma')
'0{,}987'
>>> latex(.3, decimal_separator='comma')
'0{,}3'
>>> latex(S(.3), decimal_separator='comma')
'0{,}3'
>>> latex(5.8*10**(-7), decimal_separator='comma')
'5{,}8e-07'          <------------- ?????? (not sure about this)
>>> latex(S(5.7)*10**(-7), decimal_separator='comma')
'5{,}7 \\cdot 10^{-7}'
>>> latex(S(5.7*10**(-7)), decimal_separator='comma')
'5{,}7 \\cdot 10^{-7}'
>>> var('x')
x
>>> latex(1.2*x+3.4, decimal_separator='comma')
'1{,}2 x + 3{,}4'

```
The `{,}` syntax may seem strange, but is the customary solution for plain (La)TeX. There is of course a LaTeX package (icomma) that takes care of this minor spacing issue automatically (i.e. a plain comma inbetween digits will no longer insert a small space), but it does not convert periods to commas.

As an aside, I noticed the following:
```
>>> latex(5.6*10**(-7))
'5.599999999999999e-07'
>>> latex(S(5.6)*10**(-7))
'5.6 \\cdot 10^{-7}'
>>> latex(S(5.6*10**(-7)))
'5.6 \\cdot 10^{-7}'
```
I'm wondering if the first printout is the intended behaviour. I do understand that the internal float value (with its Python type instead of a Sympy type) may be unable to represent exactly 5.6, but I (naïvely) expected Sympy's latex function to always sympify its input.

@Sc0rpi0n101
> 
> 
> Can't `expr.replace(".", ",")` work?
> 
> you replace the `.` with `,` in the expression then print it

This is indeed a possible workaround. The reason I'm suggesting a new feature is that it would be in line with other typographic options supported by LatexPrinter. What's more, PythonTeX has convenience macros like `\sympy{.....}` that automatically take care of calling latex() and doing the necessary handling. Furthermore PythonTeX already has an interface that lets the user set the appropriate LatexPrinter options. So it'd be the most natural solution to have a specific option for this.
Thanks @gschintgen for the detailed examples. That will be very helpful for someone who tries to implement this feature.

The other reason I asked for you to do that because it would highlight some problematic cases and you've identified the one that I expected which is numbers of the form 1.2e10. In the case
```julia
In [48]: latex(5.8e-7)                                                                                                                         
Out[48]: '5.8e-07'
```
What is it that you are not sure about? Is it common to use a comma with numbers in this form? Or is it that you think `latex()` should show this with `\cdot 10^{-7}`?

For the other case you mention note that
```julia
In [51]: 5.6*10**-7 == 5.6e-7                                                                                                                  
Out[51]: False
```
since the way that you create the number is leading to a rounding error. This is compounded by a bug in SymPy's `Float.__str__`:
```julia
In [53]: x1 = S(5.6*10**-7)                                                                                                                    

In [54]: x2 = S('5.6e-7')                                                                                                                      

In [55]: x1                                                                                                                                    
Out[55]: 5.60000000000000e-7

In [56]: x2                                                                                                                                    
Out[56]: 5.60000000000000e-7

In [57]: str(x1) == str(x2)                                                                                                                    
Out[57]: True

In [58]: x1 == x2                                                                                                                              
Out[58]: False

In [61]: srepr(x1)                                                                                                                             
Out[61]: "Float('5.5999999999999993e-7', precision=53)"

In [62]: srepr(x2)                                                                                                                             
Out[62]: "Float('5.6000000000000004e-7', precision=53)"
```
For Python floats we have the property that `eval(str(x)) == x` for all floats x (except nan). SymPy's `Float` often breaks this property. I think there already is an open issue somewhere about this.

Commit message: added the decimal separator tests to test_latex.py. Latex.py and printer.py pass all tests

Patch to review:
diff --git a/sympy/printing/latex.py b/sympy/printing/latex.py
index e5cd6a446a68..6bb4803ef614 100644
--- a/sympy/printing/latex.py
+++ b/sympy/printing/latex.py
@@ -140,6 +140,7 @@ class LatexPrinter(Printer):
         "mat_symbol_style": "plain",
         "imaginary_unit": "i",
         "gothic_re_im": False,
+        "decimal_separator": "period",
     }
 
     def __init__(self, settings=None):
@@ -377,6 +378,8 @@ def _print_Float(self, expr):
 
             if exp[0] == '+':
                 exp = exp[1:]
+            if self._settings['decimal_separator'] == 'comma':
+                mant = mant.replace('.','{,}')
 
             return r"%s%s10^{%s}" % (mant, separator, exp)
         elif str_real == "+inf":
@@ -384,6 +387,8 @@ def _print_Float(self, expr):
         elif str_real == "-inf":
             return r"- \infty"
         else:
+            if self._settings['decimal_separator'] == 'comma':
+                str_real = str_real.replace('.','{,}')
             return str_real
 
     def _print_Cross(self, expr):
@@ -1729,8 +1734,14 @@ def _print_UniversalSet(self, expr):
         return r"\mathbb{U}"
 
     def _print_tuple(self, expr):
-        return r"\left( %s\right)" % \
-            r", \  ".join([self._print(i) for i in expr])
+        if self._settings['decimal_separator'] =='comma':
+            return r"\left( %s\right)" % \
+                r"; \  ".join([self._print(i) for i in expr])
+        elif self._settings['decimal_separator'] =='period':
+            return r"\left( %s\right)" % \
+                r", \  ".join([self._print(i) for i in expr])
+        else:
+            raise ValueError('DecimalSeparatorNotFoundError')
 
     def _print_TensorProduct(self, expr):
         elements = [self._print(a) for a in expr.args]
@@ -1744,8 +1755,15 @@ def _print_Tuple(self, expr):
         return self._print_tuple(expr)
 
     def _print_list(self, expr):
-        return r"\left[ %s\right]" % \
-            r", \  ".join([self._print(i) for i in expr])
+        if self._settings['decimal_separator'] == 'comma':
+            return r"\left[ %s\right]" % \
+                r"; \  ".join([self._print(i) for i in expr])
+        elif self._settings['decimal_separator'] == 'period':
+            return r"\left[ %s\right]" % \
+                r", \  ".join([self._print(i) for i in expr])
+        else:
+            raise ValueError('DecimalSeparatorNotFoundError')
+
 
     def _print_dict(self, d):
         keys = sorted(d.keys(), key=default_sort_key)
@@ -1826,9 +1844,15 @@ def _print_FiniteSet(self, s):
 
     def _print_set(self, s):
         items = sorted(s, key=default_sort_key)
-        items = ", ".join(map(self._print, items))
+        if self._settings['decimal_separator'] == 'comma':
+            items = "; ".join(map(self._print, items))
+        elif self._settings['decimal_separator'] == 'period':
+            items = ", ".join(map(self._print, items))
+        else:
+            raise ValueError('DecimalSeparatorNotFoundError')
         return r"\left\{%s\right\}" % items
 
+
     _print_frozenset = _print_set
 
     def _print_Range(self, s):
@@ -2386,7 +2410,8 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
           itex=False, ln_notation=False, long_frac_ratio=None,
           mat_delim="[", mat_str=None, mode="plain", mul_symbol=None,
           order=None, symbol_names=None, root_notation=True,
-          mat_symbol_style="plain", imaginary_unit="i", gothic_re_im=False):
+          mat_symbol_style="plain", imaginary_unit="i", gothic_re_im=False,
+          decimal_separator = "period" ):
     r"""Convert the given expression to LaTeX string representation.
 
     Parameters
@@ -2452,6 +2477,12 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
     gothic_re_im : boolean, optional
         If set to ``True``, `\Re` and `\Im` is used for ``re`` and ``im``, respectively.
         The default is ``False`` leading to `\operatorname{re}` and `\operatorname{im}`.
+    decimal_separator : string, optional
+        Specifies what separator to use to separate the whole and fractional parts of a
+        floating point number as in `2.5` (for the default, "period") or `2{,}5`
+        when "comma" is specified. Lists, sets, and tuple are printed with semicolon
+        separating the elements when 'comma' is chosen([1; 2; 3] when comma is chosen and
+        [1,2,3] for when 'period' is chosen).
 
     Notes
     =====
@@ -2580,6 +2611,7 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
         'mat_symbol_style': mat_symbol_style,
         'imaginary_unit': imaginary_unit,
         'gothic_re_im': gothic_re_im,
+        'decimal_separator': decimal_separator,
     }
 
     return LatexPrinter(settings).doprint(expr)
@@ -2588,6 +2620,7 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
 def print_latex(expr, **settings):
     """Prints LaTeX representation of the given expression. Takes the same
     settings as ``latex()``."""
+
     print(latex(expr, **settings))
 
 
diff --git a/sympy/printing/printer.py b/sympy/printing/printer.py
index 2f1a183376d1..89037ccd69b6 100644
--- a/sympy/printing/printer.py
+++ b/sympy/printing/printer.py
@@ -285,7 +285,11 @@ def _print(self, expr, **kwargs):
                 printmethod = '_print_' + cls.__name__
                 if hasattr(self, printmethod):
                     return getattr(self, printmethod)(expr, **kwargs)
-            # Unknown object, fall back to the emptyPrinter.
+            # Unknown object, fall back to the emptyPrinter. Checks what type of
+            # decimal separator to print.
+            if (self.emptyPrinter == str) & \
+                (self._settings.get('decimal_separator', None) == 'comma'):
+                expr = str(expr).replace('.', '{,}')
             return self.emptyPrinter(expr)
         finally:
             self._print_level -= 1
diff --git a/sympy/printing/tests/test_latex.py b/sympy/printing/tests/test_latex.py
index 748ee915df23..5a7a760683bc 100644
--- a/sympy/printing/tests/test_latex.py
+++ b/sympy/printing/tests/test_latex.py
@@ -2278,3 +2278,48 @@ def test_unit_ptinting():
     assert latex(5*meter) == r'5 \text{m}'
     assert latex(3*gibibyte) == r'3 \text{gibibyte}'
     assert latex(4*microgram/second) == r'\frac{4 \mu\text{g}}{\text{s}}'
+
+def test_latex_decimal_separator():
+
+    x, y, z, t = symbols('x y z t')
+    k, m, n = symbols('k m n', integer=True)
+    f, g, h = symbols('f g h', cls=Function)
+    # init_printing()
+
+    # comma decimal_separator
+    assert(latex([1, 2.3, 4.5], decimal_separator='comma') == r'\left[ 1; \  2{,}3; \  4{,}5\right]')
+    assert(latex(FiniteSet(1, 2.3, 4.5), decimal_separator='comma') == r'\left\{1; 2{,}3; 4{,}5\right\}')
+    assert(latex((1, 2.3, 4.6), decimal_separator = 'comma') == r'\left( 1; \  2{,}3; \  4{,}6\right)')
+
+    # period decimal_separator
+    assert(latex([1, 2.3, 4.5], decimal_separator='period') == r'\left[ 1, \  2.3, \  4.5\right]' )
+    assert(latex(FiniteSet(1, 2.3, 4.5), decimal_separator='period') == r'\left\{1, 2.3, 4.5\right\}')
+    assert(latex((1, 2.3, 4.6), decimal_separator = 'period') == r'\left( 1, \  2.3, \  4.6\right)')
+
+    # default decimal_separator
+    assert(latex([1, 2.3, 4.5]) == r'\left[ 1, \  2.3, \  4.5\right]')
+    assert(latex(FiniteSet(1, 2.3, 4.5)) == r'\left\{1, 2.3, 4.5\right\}')
+    assert(latex((1, 2.3, 4.6)) == r'\left( 1, \  2.3, \  4.6\right)')
+
+    assert(latex(Mul(3.4,5.3), decimal_separator = 'comma') ==r'18{,}02')
+    assert(latex(3.4*5.3, decimal_separator = 'comma')==r'18{,}02')
+    x = symbols('x')
+    y = symbols('y')
+    z = symbols('z')
+    assert(latex(x*5.3 + 2**y**3.4 + 4.5 + z, decimal_separator = 'comma')== r'2^{y^{3{,}4}} + 5{,}3 x + z + 4{,}5')
+    #print(srepr(x*5.3 + 2**y**3.4 + 4.5 + z))
+
+    assert(latex(0.987, decimal_separator='comma') == r'0{,}987')
+    assert(latex(S(0.987), decimal_separator='comma')== r'0{,}987')
+    assert(latex(.3, decimal_separator='comma')== r'0{,}3')
+    assert(latex(S(.3), decimal_separator='comma')== r'0{,}3')
+
+
+    assert(latex(5.8*10**(-7), decimal_separator='comma') ==r'5{,}8e-07')
+    assert(latex(S(5.7)*10**(-7), decimal_separator='comma')==r'5{,}7 \cdot 10^{-7}')
+    assert(latex(S(5.7*10**(-7)), decimal_separator='comma')==r'5{,}7 \cdot 10^{-7}')
+
+    x = symbols('x')
+    assert(latex(1.2*x+3.4, decimal_separator='comma')==r'1{,}2 x + 3{,}4')
+
+    assert(latex(FiniteSet(1, 2.3, 4.5), decimal_separator='period')==r'\left\{1, 2.3, 4.5\right\}')


Code review comments:
diff_hunk: @@ -2452,6 +2477,12 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
     gothic_re_im : boolean, optional
         If set to ``True``, `\Re` and `\Im` is used for ``re`` and ``im``, respectively.
         The default is ``False`` leading to `\operatorname{re}` and `\operatorname{im}`.
+    decimal_separator : string, optional
+        Specifies what separator to use to separate the whole and fractional parts of a
+        floating point number as in `2.5` (for the default, "period") or `2{,}5`
+        when "comma" is specified. Lists, sets, and tuple are printed with semicolon
+        separating the elements when 'comma' is chosen([1; 2; 3] when comma is chosen and
+        [1,2,3] for when 'period' is chosen).
path: sympy/printing/latex.py
text: @user1:
`"period"` (for consistency)

@user1:
Double backticks.

diff_hunk: @@ -2452,6 +2477,12 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
     gothic_re_im : boolean, optional
         If set to ``True``, `\Re` and `\Im` is used for ``re`` and ``im``, respectively.
         The default is ``False`` leading to `\operatorname{re}` and `\operatorname{im}`.
+    decimal_separator : string, optional
+        Specifies what separator to use to separate the whole and fractional parts of a
+        floating point number as in `2.5` (for the default, "period") or `2{,}5`
path: sympy/printing/latex.py
text: @user1:
If I understand correctly using a single backtick leads to LaTeX rendering, which is OK, but it is not so clear for the user that the `{}` are added as well. Not sure what is the best option.

I think that the () can be removes though.

For consistency I think all options (period, comma) should be in double backticks, which will make it print as source code.

@author:
I'm not sure about the {} as well. It was something to do with the spacing being strange around the commas during the latex rendering. 

EDIT - Forgot to add this:

As for removing the (), I have it as this now
 `2.5` for the default, ``period`` or `2{,}5` when ``comma`` is specified. Lists, sets, and tuple are printed with semicolon separating the elements when ``comma`` is chosen. For example, [1; 2; 3] when ``comma`` is chosen and [1,2,3] for when ``period`` is chosen.