Repository: sympy/sympy

Issue:
[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.)

Discussion:

Patch:
diff --git a/sympy/printing/latex.py b/sympy/printing/latex.py
index e5cd6a446a..a00aa2f608 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('Unknown Decimal Separator')
 
     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('Unknown Decimal Separator')
+
 
     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('Unknown Decimal Separator')
         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. For example, [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 2f1a183376..89037ccd69 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


Test patch:
diff --git a/sympy/printing/tests/test_latex.py b/sympy/printing/tests/test_latex.py
index 748ee915df..e58a61c955 100644
--- a/sympy/printing/tests/test_latex.py
+++ b/sympy/printing/tests/test_latex.py
@@ -2278,3 +2278,50 @@ 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)
+
+    # 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')
+
+    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\}')
+
+    # Error Handling tests
+    raises(ValueError, lambda: latex([1,2.3,4.5], decimal_separator='non_existing_decimal_separator_in_list'))
+    raises(ValueError, lambda: latex(FiniteSet(1,2.3,4.5), decimal_separator='non_existing_decimal_separator_in_set'))
+    raises(ValueError, lambda: latex((1,2.3,4.5), decimal_separator='non_existing_decimal_separator_in_tuple'))