Repository: hgrecco/pint

Issue:
Modulo & floor div are not handled properly in pint_eval
### Currently:

```python
>>> from pint import UnitRegistry
>>> 
>>> ureg = UnitRegistry()
>>> parse = ureg.parse_expression
>>> parse("(5 % 2) m")
<Quantity(10, 'meter')>
>>> parse("(5 // 2) m")
<Quantity(10, 'meter')>
```

### Expected:

```python
>>> from pint import UnitRegistry
>>> 
>>> ureg = UnitRegistry()
>>> parse = ureg.parse_expression
>>> parse("(5 % 2) m")
<Quantity(1, 'meter')>
>>> parse("(5 // 2) m")
<Quantity(2, 'meter')>
```

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index ba0397c..d31e20a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,7 @@ Pint Changelog
 - Use default numpy `np.printoptions` available since numpy 1.15.
 - Implement `numpy.nanprod` (Issue #1369)
 - Fix default_format ignored for measurement (Issue #1456)
+- Fix handling modulo & floordiv operator in pint_eval (Issue #1470)
 - Fix `to_compact` and `infer_base_unit` for non-float non_int_type.
 
 ### Breaking Changes
diff --git a/pint/pint_eval.py b/pint/pint_eval.py
index 0d16c63..77fe243 100644
--- a/pint/pint_eval.py
+++ b/pint/pint_eval.py
@@ -20,7 +20,9 @@ _OP_PRIORITY = {
     "unary": 2,
     "*": 1,
     "": 1,  # operator for implicit ops
+    "//": 1,
     "/": 1,
+    "%": 1,
     "+": 0,
     "-": 0,
 }
@@ -48,6 +50,8 @@ _BINARY_OPERATOR_MAP = {
     "/": operator.truediv,
     "+": operator.add,
     "-": operator.sub,
+    "%": operator.mod,
+    "//": operator.floordiv,
 }
 
 _UNARY_OPERATOR_MAP = {"+": lambda x: x, "-": lambda x: x * -1}


Test patch:
diff --git a/pint/testsuite/test_pint_eval.py b/pint/testsuite/test_pint_eval.py
index 2372933..bed8105 100644
--- a/pint/testsuite/test_pint_eval.py
+++ b/pint/testsuite/test_pint_eval.py
@@ -1,3 +1,5 @@
+import pytest
+
 from pint.compat import tokenizer
 from pint.pint_eval import build_eval_tree
 
@@ -6,61 +8,67 @@ class TestPintEval:
     def _test_one(self, input_text, parsed):
         assert build_eval_tree(tokenizer(input_text)).to_string() == parsed
 
-    def test_build_eval_tree(self):
-        self._test_one("3", "3")
-        self._test_one("1 + 2", "(1 + 2)")
-        # order of operations
-        self._test_one("2 * 3 + 4", "((2 * 3) + 4)")
-        # parentheses
-        self._test_one("2 * (3 + 4)", "(2 * (3 + 4))")
-        # more order of operations
-        self._test_one("1 + 2 * 3 ** (4 + 3 / 5)", "(1 + (2 * (3 ** (4 + (3 / 5)))))")
-        # nested parentheses at beginning
-        self._test_one("1 * ((3 + 4) * 5)", "(1 * ((3 + 4) * 5))")
-        # nested parentheses at end
-        self._test_one("1 * (5 * (3 + 4))", "(1 * (5 * (3 + 4)))")
-        # nested parentheses in middle
-        self._test_one("1 * (5 * (3 + 4) / 6)", "(1 * ((5 * (3 + 4)) / 6))")
-        # unary
-        self._test_one("-1", "(- 1)")
-        # unary
-        self._test_one("3 * -1", "(3 * (- 1))")
-        # double unary
-        self._test_one("3 * --1", "(3 * (- (- 1)))")
-        # parenthetical unary
-        self._test_one("3 * -(2 + 4)", "(3 * (- (2 + 4)))")
-        # parenthetical unary
-        self._test_one("3 * -((2 + 4))", "(3 * (- (2 + 4)))")
-        # implicit op
-        self._test_one("3 4", "(3 4)")
-        # implicit op, then parentheses
-        self._test_one("3 (2 + 4)", "(3 (2 + 4))")
-        # parentheses, then implicit
-        self._test_one("(3 ** 4 ) 5", "((3 ** 4) 5)")
-        # implicit op, then exponentiation
-        self._test_one("3 4 ** 5", "(3 (4 ** 5))")
-        # implicit op, then addition
-        self._test_one("3 4 + 5", "((3 4) + 5)")
-        # power followed by implicit
-        self._test_one("3 ** 4 5", "((3 ** 4) 5)")
-        # implicit with parentheses
-        self._test_one("3 (4 ** 5)", "(3 (4 ** 5))")
-        # exponent with e
-        self._test_one("3e-1", "3e-1")
-        # multiple units with exponents
-        self._test_one("kg ** 1 * s ** 2", "((kg ** 1) * (s ** 2))")
-        # multiple units with neg exponents
-        self._test_one("kg ** -1 * s ** -2", "((kg ** (- 1)) * (s ** (- 2)))")
-        # multiple units with neg exponents
-        self._test_one("kg^-1 * s^-2", "((kg ^ (- 1)) * (s ^ (- 2)))")
-        # multiple units with neg exponents, implicit op
-        self._test_one("kg^-1 s^-2", "((kg ^ (- 1)) (s ^ (- 2)))")
-        # nested power
-        self._test_one("2 ^ 3 ^ 2", "(2 ^ (3 ^ 2))")
-        # nested power
-        self._test_one("gram * second / meter ** 2", "((gram * second) / (meter ** 2))")
-        # nested power
-        self._test_one("gram / meter ** 2 / second", "((gram / (meter ** 2)) / second)")
-        # units should behave like numbers, so we don't need a bunch of extra tests for them
-        # implicit op, then addition
-        self._test_one("3 kg + 5", "((3 kg) + 5)")
+    @pytest.mark.parametrize(
+        ("input_text", "parsed"),
+        (
+            ("3", "3"),
+            ("1 + 2", "(1 + 2)"),
+            ("2 * 3 + 4", "((2 * 3) + 4)"),  # order of operations
+            ("2 * (3 + 4)", "(2 * (3 + 4))"),  # parentheses
+            (
+                "1 + 2 * 3 ** (4 + 3 / 5)",
+                "(1 + (2 * (3 ** (4 + (3 / 5)))))",
+            ),  # more order of operations
+            (
+                "1 * ((3 + 4) * 5)",
+                "(1 * ((3 + 4) * 5))",
+            ),  # nested parentheses at beginning
+            ("1 * (5 * (3 + 4))", "(1 * (5 * (3 + 4)))"),  # nested parentheses at end
+            (
+                "1 * (5 * (3 + 4) / 6)",
+                "(1 * ((5 * (3 + 4)) / 6))",
+            ),  # nested parentheses in middle
+            ("-1", "(- 1)"),  # unary
+            ("3 * -1", "(3 * (- 1))"),  # unary
+            ("3 * --1", "(3 * (- (- 1)))"),  # double unary
+            ("3 * -(2 + 4)", "(3 * (- (2 + 4)))"),  # parenthetical unary
+            ("3 * -((2 + 4))", "(3 * (- (2 + 4)))"),  # parenthetical unary
+            # implicit op
+            ("3 4", "(3 4)"),
+            # implicit op, then parentheses
+            ("3 (2 + 4)", "(3 (2 + 4))"),
+            # parentheses, then implicit
+            ("(3 ** 4 ) 5", "((3 ** 4) 5)"),
+            # implicit op, then exponentiation
+            ("3 4 ** 5", "(3 (4 ** 5))"),
+            # implicit op, then addition
+            ("3 4 + 5", "((3 4) + 5)"),
+            # power followed by implicit
+            ("3 ** 4 5", "((3 ** 4) 5)"),
+            # implicit with parentheses
+            ("3 (4 ** 5)", "(3 (4 ** 5))"),
+            # exponent with e
+            ("3e-1", "3e-1"),
+            # multiple units with exponents
+            ("kg ** 1 * s ** 2", "((kg ** 1) * (s ** 2))"),
+            # multiple units with neg exponents
+            ("kg ** -1 * s ** -2", "((kg ** (- 1)) * (s ** (- 2)))"),
+            # multiple units with neg exponents
+            ("kg^-1 * s^-2", "((kg ^ (- 1)) * (s ^ (- 2)))"),
+            # multiple units with neg exponents, implicit op
+            ("kg^-1 s^-2", "((kg ^ (- 1)) (s ^ (- 2)))"),
+            # nested power
+            ("2 ^ 3 ^ 2", "(2 ^ (3 ^ 2))"),
+            # nested power
+            ("gram * second / meter ** 2", "((gram * second) / (meter ** 2))"),
+            # nested power
+            ("gram / meter ** 2 / second", "((gram / (meter ** 2)) / second)"),
+            # units should behave like numbers, so we don't need a bunch of extra tests for them
+            # implicit op, then addition
+            ("3 kg + 5", "((3 kg) + 5)"),
+            ("(5 % 2) m", "((5 % 2) m)"),  # mod operator
+            ("(5 // 2) m", "((5 // 2) m)"),  # floordiv operator
+        ),
+    )
+    def test_build_eval_tree(self, input_text, parsed):
+        self._test_one(input_text, parsed)