Repository: hgrecco/pint

Issue:
Suggestion: Add unitless dimensions
Not sure if this would be difficult to implement internally, but one common thing I do with units is convert between various unitless representations (i.e. I want to convert a percent to ppm).

Suggested additional dimensionless units:
- percent
- ppthousand
- ppm
- ppb
- ppt
- ppq

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 1416ada..1cc6b51 100644
--- a/CHANGES
+++ b/CHANGES
@@ -13,6 +13,10 @@ Pint Changelog
 - Changed frequency to angular frequency in the docs.
   (PR #1668)
 
+### Breaking Changes
+
+- Support percent and ppm units. Support the `%` symbol.
+  (Issue #1277)
 
 0.20.1 (2022-10-27)
 -------------------
diff --git a/pint/default_en.txt b/pint/default_en.txt
index 95e2987..ed4f3d8 100644
--- a/pint/default_en.txt
+++ b/pint/default_en.txt
@@ -148,6 +148,10 @@ byte = 8 * bit = B = octet
 # byte = 8 * bit = _ = octet
 ## NOTE: B (byte) symbol can conflict with Bell
 
+# Ratios
+percent = 0.01 = %
+ppm = 1e-6
+
 # Length
 angstrom = 1e-10 * meter = Å = ångström = Å
 micron = micrometer = µ = μ
diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py
index 8b98965..ffa6fb4 100644
--- a/pint/facets/plain/registry.py
+++ b/pint/facets/plain/registry.py
@@ -229,6 +229,8 @@ class PlainRegistry(metaclass=RegistryMeta):
         self.force_ndarray = force_ndarray
         self.force_ndarray_like = force_ndarray_like
         self.preprocessors = preprocessors or []
+        # use a default preprocessor to support "%"
+        self.preprocessors.insert(0, lambda string: string.replace("%", " percent "))
 
         #: mode used to fill in the format defaults
         self.separate_format_defaults = separate_format_defaults


Test patch:
diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index a07e850..cf7e39c 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -7,6 +7,7 @@ import pytest
 from pint import Context, DimensionalityError, UnitRegistry, get_application_registry
 from pint.compat import np
 from pint.facets.plain.unit import UnitsContainer
+from pint.testing import assert_equal
 from pint.testsuite import QuantityTestCase, helpers
 from pint.util import ParserHelper
 
@@ -855,6 +856,29 @@ class TestIssues(QuantityTestCase):
             np.array((0.04, 0.09)),
         )
 
+    def test_issue1277(self, module_registry):
+        ureg = module_registry
+        assert ureg("%") == ureg("percent")
+        assert ureg("%") == ureg.percent
+        assert ureg("ppm") == ureg.ppm
+
+        a = ureg.Quantity("10 %")
+        b = ureg.Quantity("100 ppm")
+        c = ureg.Quantity("0.5")
+
+        assert f"{a}" == "10 percent"
+        assert f"{a:~}" == "10 %"
+        assert f"{b}" == "100 ppm"
+        assert f"{b:~}" == "100 ppm"
+
+        assert_equal(a, 0.1)
+        assert_equal(1000 * b, a)
+        assert_equal(c, 5 * a)
+
+        assert_equal((1 * ureg.meter) / (1 * ureg.kilometer), 0.1 * ureg.percent)
+        assert c.to("percent").m == 50
+        # assert c.to("%").m == 50  # TODO: fails.
+
     @helpers.requires_uncertainties()
     def test_issue_1300(self):
         module_registry = UnitRegistry()