Repository: hgrecco/pint

Issue:
Decimal Parsing Issue
In version 0.9 the following code doesn't work with python 3.7.5.

```python
import decimal
import pint

registry = pint.UnitRegistry()

mps = decimal.Decimal('65000.0') * registry('m/s')
```

I get the following exception:
```
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "env/lib64/python3.7/site-packages/pint/quantity.py", line 900, in __mul__
    return self._mul_div(other, operator.mul)
  File "env/lib64/python3.7/site-packages/pint/quantity.py", line 75, in wrapped
    result = f(self, *args, **kwargs)
  File "env/lib64/python3.7/site-packages/pint/quantity.py", line 60, in wrapped
    result = f(self, *args, **kwargs)
  File "env/lib64/python3.7/site-packages/pint/quantity.py", line 866, in _mul_div
    magnitude = magnitude_op(self._magnitude, other_magnitude)
TypeError: unsupported operand type(s) for *: 'float' and 'decimal.Decimal'
```

The work around is to use quantities:

```python
Q_ = registry.Quantity                                                                                                                                                                                                           
Q_(decimal.Decimal('65000.0'), 'm/s')
```
I would expect that both would work?  Is this not correct?

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 0e52d79..b897f29 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,7 +4,12 @@ Pint Changelog
 0.12 (unreleased)
 -----------------
 
-- Nothing changed yet.
+- Add full support for Decimal and Fraction at the registry level.
+  **BREAKING CHANGE**: 
+  `use_decimal` is deprecated. Use `non_int_type=Decimal` when instantiating
+  the registry.
+- Fixed bug where numpy.pad didn't work without specifying constant_values or
+  end_values (Issue #1026)
 
 
 0.11 (2020-02-19)
@@ -35,8 +40,7 @@ Pint Changelog
 - Allow constants in units by using a leading underscore (Issue #989, Thanks
   Juan Nunez-Iglesias)
 - Fixed bug where to_compact handled prefix units incorrectly (Issue #960)
-- Fixed bug where numpy.pad didn't work without specifying constant_values or
-  end_values (Issue #1026)
+
 
 0.10.1 (2020-01-07)
 -------------------
diff --git a/pint/context.py b/pint/context.py
index bcc34c3..e1405d5 100644
--- a/pint/context.py
+++ b/pint/context.py
@@ -133,7 +133,7 @@ class Context:
         return context
 
     @classmethod
-    def from_lines(cls, lines, to_base_func=None):
+    def from_lines(cls, lines, to_base_func=None, non_int_type=float):
         lines = SourceIterator(lines)
 
         lineno, header = next(lines)
@@ -186,14 +186,20 @@ class Context:
                 func = _expression_to_function(eq)
 
                 if "<->" in rel:
-                    src, dst = (ParserHelper.from_string(s) for s in rel.split("<->"))
+                    src, dst = (
+                        ParserHelper.from_string(s, non_int_type)
+                        for s in rel.split("<->")
+                    )
                     if to_base_func:
                         src = to_base_func(src)
                         dst = to_base_func(dst)
                     ctx.add_transformation(src, dst, func)
                     ctx.add_transformation(dst, src, func)
                 elif "->" in rel:
-                    src, dst = (ParserHelper.from_string(s) for s in rel.split("->"))
+                    src, dst = (
+                        ParserHelper.from_string(s, non_int_type)
+                        for s in rel.split("->")
+                    )
                     if to_base_func:
                         src = to_base_func(src)
                         dst = to_base_func(dst)
diff --git a/pint/definitions.py b/pint/definitions.py
index 5dfc0ea..338d1fc 100644
--- a/pint/definitions.py
+++ b/pint/definitions.py
@@ -65,12 +65,13 @@ class _NotNumeric(Exception):
         self.value = value
 
 
-def numeric_parse(s):
+def numeric_parse(s, non_int_type=float):
     """Try parse a string into a number (without using eval).
 
     Parameters
     ----------
     s : str
+    non_int_type : type
 
     Returns
     -------
@@ -81,7 +82,7 @@ def numeric_parse(s):
     _NotNumeric
         If the string cannot be parsed as a number.
     """
-    ph = ParserHelper.from_string(s)
+    ph = ParserHelper.from_string(s, non_int_type)
 
     if len(ph):
         raise _NotNumeric(s)
@@ -120,12 +121,13 @@ class Definition:
         return self._converter.is_multiplicative
 
     @classmethod
-    def from_string(cls, definition):
+    def from_string(cls, definition, non_int_type=float):
         """Parse a definition.
 
         Parameters
         ----------
         definition : str or PreprocessedDefinition
+        non_int_type : type
 
         Returns
         -------
@@ -136,13 +138,13 @@ class Definition:
             definition = PreprocessedDefinition.from_string(definition)
 
         if definition.name.startswith("@alias "):
-            return AliasDefinition.from_string(definition)
+            return AliasDefinition.from_string(definition, non_int_type)
         elif definition.name.startswith("["):
-            return DimensionDefinition.from_string(definition)
+            return DimensionDefinition.from_string(definition, non_int_type)
         elif definition.name.endswith("-"):
-            return PrefixDefinition.from_string(definition)
+            return PrefixDefinition.from_string(definition, non_int_type)
         else:
-            return UnitDefinition.from_string(definition)
+            return UnitDefinition.from_string(definition, non_int_type)
 
     @property
     def name(self):
@@ -182,7 +184,7 @@ class PrefixDefinition(Definition):
     """
 
     @classmethod
-    def from_string(cls, definition):
+    def from_string(cls, definition, non_int_type=float):
         if isinstance(definition, str):
             definition = PreprocessedDefinition.from_string(definition)
 
@@ -193,7 +195,7 @@ class PrefixDefinition(Definition):
             symbol = definition.symbol
 
         try:
-            converter = ScaleConverter(numeric_parse(definition.value))
+            converter = ScaleConverter(numeric_parse(definition.value, non_int_type))
         except _NotNumeric as ex:
             raise ValueError(
                 f"Prefix definition ('{definition.name}') must contain only numbers, not {ex.value}"
@@ -226,7 +228,7 @@ class UnitDefinition(Definition):
         super().__init__(name, symbol, aliases, converter)
 
     @classmethod
-    def from_string(cls, definition):
+    def from_string(cls, definition, non_int_type=float):
         if isinstance(definition, str):
             definition = PreprocessedDefinition.from_string(definition)
 
@@ -235,7 +237,7 @@ class UnitDefinition(Definition):
 
             try:
                 modifiers = dict(
-                    (key.strip(), numeric_parse(value))
+                    (key.strip(), numeric_parse(value, non_int_type))
                     for key, value in (part.split(":") for part in modifiers.split(";"))
                 )
             except _NotNumeric as ex:
@@ -247,7 +249,7 @@ class UnitDefinition(Definition):
             converter = definition.value
             modifiers = {}
 
-        converter = ParserHelper.from_string(converter)
+        converter = ParserHelper.from_string(converter, non_int_type)
 
         if not any(_is_dim(key) for key in converter.keys()):
             is_base = False
@@ -292,11 +294,11 @@ class DimensionDefinition(Definition):
         super().__init__(name, symbol, aliases, converter=None)
 
     @classmethod
-    def from_string(cls, definition):
+    def from_string(cls, definition, non_int_type=float):
         if isinstance(definition, str):
             definition = PreprocessedDefinition.from_string(definition)
 
-        converter = ParserHelper.from_string(definition.value)
+        converter = ParserHelper.from_string(definition.value, non_int_type)
 
         if not converter:
             is_base = True
@@ -308,7 +310,7 @@ class DimensionDefinition(Definition):
                 "Derived dimensions must only be referenced "
                 "to dimensions."
             )
-        reference = UnitsContainer(converter)
+        reference = UnitsContainer(converter, non_int_type=non_int_type)
 
         return cls(
             definition.name,
@@ -333,7 +335,7 @@ class AliasDefinition(Definition):
         super().__init__(name=name, symbol=None, aliases=aliases, converter=None)
 
     @classmethod
-    def from_string(cls, definition):
+    def from_string(cls, definition, non_int_type=float):
 
         if isinstance(definition, str):
             definition = PreprocessedDefinition.from_string(definition)
diff --git a/pint/quantity.py b/pint/quantity.py
index c8fcde3..d434a0f 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -146,6 +146,10 @@ class Quantity(PrettyIPython, SharedRegistryObject):
     def force_ndarray_like(self):
         return self._REGISTRY.force_ndarray_like
 
+    @property
+    def UnitsContainer(self):
+        return self._REGISTRY.UnitsContainer
+
     def __reduce__(self):
         """Allow pickling quantities. Since UnitRegistries are not pickled, upon
         unpickling the new object is always attached to the application registry.
@@ -175,7 +179,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                 inst._magnitude = _to_magnitude(
                     value, inst.force_ndarray, inst.force_ndarray_like
                 )
-                inst._units = UnitsContainer()
+                inst._units = inst.UnitsContainer()
         elif isinstance(units, (UnitsContainer, UnitDefinition)):
             inst = SharedRegistryObject.__new__(cls)
             inst._magnitude = _to_magnitude(
@@ -486,7 +490,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
 
     @classmethod
     def from_tuple(cls, tup):
-        return cls(tup[0], UnitsContainer(tup[1]))
+        return cls(tup[0], cls._REGISTRY.UnitsContainer(tup[1]))
 
     def to_tuple(self):
         return self.m, tuple(self._units.items())
@@ -790,7 +794,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                 # the operation.
                 self._magnitude = op(self._magnitude, other_magnitude)
             elif self.dimensionless:
-                self.ito(UnitsContainer())
+                self.ito(self.UnitsContainer())
                 self._magnitude = op(self._magnitude, other_magnitude)
             else:
                 raise DimensionalityError(self._units, "dimensionless")
@@ -900,7 +904,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                     _to_magnitude(other, self.force_ndarray, self.force_ndarray_like),
                 )
             elif self.dimensionless:
-                units = UnitsContainer()
+                units = self.UnitsContainer()
                 magnitude = op(
                     self.to(units)._magnitude,
                     _to_magnitude(other, self.force_ndarray, self.force_ndarray_like),
@@ -1065,7 +1069,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             except TypeError:
                 return NotImplemented
             self._magnitude = magnitude_op(self._magnitude, other_magnitude)
-            self._units = units_op(self._units, UnitsContainer())
+            self._units = units_op(self._units, self.UnitsContainer())
             return self
 
         if isinstance(other, self._REGISTRY.Unit):
@@ -1136,7 +1140,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                 return NotImplemented
 
             magnitude = magnitude_op(self._magnitude, other_magnitude)
-            units = units_op(self._units, UnitsContainer())
+            units = units_op(self._units, self.UnitsContainer())
 
             return self.__class__(magnitude, units)
 
@@ -1220,7 +1224,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             self._magnitude = self.to("")._magnitude // other
         else:
             raise DimensionalityError(self._units, "dimensionless")
-        self._units = UnitsContainer({})
+        self._units = self.UnitsContainer({})
         return self
 
     @check_implemented
@@ -1231,7 +1235,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             magnitude = self.to("")._magnitude // other
         else:
             raise DimensionalityError(self._units, "dimensionless")
-        return self.__class__(magnitude, UnitsContainer({}))
+        return self.__class__(magnitude, self.UnitsContainer({}))
 
     @check_implemented
     def __rfloordiv__(self, other):
@@ -1241,19 +1245,19 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             magnitude = other // self.to("")._magnitude
         else:
             raise DimensionalityError(self._units, "dimensionless")
-        return self.__class__(magnitude, UnitsContainer({}))
+        return self.__class__(magnitude, self.UnitsContainer({}))
 
     @check_implemented
     def __imod__(self, other):
         if not self._check(other):
-            other = self.__class__(other, UnitsContainer({}))
+            other = self.__class__(other, self.UnitsContainer({}))
         self._magnitude %= other.to(self._units)._magnitude
         return self
 
     @check_implemented
     def __mod__(self, other):
         if not self._check(other):
-            other = self.__class__(other, UnitsContainer({}))
+            other = self.__class__(other, self.UnitsContainer({}))
         magnitude = self._magnitude % other.to(self._units)._magnitude
         return self.__class__(magnitude, self._units)
 
@@ -1264,16 +1268,19 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             return self.__class__(magnitude, other._units)
         elif self.dimensionless:
             magnitude = other % self.to("")._magnitude
-            return self.__class__(magnitude, UnitsContainer({}))
+            return self.__class__(magnitude, self.UnitsContainer({}))
         else:
             raise DimensionalityError(self._units, "dimensionless")
 
     @check_implemented
     def __divmod__(self, other):
         if not self._check(other):
-            other = self.__class__(other, UnitsContainer({}))
+            other = self.__class__(other, self.UnitsContainer({}))
         q, r = divmod(self._magnitude, other.to(self._units)._magnitude)
-        return (self.__class__(q, UnitsContainer({})), self.__class__(r, self._units))
+        return (
+            self.__class__(q, self.UnitsContainer({})),
+            self.__class__(r, self._units),
+        )
 
     @check_implemented
     def __rdivmod__(self, other):
@@ -1282,10 +1289,10 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             unit = other._units
         elif self.dimensionless:
             q, r = divmod(other, self.to("")._magnitude)
-            unit = UnitsContainer({})
+            unit = self.UnitsContainer({})
         else:
             raise DimensionalityError(self._units, "dimensionless")
-        return (self.__class__(q, UnitsContainer({})), self.__class__(r, unit))
+        return (self.__class__(q, self.UnitsContainer({})), self.__class__(r, unit))
 
     @check_implemented
     def __ipow__(self, other):
@@ -1326,7 +1333,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             if other == 1:
                 return self
             elif other == 0:
-                self._units = UnitsContainer()
+                self._units = self.UnitsContainer()
             else:
                 if not self._is_multiplicative:
                     if self._REGISTRY.autoconvert_offset_to_baseunit:
@@ -1383,7 +1390,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                 return self
             elif other == 0:
                 exponent = 0
-                units = UnitsContainer()
+                units = self.UnitsContainer()
             else:
                 if not self._is_multiplicative:
                     if self._REGISTRY.autoconvert_offset_to_baseunit:
@@ -1454,7 +1461,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                         raise OffsetUnitCalculusError(self._units)
 
             return self.dimensionless and eq(
-                self._convert_magnitude(UnitsContainer()), other, False
+                self._convert_magnitude(self.UnitsContainer()), other, False
             )
 
         if eq(self._magnitude, 0, True) and eq(other._magnitude, 0, True):
@@ -1483,7 +1490,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
     def compare(self, other, op):
         if not isinstance(other, self.__class__):
             if self.dimensionless:
-                return op(self._convert_magnitude_not_inplace(UnitsContainer()), other)
+                return op(
+                    self._convert_magnitude_not_inplace(self.UnitsContainer()), other
+                )
             elif eq(other, 0, True):
                 # Handle the special case in which we compare to zero
                 # (or an array of zeros)
diff --git a/pint/registry.py b/pint/registry.py
index c222346..99873ed 100644
--- a/pint/registry.py
+++ b/pint/registry.py
@@ -165,6 +165,8 @@ class BaseRegistry(metaclass=RegistryMeta):
         string
     fmt_locale :
         locale identifier string, used in `format_babel`
+    non_int_type : type
+        numerical type used for non integer values. (Default: float)
 
     """
 
@@ -180,6 +182,7 @@ class BaseRegistry(metaclass=RegistryMeta):
     _dir = [
         "Quantity",
         "Unit",
+        "UnitsContainer",
         "Measurement",
         "define",
         "load_definitions",
@@ -203,6 +206,7 @@ class BaseRegistry(metaclass=RegistryMeta):
         auto_reduce_dimensions=False,
         preprocessors=None,
         fmt_locale=None,
+        non_int_type=float,
     ):
         self._register_parsers()
         self._init_dynamic_classes()
@@ -221,6 +225,9 @@ class BaseRegistry(metaclass=RegistryMeta):
         #: Default locale identifier string, used when calling format_babel without explicit locale.
         self.set_fmt_locale(fmt_locale)
 
+        #: Numerical type used for non integer values.
+        self.non_int_type = non_int_type
+
         #: Map between name (string) and value (string) of defaults stored in the
         #: definitions file.
         self._defaults = {}
@@ -322,6 +329,9 @@ class BaseRegistry(metaclass=RegistryMeta):
 
         self.fmt_locale = loc
 
+    def UnitsContainer(self, *args, **kwargs):
+        return UnitsContainer(*args, non_int_type=self.non_int_type, **kwargs)
+
     @property
     def default_format(self):
         """Default formatting string for quantities."""
@@ -343,7 +353,7 @@ class BaseRegistry(metaclass=RegistryMeta):
 
         if isinstance(definition, str):
             for line in definition.split("\n"):
-                self._define(Definition.from_string(line))
+                self._define(Definition.from_string(line, self.non_int_type))
         else:
             self._define(definition)
 
@@ -414,7 +424,7 @@ class BaseRegistry(metaclass=RegistryMeta):
                 "delta_" + alias for alias in definition.aliases
             )
 
-            d_reference = UnitsContainer(
+            d_reference = self.UnitsContainer(
                 {ref: value for ref, value in definition.reference.items()}
             )
 
@@ -564,7 +574,7 @@ class BaseRegistry(metaclass=RegistryMeta):
                         raise ex
             else:
                 try:
-                    self.define(Definition.from_string(line))
+                    self.define(Definition.from_string(line, self.non_int_type))
                 except DefinitionSyntaxError as ex:
                     if ex.lineno is None:
                         ex.lineno = no
@@ -592,7 +602,7 @@ class BaseRegistry(metaclass=RegistryMeta):
                     prefix, base_name = "", unit_name
 
                 try:
-                    uc = ParserHelper.from_word(base_name)
+                    uc = ParserHelper.from_word(base_name, self.non_int_type)
 
                     bu = self._get_root_units(uc)
                     di = self._get_dimensionality(uc)
@@ -638,7 +648,11 @@ class BaseRegistry(metaclass=RegistryMeta):
             symbol = self.get_symbol(name)
             prefix_def = self._prefixes[prefix]
             self._units[name] = UnitDefinition(
-                name, symbol, (), prefix_def.converter, UnitsContainer({unit_name: 1})
+                name,
+                symbol,
+                (),
+                prefix_def.converter,
+                self.UnitsContainer({unit_name: 1}),
             )
             return prefix + unit_name
 
@@ -668,6 +682,9 @@ class BaseRegistry(metaclass=RegistryMeta):
         """Convert unit or dict of units or dimensions to a dict of base dimensions
         dimensions
         """
+
+        # TODO: This should be to_units_container(input_units, self)
+        # but this tries to reparse and fail for dimensions.
         input_units = to_units_container(input_units)
 
         return self._get_dimensionality(input_units)
@@ -676,7 +693,7 @@ class BaseRegistry(metaclass=RegistryMeta):
         """Convert a UnitsContainer to base dimensions.
         """
         if not input_units:
-            return UnitsContainer()
+            return self.UnitsContainer()
 
         cache = self._cache.dimensionality
 
@@ -691,7 +708,7 @@ class BaseRegistry(metaclass=RegistryMeta):
         if "[]" in accumulator:
             del accumulator["[]"]
 
-        dims = UnitsContainer({k: v for k, v in accumulator.items() if v != 0})
+        dims = self.UnitsContainer({k: v for k, v in accumulator.items() if v != 0})
 
         cache[input_units] = dims
 
@@ -762,7 +779,7 @@ class BaseRegistry(metaclass=RegistryMeta):
             multiplicative factor, base units
 
         """
-        input_units = to_units_container(input_units)
+        input_units = to_units_container(input_units, self)
 
         f, units = self._get_root_units(input_units, check_nonmult)
 
@@ -790,7 +807,7 @@ class BaseRegistry(metaclass=RegistryMeta):
 
         """
         if not input_units:
-            return 1, UnitsContainer()
+            return 1, self.UnitsContainer()
 
         cache = self._cache.root_units
         try:
@@ -802,7 +819,9 @@ class BaseRegistry(metaclass=RegistryMeta):
         self._get_root_units_recurse(input_units, 1, accumulators)
 
         factor = accumulators[0]
-        units = UnitsContainer({k: v for k, v in accumulators[1].items() if v != 0})
+        units = self.UnitsContainer(
+            {k: v for k, v in accumulators[1].items() if v != 0}
+        )
 
         # Check if any of the final units is non multiplicative and return None instead.
         if check_nonmult:
@@ -1078,12 +1097,12 @@ class BaseRegistry(metaclass=RegistryMeta):
                 pass
 
         if not input_string:
-            return UnitsContainer()
+            return self.UnitsContainer()
 
         # Sanitize input_string with whitespaces.
         input_string = input_string.strip()
 
-        units = ParserHelper.from_string(input_string)
+        units = ParserHelper.from_string(input_string, self.non_int_type)
         if units.scale != 1:
             raise ValueError("Unit expression cannot have a scaling factor.")
 
@@ -1100,14 +1119,25 @@ class BaseRegistry(metaclass=RegistryMeta):
                     cname = "delta_" + cname
             ret[cname] = value
 
-        ret = UnitsContainer(ret)
+        ret = self.UnitsContainer(ret)
 
         if as_delta:
             cache[input_string] = ret
 
         return ret
 
-    def _eval_token(self, token, case_sensitive=True, use_decimal=False, **values):
+    def _eval_token(
+        self, token, case_sensitive=True, use_decimal=False, **values,
+    ):
+
+        # TODO: remove this code when use_decimal is deprecated
+        if use_decimal:
+            raise DeprecationWarning(
+                "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n"
+                ">>> from decimal import Decimal\n"
+                ">>> ureg = UnitRegistry(non_int_type=Decimal)"
+            )
+
         token_type = token[0]
         token_text = token[1]
         if token_type == NAME:
@@ -1118,12 +1148,12 @@ class BaseRegistry(metaclass=RegistryMeta):
             else:
                 return self.Quantity(
                     1,
-                    UnitsContainer(
+                    self.UnitsContainer(
                         {self.get_name(token_text, case_sensitive=case_sensitive): 1}
                     ),
                 )
         elif token_type == NUMBER:
-            return ParserHelper.eval_token(token, use_decimal=use_decimal)
+            return ParserHelper.eval_token(token, non_int_type=self.non_int_type)
         else:
             raise Exception("unknown token type")
 
@@ -1184,7 +1214,7 @@ class BaseRegistry(metaclass=RegistryMeta):
         return results
 
     def parse_expression(
-        self, input_string, case_sensitive=True, use_decimal=False, **values
+        self, input_string, case_sensitive=True, use_decimal=False, **values,
     ):
         """Parse a mathematical expression including units and return a quantity object.
 
@@ -1207,6 +1237,14 @@ class BaseRegistry(metaclass=RegistryMeta):
 
         """
 
+        # TODO: remove this code when use_decimal is deprecated
+        if use_decimal:
+            raise DeprecationWarning(
+                "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n"
+                ">>> from decimal import Decimal\n"
+                ">>> ureg = UnitRegistry(non_int_type=Decimal)"
+            )
+
         if not input_string:
             return self.Quantity(1)
 
@@ -1216,9 +1254,7 @@ class BaseRegistry(metaclass=RegistryMeta):
         gen = tokenizer(input_string)
 
         return build_eval_tree(gen).evaluate(
-            lambda x: self._eval_token(
-                x, case_sensitive=case_sensitive, use_decimal=use_decimal, **values
-            )
+            lambda x: self._eval_token(x, case_sensitive=case_sensitive, **values)
         )
 
     __call__ = parse_expression
@@ -1440,7 +1476,11 @@ class ContextRegistry(BaseRegistry):
     def _parse_context(self, ifile):
         try:
             self.add_context(
-                Context.from_lines(ifile.block_iter(), self.get_dimensionality)
+                Context.from_lines(
+                    ifile.block_iter(),
+                    self.get_dimensionality,
+                    non_int_type=self.non_int_type,
+                )
             )
         except KeyError as e:
             raise DefinitionSyntaxError(f"unknown dimension {e} in context")
@@ -1845,10 +1885,12 @@ class SystemRegistry(BaseRegistry):
         self._register_parser("@system", self._parse_system)
 
     def _parse_group(self, ifile):
-        self.Group.from_lines(ifile.block_iter(), self.define)
+        self.Group.from_lines(ifile.block_iter(), self.define, self.non_int_type)
 
     def _parse_system(self, ifile):
-        self.System.from_lines(ifile.block_iter(), self.get_root_units)
+        self.System.from_lines(
+            ifile.block_iter(), self.get_root_units, self.non_int_type
+        )
 
     def get_group(self, name, create_if_needed=True):
         """Return a Group.
@@ -1985,7 +2027,7 @@ class SystemRegistry(BaseRegistry):
         # as it has a UnitsContainer intermediate
         units = to_units_container(units, self)
 
-        destination_units = UnitsContainer()
+        destination_units = self.UnitsContainer()
 
         bu = self.get_system(system, False).base_units
 
@@ -1995,7 +2037,7 @@ class SystemRegistry(BaseRegistry):
                 new_unit = to_units_container(new_unit, self)
                 destination_units *= new_unit ** value
             else:
-                destination_units *= UnitsContainer({unit: value})
+                destination_units *= self.UnitsContainer({unit: value})
 
         base_factor = self.convert(factor, units, destination_units)
 
@@ -2069,6 +2111,7 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry):
         auto_reduce_dimensions=False,
         preprocessors=None,
         fmt_locale=None,
+        non_int_type=float,
     ):
 
         super().__init__(
@@ -2082,6 +2125,7 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry):
             auto_reduce_dimensions=auto_reduce_dimensions,
             preprocessors=preprocessors,
             fmt_locale=fmt_locale,
+            non_int_type=non_int_type,
         )
 
     def pi_theorem(self, quantities):
diff --git a/pint/systems.py b/pint/systems.py
index 05d2b6f..2a8f802 100644
--- a/pint/systems.py
+++ b/pint/systems.py
@@ -174,7 +174,7 @@ class Group(SharedRegistryObject):
         self.invalidate_members()
 
     @classmethod
-    def from_lines(cls, lines, define_func):
+    def from_lines(cls, lines, define_func, non_int_type=float):
         """Return a Group object parsing an iterable of lines.
 
         Parameters
@@ -208,7 +208,7 @@ class Group(SharedRegistryObject):
         for lineno, line in lines:
             if "=" in line:
                 # Is a definition
-                definition = Definition.from_string(line)
+                definition = Definition.from_string(line, non_int_type=non_int_type)
                 if not isinstance(definition, UnitDefinition):
                     raise DefinitionSyntaxError(
                         "Only UnitDefinition are valid inside _used_groups, not "
@@ -357,7 +357,7 @@ class System(SharedRegistryObject):
         return self.name
 
     @classmethod
-    def from_lines(cls, lines, get_root_func):
+    def from_lines(cls, lines, get_root_func, non_int_type=float):
         lines = SourceIterator(lines)
 
         lineno, header = next(lines)
@@ -399,7 +399,9 @@ class System(SharedRegistryObject):
                     )
 
                 # Here we find new_unit expanded in terms of root_units
-                new_unit_expanded = to_units_container(get_root_func(new_unit)[1])
+                new_unit_expanded = to_units_container(
+                    get_root_func(new_unit)[1], cls._REGISTRY
+                )
 
                 # We require that the old unit is present in the new_unit expanded
                 if old_unit not in new_unit_expanded:
diff --git a/pint/util.py b/pint/util.py
index 4c62b6a..aeebff1 100644
--- a/pint/util.py
+++ b/pint/util.py
@@ -13,9 +13,8 @@ import math
 import operator
 import re
 from collections.abc import Mapping
-from decimal import Decimal
 from fractions import Fraction
-from functools import lru_cache
+from functools import lru_cache, partial
 from logging import NullHandler
 from numbers import Number
 from token import NAME, NUMBER
@@ -178,14 +177,16 @@ def pi_theorem(quantities, registry=None):
 
     if registry is None:
         getdim = lambda x: x
+        non_int_type = float
     else:
         getdim = registry.get_dimensionality
+        non_int_type = registry.non_int_type
 
     for name, value in quantities.items():
         if isinstance(value, str):
-            value = ParserHelper.from_string(value)
+            value = ParserHelper.from_string(value, non_int_type=non_int_type)
         if isinstance(value, dict):
-            dims = getdim(UnitsContainer(value))
+            dims = getdim(registry.UnitsContainer(value))
         elif not hasattr(value, "dimensionality"):
             dims = getdim(value)
         else:
@@ -318,9 +319,21 @@ class UnitsContainer(Mapping):
 
     """
 
-    __slots__ = ("_d", "_hash")
+    __slots__ = ("_d", "_hash", "_one", "_non_int_type")
 
     def __init__(self, *args, **kwargs):
+        if args and isinstance(args[0], UnitsContainer):
+            default_non_int_type = args[0]._non_int_type
+        else:
+            default_non_int_type = float
+
+        self._non_int_type = kwargs.pop("non_int_type", default_non_int_type)
+
+        if self._non_int_type is float:
+            self._one = 1
+        else:
+            self._one = self._non_int_type("1")
+
         d = udict(*args, **kwargs)
         self._d = d
         for key, value in d.items():
@@ -328,8 +341,8 @@ class UnitsContainer(Mapping):
                 raise TypeError("key must be a str, not {}".format(type(key)))
             if not isinstance(value, Number):
                 raise TypeError("value must be a number, not {}".format(type(value)))
-            if not isinstance(value, float):
-                d[key] = float(value)
+            if not isinstance(value, int) and not isinstance(value, self._non_int_type):
+                d[key] = self._non_int_type(value)
         self._hash = None
 
     def copy(self):
@@ -411,7 +424,7 @@ class UnitsContainer(Mapping):
             other = other._d
 
         elif isinstance(other, str):
-            other = ParserHelper.from_string(other)
+            other = ParserHelper.from_string(other, self._non_int_type)
             other = other._d
 
         return dict.__eq__(self._d, other)
@@ -436,6 +449,8 @@ class UnitsContainer(Mapping):
         out = object.__new__(self.__class__)
         out._d = self._d.copy()
         out._hash = self._hash
+        out._non_int_type = self._non_int_type
+        out._one = self._one
         return out
 
     def __mul__(self, other):
@@ -512,7 +527,7 @@ class ParserHelper(UnitsContainer):
         self.scale = scale
 
     @classmethod
-    def from_word(cls, input_word):
+    def from_word(cls, input_word, non_int_type=float):
         """Creates a ParserHelper object with a single variable with exponent one.
 
         Equivalent to: ParserHelper({'word': 1})
@@ -526,27 +541,41 @@ class ParserHelper(UnitsContainer):
         -------
 
         """
-        return cls(1, [(input_word, 1)])
+        if non_int_type is float:
+            return cls(1, [(input_word, 1)], non_int_type=non_int_type)
+        else:
+            ONE = non_int_type("1.0")
+            return cls(ONE, [(input_word, ONE)], non_int_type=non_int_type)
 
     @classmethod
-    def eval_token(cls, token, use_decimal=False):
+    def eval_token(cls, token, use_decimal=False, non_int_type=float):
+
+        # TODO: remove this code when use_decimal is deprecated
+        if use_decimal:
+            raise DeprecationWarning(
+                "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n"
+                ">>> from decimal import Decimal\n"
+                ">>> ureg = UnitRegistry(non_int_type=Decimal)"
+            )
+
         token_type = token.type
         token_text = token.string
         if token_type == NUMBER:
-            try:
-                return int(token_text)
-            except ValueError:
-                if use_decimal:
-                    return Decimal(token_text)
-                return float(token_text)
+            if non_int_type is float:
+                try:
+                    return int(token_text)
+                except ValueError:
+                    return float(token_text)
+            else:
+                return non_int_type(token_text)
         elif token_type == NAME:
-            return ParserHelper.from_word(token_text)
+            return ParserHelper.from_word(token_text, non_int_type=non_int_type)
         else:
             raise Exception("unknown token type")
 
     @classmethod
     @lru_cache()
-    def from_string(cls, input_string):
+    def from_string(cls, input_string, non_int_type=float):
         """Parse linear expression mathematical units and return a quantity object.
 
         Parameters
@@ -559,7 +588,7 @@ class ParserHelper(UnitsContainer):
 
         """
         if not input_string:
-            return cls()
+            return cls(non_int_type=non_int_type)
 
         input_string = string_preprocessor(input_string)
         if "[" in input_string:
@@ -571,10 +600,12 @@ class ParserHelper(UnitsContainer):
             reps = False
 
         gen = tokenizer(input_string)
-        ret = build_eval_tree(gen).evaluate(cls.eval_token)
+        ret = build_eval_tree(gen).evaluate(
+            partial(cls.eval_token, non_int_type=non_int_type)
+        )
 
         if isinstance(ret, Number):
-            return ParserHelper(ret)
+            return ParserHelper(ret, non_int_type=non_int_type)
 
         if reps:
             ret = ParserHelper(
@@ -583,6 +614,7 @@ class ParserHelper(UnitsContainer):
                     key.replace("__obra__", "[").replace("__cbra__", "]"): value
                     for key, value in ret.items()
                 },
+                non_int_type=non_int_type,
             )
 
         for k in list(ret):
@@ -610,7 +642,7 @@ class ParserHelper(UnitsContainer):
         if isinstance(other, ParserHelper):
             return self.scale == other.scale and super().__eq__(other)
         elif isinstance(other, str):
-            return self == ParserHelper.from_string(other)
+            return self == ParserHelper.from_string(other, self._non_int_type)
         elif isinstance(other, Number):
             return self.scale == other and not len(self._d)
         else:
@@ -626,7 +658,7 @@ class ParserHelper(UnitsContainer):
             for key in keys:
                 del d[key]
 
-        return self.__class__(self.scale, d)
+        return self.__class__(self.scale, d, non_int_type=self._non_int_type)
 
     def __str__(self):
         tmp = "{%s}" % ", ".join(
@@ -642,7 +674,7 @@ class ParserHelper(UnitsContainer):
 
     def __mul__(self, other):
         if isinstance(other, str):
-            new = self.add(other, 1)
+            new = self.add(other, self._one)
         elif isinstance(other, Number):
             new = self.copy()
             new.scale *= other
@@ -659,7 +691,7 @@ class ParserHelper(UnitsContainer):
         d = self._d.copy()
         for key in self._d:
             d[key] *= other
-        return self.__class__(self.scale ** other, d)
+        return self.__class__(self.scale ** other, d, non_int_type=self._non_int_type)
 
     def __truediv__(self, other):
         if isinstance(other, str):
@@ -679,7 +711,7 @@ class ParserHelper(UnitsContainer):
     def __rtruediv__(self, other):
         new = self.__pow__(-1)
         if isinstance(other, str):
-            new = new.add(other, 1)
+            new = new.add(other, self._one)
         elif isinstance(other, Number):
             new.scale *= other
         elif isinstance(other, self.__class__):
@@ -833,7 +865,10 @@ def to_units_container(unit_like, registry=None):
         else:
             return ParserHelper.from_string(unit_like)
     elif dict in mro:
-        return UnitsContainer(unit_like)
+        if registry:
+            return registry.UnitsContainer(unit_like)
+        else:
+            return UnitsContainer(unit_like)
 
 
 def infer_base_unit(q):


Test patch:
diff --git a/pint/testsuite/__init__.py b/pint/testsuite/__init__.py
index 1614f59..4aa3e32 100644
--- a/pint/testsuite/__init__.py
+++ b/pint/testsuite/__init__.py
@@ -106,7 +106,13 @@ class QuantityTestCase(BaseTestCase):
 
     def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None):
         if msg is None:
-            msg = "Comparing %r and %r. " % (first, second)
+            try:
+                msg = "Comparing %r and %r. " % (first, second)
+            except TypeError:
+                try:
+                    msg = "Comparing %s and %s. " % (first, second)
+                except Exception:
+                    msg = "Comparing"
 
         m1, m2 = self._get_comparable_magnitudes(first, second, msg)
 
diff --git a/pint/testsuite/test_non_int.py b/pint/testsuite/test_non_int.py
new file mode 100644
index 0000000..47409f2
--- /dev/null
+++ b/pint/testsuite/test_non_int.py
@@ -0,0 +1,1076 @@
+import copy
+import math
+import operator as op
+from decimal import Decimal
+from fractions import Fraction
+
+from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry
+from pint.testsuite import QuantityTestCase
+from pint.testsuite.parameterized import ParameterizedTestMixin as ParameterizedTestCase
+from pint.unit import UnitsContainer
+
+
+class FakeWrapper:
+    # Used in test_upcast_type_rejection_on_creation
+    def __init__(self, q):
+        self.q = q
+
+
+class NonIntTypeQuantityTestCase(QuantityTestCase):
+
+    NON_INT_TYPE = None
+
+    @classmethod
+    def setUpClass(cls):
+        cls.ureg = UnitRegistry(
+            force_ndarray=cls.FORCE_NDARRAY, non_int_type=cls.NON_INT_TYPE
+        )
+        cls.Q_ = cls.ureg.Quantity
+        cls.U_ = cls.ureg.Unit
+
+    def assertQuantityAlmostEqual(
+        self, first, second, rtol="1e-07", atol="0", msg=None
+    ):
+
+        if isinstance(first, self.Q_):
+            self.assertIsInstance(first.m, (self.NON_INT_TYPE, int))
+        else:
+            self.assertIsInstance(first, (self.NON_INT_TYPE, int))
+
+        if isinstance(second, self.Q_):
+            self.assertIsInstance(second.m, (self.NON_INT_TYPE, int))
+        else:
+            self.assertIsInstance(second, (self.NON_INT_TYPE, int))
+        super().assertQuantityAlmostEqual(
+            first, second, self.NON_INT_TYPE(rtol), self.NON_INT_TYPE(atol), msg
+        )
+
+    def QP_(self, value, units):
+        self.assertIsInstance(value, str)
+        return self.Q_(self.NON_INT_TYPE(value), units)
+
+
+class _TestBasic:
+    def test_quantity_creation(self):
+
+        value = self.NON_INT_TYPE("4.2")
+
+        for args in (
+            (value, "meter"),
+            (value, UnitsContainer(meter=1)),
+            (value, self.ureg.meter),
+            ("4.2*meter",),
+            ("4.2/meter**(-1)",),
+            (self.Q_(value, "meter"),),
+        ):
+            x = self.Q_(*args)
+            self.assertEqual(x.magnitude, value)
+            self.assertEqual(x.units, self.ureg.UnitsContainer(meter=1))
+
+        x = self.Q_(value, UnitsContainer(length=1))
+        y = self.Q_(x)
+        self.assertEqual(x.magnitude, y.magnitude)
+        self.assertEqual(x.units, y.units)
+        self.assertIsNot(x, y)
+
+        x = self.Q_(value, None)
+        self.assertEqual(x.magnitude, value)
+        self.assertEqual(x.units, UnitsContainer())
+
+        with self.capture_log() as buffer:
+            self.assertEqual(
+                value * self.ureg.meter,
+                self.Q_(value, self.NON_INT_TYPE("2") * self.ureg.meter),
+            )
+            self.assertEqual(len(buffer), 1)
+
+    def test_quantity_comparison(self):
+        x = self.QP_("4.2", "meter")
+        y = self.QP_("4.2", "meter")
+        z = self.QP_("5", "meter")
+        j = self.QP_("5", "meter*meter")
+
+        # identity for single object
+        self.assertTrue(x == x)
+        self.assertFalse(x != x)
+
+        # identity for multiple objects with same value
+        self.assertTrue(x == y)
+        self.assertFalse(x != y)
+
+        self.assertTrue(x <= y)
+        self.assertTrue(x >= y)
+        self.assertFalse(x < y)
+        self.assertFalse(x > y)
+
+        self.assertFalse(x == z)
+        self.assertTrue(x != z)
+        self.assertTrue(x < z)
+
+        self.assertTrue(z != j)
+
+        self.assertNotEqual(z, j)
+        self.assertEqual(self.QP_("0", "meter"), self.QP_("0", "centimeter"))
+        self.assertNotEqual(self.QP_("0", "meter"), self.QP_("0", "second"))
+
+        self.assertLess(self.QP_("10", "meter"), self.QP_("5", "kilometer"))
+
+    def test_quantity_comparison_convert(self):
+        self.assertEqual(self.QP_("1000", "millimeter"), self.QP_("1", "meter"))
+        self.assertEqual(
+            self.QP_("1000", "millimeter/min"),
+            self.Q_(
+                self.NON_INT_TYPE("1000") / self.NON_INT_TYPE("60"), "millimeter/s"
+            ),
+        )
+
+    def test_quantity_hash(self):
+        x = self.QP_("4.2", "meter")
+        x2 = self.QP_("4200", "millimeter")
+        y = self.QP_("2", "second")
+        z = self.QP_("0.5", "hertz")
+        self.assertEqual(hash(x), hash(x2))
+
+        # Dimensionless equality
+        self.assertEqual(hash(y * z), hash(1.0))
+
+        # Dimensionless equality from a different unit registry
+        ureg2 = UnitRegistry(force_ndarray=self.FORCE_NDARRAY)
+        y2 = ureg2.Quantity(self.NON_INT_TYPE("2"), "second")
+        z2 = ureg2.Quantity(self.NON_INT_TYPE("0.5"), "hertz")
+        self.assertEqual(hash(y * z), hash(y2 * z2))
+
+    def test_to_base_units(self):
+        x = self.Q_("1*inch")
+        self.assertQuantityAlmostEqual(x.to_base_units(), self.QP_("0.0254", "meter"))
+        x = self.Q_("1*inch*inch")
+        self.assertQuantityAlmostEqual(
+            x.to_base_units(),
+            self.Q_(
+                self.NON_INT_TYPE("0.0254") ** self.NON_INT_TYPE("2.0"), "meter*meter"
+            ),
+        )
+        x = self.Q_("1*inch/minute")
+        self.assertQuantityAlmostEqual(
+            x.to_base_units(),
+            self.Q_(
+                self.NON_INT_TYPE("0.0254") / self.NON_INT_TYPE("60"), "meter/second"
+            ),
+        )
+
+    def test_convert(self):
+        self.assertQuantityAlmostEqual(
+            self.Q_("2 inch").to("meter"),
+            self.Q_(self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254"), "meter"),
+        )
+        self.assertQuantityAlmostEqual(
+            self.Q_("2 meter").to("inch"),
+            self.Q_(self.NON_INT_TYPE("2") / self.NON_INT_TYPE("0.0254"), "inch"),
+        )
+        self.assertQuantityAlmostEqual(
+            self.Q_("2 sidereal_year").to("second"), self.QP_("63116297.5325", "second")
+        )
+        self.assertQuantityAlmostEqual(
+            self.Q_("2.54 centimeter/second").to("inch/second"),
+            self.Q_("1 inch/second"),
+        )
+        self.assertAlmostEqual(self.Q_("2.54 centimeter").to("inch").magnitude, 1)
+        self.assertAlmostEqual(self.Q_("2 second").to("millisecond").magnitude, 2000)
+
+    def test_convert_from(self):
+        x = self.Q_("2*inch")
+        meter = self.ureg.meter
+
+        # from quantity
+        self.assertQuantityAlmostEqual(
+            meter.from_(x),
+            self.Q_(self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254"), "meter"),
+        )
+        self.assertQuantityAlmostEqual(
+            meter.m_from(x), self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254")
+        )
+
+        # from unit
+        self.assertQuantityAlmostEqual(
+            meter.from_(self.ureg.inch), self.QP_("0.0254", "meter")
+        )
+        self.assertQuantityAlmostEqual(
+            meter.m_from(self.ureg.inch), self.NON_INT_TYPE("0.0254")
+        )
+
+        # from number
+        self.assertQuantityAlmostEqual(
+            meter.from_(2, strict=False), self.QP_("2", "meter")
+        )
+        self.assertQuantityAlmostEqual(
+            meter.m_from(self.NON_INT_TYPE("2"), strict=False), self.NON_INT_TYPE("2")
+        )
+
+        # from number (strict mode)
+        self.assertRaises(ValueError, meter.from_, self.NON_INT_TYPE("2"))
+        self.assertRaises(ValueError, meter.m_from, self.NON_INT_TYPE("2"))
+
+    def test_context_attr(self):
+        self.assertEqual(self.ureg.meter, self.QP_("1", "meter"))
+
+    def test_both_symbol(self):
+        self.assertEqual(self.QP_("2", "ms"), self.QP_("2", "millisecond"))
+        self.assertEqual(self.QP_("2", "cm"), self.QP_("2", "centimeter"))
+
+    def test_dimensionless_units(self):
+        twopi = self.NON_INT_TYPE("2") * self.ureg.pi
+        self.assertAlmostEqual(
+            self.QP_("360", "degree").to("radian").magnitude, twopi,
+        )
+        self.assertAlmostEqual(self.Q_(twopi, "radian"), self.QP_("360", "degree"))
+        self.assertEqual(self.QP_("1", "radian").dimensionality, UnitsContainer())
+        self.assertTrue(self.QP_("1", "radian").dimensionless)
+        self.assertFalse(self.QP_("1", "radian").unitless)
+
+        self.assertEqual(self.QP_("1", "meter") / self.QP_("1", "meter"), 1)
+        self.assertEqual((self.QP_("1", "meter") / self.QP_("1", "mm")).to(""), 1000)
+
+        self.assertEqual(self.Q_(10) // self.QP_("360", "degree"), 1)
+        self.assertEqual(self.QP_("400", "degree") // self.Q_(twopi), 1)
+        self.assertEqual(self.QP_("400", "degree") // twopi, 1)
+        self.assertEqual(7 // self.QP_("360", "degree"), 1)
+
+    def test_offset(self):
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "kelvin").to("kelvin"), self.QP_("0", "kelvin")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "degC").to("kelvin"), self.QP_("273.15", "kelvin")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "degF").to("kelvin"),
+            self.QP_("255.372222", "kelvin"),
+            rtol=0.01,
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "kelvin").to("kelvin"), self.QP_("100", "kelvin")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "degC").to("kelvin"), self.QP_("373.15", "kelvin")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "degF").to("kelvin"),
+            self.QP_("310.92777777", "kelvin"),
+            rtol=0.01,
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "kelvin").to("degC"), self.QP_("-273.15", "degC")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "kelvin").to("degC"), self.QP_("-173.15", "degC")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "kelvin").to("degF"), self.QP_("-459.67", "degF"), rtol=0.01
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "kelvin").to("degF"), self.QP_("-279.67", "degF"), rtol=0.01
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("32", "degF").to("degC"), self.QP_("0", "degC"), atol=0.01
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "degC").to("degF"), self.QP_("212", "degF"), atol=0.01
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("54", "degF").to("degC"), self.QP_("12.2222", "degC"), atol=0.01
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "degC").to("degF"), self.QP_("53.6", "degF"), atol=0.01
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "kelvin").to("degC"), self.QP_("-261.15", "degC"), atol=0.01
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "degC").to("kelvin"), self.QP_("285.15", "kelvin"), atol=0.01
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "kelvin").to("degR"), self.QP_("21.6", "degR"), atol=0.01
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "degR").to("kelvin"),
+            self.QP_("6.66666667", "kelvin"),
+            atol=0.01,
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "degC").to("degR"), self.QP_("513.27", "degR"), atol=0.01
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("12", "degR").to("degC"),
+            self.QP_("-266.483333", "degC"),
+            atol=0.01,
+        )
+
+    def test_offset_delta(self):
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "delta_degC").to("kelvin"), self.QP_("0", "kelvin")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("0", "delta_degF").to("kelvin"), self.QP_("0", "kelvin"), rtol=0.01
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "kelvin").to("delta_degC"), self.QP_("100", "delta_degC")
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "kelvin").to("delta_degF"),
+            self.QP_("180", "delta_degF"),
+            rtol=0.01,
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "delta_degF").to("kelvin"),
+            self.QP_("55.55555556", "kelvin"),
+            rtol=0.01,
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "delta_degC").to("delta_degF"),
+            self.QP_("180", "delta_degF"),
+            rtol=0.01,
+        )
+        self.assertQuantityAlmostEqual(
+            self.QP_("100", "delta_degF").to("delta_degC"),
+            self.QP_("55.55555556", "delta_degC"),
+            rtol=0.01,
+        )
+
+        self.assertQuantityAlmostEqual(
+            self.QP_("12.3", "delta_degC").to("delta_degF"),
+            self.QP_("22.14", "delta_degF"),
+            rtol=0.01,
+        )
+
+    def test_pickle(self):
+        import pickle
+
+        def pickle_test(q):
+            self.assertEqual(q, pickle.loads(pickle.dumps(q)))
+
+        pickle_test(self.QP_("32", ""))
+        pickle_test(self.QP_("2.4", ""))
+        pickle_test(self.QP_("32", "m/s"))
+        pickle_test(self.QP_("2.4", "m/s"))
+
+    def test_notiter(self):
+        # Verify that iter() crashes immediately, without needing to draw any
+        # element from it, if the magnitude isn't iterable
+        x = self.QP_("1", "m")
+        with self.assertRaises(TypeError):
+            iter(x)
+
+
+class _TestQuantityBasicMath:
+
+    FORCE_NDARRAY = False
+
+    def _test_inplace(self, operator, value1, value2, expected_result, unit=None):
+        if isinstance(value1, str):
+            value1 = self.Q_(value1)
+        if isinstance(value2, str):
+            value2 = self.Q_(value2)
+        if isinstance(expected_result, str):
+            expected_result = self.Q_(expected_result)
+
+        if unit is not None:
+            value1 = value1 * unit
+            value2 = value2 * unit
+            expected_result = expected_result * unit
+
+        value1 = copy.copy(value1)
+        value2 = copy.copy(value2)
+        id1 = id(value1)
+        id2 = id(value2)
+        value1 = operator(value1, value2)
+        value2_cpy = copy.copy(value2)
+        self.assertQuantityAlmostEqual(value1, expected_result)
+        self.assertEqual(id1, id(value1))
+        self.assertQuantityAlmostEqual(value2, value2_cpy)
+        self.assertEqual(id2, id(value2))
+
+    def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None):
+        if isinstance(value1, str):
+            value1 = self.Q_(value1)
+        if isinstance(value2, str):
+            value2 = self.Q_(value2)
+        if isinstance(expected_result, str):
+            expected_result = self.Q_(expected_result)
+
+        if unit is not None:
+            value1 = value1 * unit
+            value2 = value2 * unit
+            expected_result = expected_result * unit
+
+        id1 = id(value1)
+        id2 = id(value2)
+
+        value1_cpy = copy.copy(value1)
+        value2_cpy = copy.copy(value2)
+
+        result = operator(value1, value2)
+
+        self.assertQuantityAlmostEqual(expected_result, result)
+        self.assertQuantityAlmostEqual(value1, value1_cpy)
+        self.assertQuantityAlmostEqual(value2, value2_cpy)
+        self.assertNotEqual(id(result), id1)
+        self.assertNotEqual(id(result), id2)
+
+    def _test_quantity_add_sub(self, unit, func):
+        x = self.Q_(unit, "centimeter")
+        y = self.Q_(unit, "inch")
+        z = self.Q_(unit, "second")
+        a = self.Q_(unit, None)
+
+        func(op.add, x, x, self.Q_(unit + unit, "centimeter"))
+        func(
+            op.add, x, y, self.Q_(unit + self.NON_INT_TYPE("2.54") * unit, "centimeter")
+        )
+        func(
+            op.add,
+            y,
+            x,
+            self.Q_(unit + unit / (self.NON_INT_TYPE("2.54") * unit), "inch"),
+        )
+        func(op.add, a, unit, self.Q_(unit + unit, None))
+        self.assertRaises(DimensionalityError, op.add, self.NON_INT_TYPE("10"), x)
+        self.assertRaises(DimensionalityError, op.add, x, self.NON_INT_TYPE("10"))
+        self.assertRaises(DimensionalityError, op.add, x, z)
+
+        func(op.sub, x, x, self.Q_(unit - unit, "centimeter"))
+        func(
+            op.sub, x, y, self.Q_(unit - self.NON_INT_TYPE("2.54") * unit, "centimeter")
+        )
+        func(
+            op.sub,
+            y,
+            x,
+            self.Q_(unit - unit / (self.NON_INT_TYPE("2.54") * unit), "inch"),
+        )
+        func(op.sub, a, unit, self.Q_(unit - unit, None))
+        self.assertRaises(DimensionalityError, op.sub, self.NON_INT_TYPE("10"), x)
+        self.assertRaises(DimensionalityError, op.sub, x, self.NON_INT_TYPE("10"))
+        self.assertRaises(DimensionalityError, op.sub, x, z)
+
+    def _test_quantity_iadd_isub(self, unit, func):
+        x = self.Q_(unit, "centimeter")
+        y = self.Q_(unit, "inch")
+        z = self.Q_(unit, "second")
+        a = self.Q_(unit, None)
+
+        func(op.iadd, x, x, self.Q_(unit + unit, "centimeter"))
+        func(
+            op.iadd,
+            x,
+            y,
+            self.Q_(unit + self.NON_INT_TYPE("2.54") * unit, "centimeter"),
+        )
+        func(op.iadd, y, x, self.Q_(unit + unit / self.NON_INT_TYPE("2.54"), "inch"))
+        func(op.iadd, a, unit, self.Q_(unit + unit, None))
+        self.assertRaises(DimensionalityError, op.iadd, self.NON_INT_TYPE("10"), x)
+        self.assertRaises(DimensionalityError, op.iadd, x, self.NON_INT_TYPE("10"))
+        self.assertRaises(DimensionalityError, op.iadd, x, z)
+
+        func(op.isub, x, x, self.Q_(unit - unit, "centimeter"))
+        func(op.isub, x, y, self.Q_(unit - self.NON_INT_TYPE("2.54"), "centimeter"))
+        func(op.isub, y, x, self.Q_(unit - unit / self.NON_INT_TYPE("2.54"), "inch"))
+        func(op.isub, a, unit, self.Q_(unit - unit, None))
+        self.assertRaises(DimensionalityError, op.sub, self.NON_INT_TYPE("10"), x)
+        self.assertRaises(DimensionalityError, op.sub, x, self.NON_INT_TYPE("10"))
+        self.assertRaises(DimensionalityError, op.sub, x, z)
+
+    def _test_quantity_mul_div(self, unit, func):
+        func(op.mul, unit * self.NON_INT_TYPE("10"), "4.2*meter", "42*meter", unit)
+        func(op.mul, "4.2*meter", unit * self.NON_INT_TYPE("10"), "42*meter", unit)
+        func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit)
+        func(op.truediv, unit * self.NON_INT_TYPE("42"), "4.2*meter", "10/meter", unit)
+        func(
+            op.truediv, "4.2*meter", unit * self.NON_INT_TYPE("10"), "0.42*meter", unit
+        )
+        func(op.truediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)
+
+    def _test_quantity_imul_idiv(self, unit, func):
+        # func(op.imul, 10.0, '4.2*meter', '42*meter')
+        func(op.imul, "4.2*meter", self.NON_INT_TYPE("10"), "42*meter", unit)
+        func(op.imul, "4.2*meter", "10*inch", "42*meter*inch", unit)
+        # func(op.truediv, 42, '4.2*meter', '10/meter')
+        func(
+            op.itruediv, "4.2*meter", unit * self.NON_INT_TYPE("10"), "0.42*meter", unit
+        )
+        func(op.itruediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)
+
+    def _test_quantity_floordiv(self, unit, func):
+        a = self.Q_("10*meter")
+        b = self.Q_("3*second")
+        self.assertRaises(DimensionalityError, op.floordiv, a, b)
+        self.assertRaises(DimensionalityError, op.floordiv, self.NON_INT_TYPE("3"), b)
+        self.assertRaises(DimensionalityError, op.floordiv, a, self.NON_INT_TYPE("3"))
+        self.assertRaises(DimensionalityError, op.ifloordiv, a, b)
+        self.assertRaises(DimensionalityError, op.ifloordiv, self.NON_INT_TYPE("3"), b)
+        self.assertRaises(DimensionalityError, op.ifloordiv, a, self.NON_INT_TYPE("3"))
+        func(
+            op.floordiv,
+            unit * self.NON_INT_TYPE("10"),
+            "4.2*meter/meter",
+            self.NON_INT_TYPE("2"),
+            unit,
+        )
+        func(op.floordiv, "10*meter", "4.2*inch", self.NON_INT_TYPE("93"), unit)
+
+    def _test_quantity_mod(self, unit, func):
+        a = self.Q_("10*meter")
+        b = self.Q_("3*second")
+        self.assertRaises(DimensionalityError, op.mod, a, b)
+        self.assertRaises(DimensionalityError, op.mod, 3, b)
+        self.assertRaises(DimensionalityError, op.mod, a, 3)
+        self.assertRaises(DimensionalityError, op.imod, a, b)
+        self.assertRaises(DimensionalityError, op.imod, 3, b)
+        self.assertRaises(DimensionalityError, op.imod, a, 3)
+        func(
+            op.mod,
+            unit * self.NON_INT_TYPE("10"),
+            "4.2*meter/meter",
+            self.NON_INT_TYPE("1.6"),
+            unit,
+        )
+
+    def _test_quantity_ifloordiv(self, unit, func):
+        func(
+            op.ifloordiv,
+            self.NON_INT_TYPE("10"),
+            "4.2*meter/meter",
+            self.NON_INT_TYPE("2"),
+            unit,
+        )
+        func(op.ifloordiv, "10*meter", "4.2*inch", self.NON_INT_TYPE("93"), unit)
+
+    def _test_quantity_divmod_one(self, a, b):
+        if isinstance(a, str):
+            a = self.Q_(a)
+        if isinstance(b, str):
+            b = self.Q_(b)
+
+        q, r = divmod(a, b)
+        self.assertEqual(q, a // b)
+        self.assertEqual(r, a % b)
+        self.assertQuantityEqual(a, (q * b) + r)
+        self.assertEqual(q, math.floor(q))
+        if b > (0 * b):
+            self.assertTrue((0 * b) <= r < b)
+        else:
+            self.assertTrue((0 * b) >= r > b)
+        if isinstance(a, self.Q_):
+            self.assertEqual(r.units, a.units)
+        else:
+            self.assertTrue(r.unitless)
+        self.assertTrue(q.unitless)
+
+        copy_a = copy.copy(a)
+        a %= b
+        self.assertEqual(a, r)
+        copy_a //= b
+        self.assertEqual(copy_a, q)
+
+    def _test_quantity_divmod(self):
+        self._test_quantity_divmod_one("10*meter", "4.2*inch")
+
+        # Disabling these tests as it yields different results without Quantities
+        # >>> from decimal import Decimal as D
+        # >>> divmod(-D('100'), D('3'))
+        # (Decimal('-33'), Decimal('-1'))
+        # >>> divmod(-100, 3)
+        # (-34, 2)
+
+        # self._test_quantity_divmod_one("-10*meter", "4.2*inch")
+        # self._test_quantity_divmod_one("-10*meter", "-4.2*inch")
+        # self._test_quantity_divmod_one("10*meter", "-4.2*inch")
+
+        self._test_quantity_divmod_one("400*degree", "3")
+        self._test_quantity_divmod_one("4", "180 degree")
+        self._test_quantity_divmod_one(4, "180 degree")
+        self._test_quantity_divmod_one("20", 4)
+        self._test_quantity_divmod_one("300*degree", "100 degree")
+
+        a = self.Q_("10*meter")
+        b = self.Q_("3*second")
+        self.assertRaises(DimensionalityError, divmod, a, b)
+        self.assertRaises(DimensionalityError, divmod, 3, b)
+        self.assertRaises(DimensionalityError, divmod, a, 3)
+
+    def _test_numeric(self, unit, ifunc):
+        self._test_quantity_add_sub(unit, self._test_not_inplace)
+        self._test_quantity_iadd_isub(unit, ifunc)
+        self._test_quantity_mul_div(unit, self._test_not_inplace)
+        self._test_quantity_imul_idiv(unit, ifunc)
+        self._test_quantity_floordiv(unit, self._test_not_inplace)
+        self._test_quantity_mod(unit, self._test_not_inplace)
+        self._test_quantity_divmod()
+        # self._test_quantity_ifloordiv(unit, ifunc)
+
+    def test_quantity_abs_round(self):
+
+        value = self.NON_INT_TYPE("4.2")
+        x = self.Q_(-value, "meter")
+        y = self.Q_(value, "meter")
+
+        for fun in (abs, round, op.pos, op.neg):
+            zx = self.Q_(fun(x.magnitude), "meter")
+            zy = self.Q_(fun(y.magnitude), "meter")
+            rx = fun(x)
+            ry = fun(y)
+            self.assertEqual(rx, zx, "while testing {0}".format(fun))
+            self.assertEqual(ry, zy, "while testing {0}".format(fun))
+            self.assertIsNot(rx, zx, "while testing {0}".format(fun))
+            self.assertIsNot(ry, zy, "while testing {0}".format(fun))
+
+    def test_quantity_float_complex(self):
+        x = self.QP_("-4.2", None)
+        y = self.QP_("4.2", None)
+        z = self.QP_("1", "meter")
+        for fun in (float, complex):
+            self.assertEqual(fun(x), fun(x.magnitude))
+            self.assertEqual(fun(y), fun(y.magnitude))
+            self.assertRaises(DimensionalityError, fun, z)
+
+    def test_not_inplace(self):
+        self._test_numeric(self.NON_INT_TYPE("1.0"), self._test_not_inplace)
+
+
+class _TestOffsetUnitMath(ParameterizedTestCase):
+    def setup(self):
+        self.ureg.autoconvert_offset_to_baseunit = False
+        self.ureg.default_as_delta = True
+
+    additions = [
+        # --- input tuple -------------------- | -- expected result --
+        ((("100", "kelvin"), ("10", "kelvin")), ("110", "kelvin")),
+        ((("100", "kelvin"), ("10", "degC")), "error"),
+        ((("100", "kelvin"), ("10", "degF")), "error"),
+        ((("100", "kelvin"), ("10", "degR")), ("105.56", "kelvin")),
+        ((("100", "kelvin"), ("10", "delta_degC")), ("110", "kelvin")),
+        ((("100", "kelvin"), ("10", "delta_degF")), ("105.56", "kelvin")),
+        ((("100", "degC"), ("10", "kelvin")), "error"),
+        ((("100", "degC"), ("10", "degC")), "error"),
+        ((("100", "degC"), ("10", "degF")), "error"),
+        ((("100", "degC"), ("10", "degR")), "error"),
+        ((("100", "degC"), ("10", "delta_degC")), ("110", "degC")),
+        ((("100", "degC"), ("10", "delta_degF")), ("105.56", "degC")),
+        ((("100", "degF"), ("10", "kelvin")), "error"),
+        ((("100", "degF"), ("10", "degC")), "error"),
+        ((("100", "degF"), ("10", "degF")), "error"),
+        ((("100", "degF"), ("10", "degR")), "error"),
+        ((("100", "degF"), ("10", "delta_degC")), ("118", "degF")),
+        ((("100", "degF"), ("10", "delta_degF")), ("110", "degF")),
+        ((("100", "degR"), ("10", "kelvin")), ("118", "degR")),
+        ((("100", "degR"), ("10", "degC")), "error"),
+        ((("100", "degR"), ("10", "degF")), "error"),
+        ((("100", "degR"), ("10", "degR")), ("110", "degR")),
+        ((("100", "degR"), ("10", "delta_degC")), ("118", "degR")),
+        ((("100", "degR"), ("10", "delta_degF")), ("110", "degR")),
+        ((("100", "delta_degC"), ("10", "kelvin")), ("110", "kelvin")),
+        ((("100", "delta_degC"), ("10", "degC")), ("110", "degC")),
+        ((("100", "delta_degC"), ("10", "degF")), ("190", "degF")),
+        ((("100", "delta_degC"), ("10", "degR")), ("190", "degR")),
+        ((("100", "delta_degC"), ("10", "delta_degC")), ("110", "delta_degC")),
+        ((("100", "delta_degC"), ("10", "delta_degF")), ("105.56", "delta_degC")),
+        ((("100", "delta_degF"), ("10", "kelvin")), ("65.56", "kelvin")),
+        ((("100", "delta_degF"), ("10", "degC")), ("65.56", "degC")),
+        ((("100", "delta_degF"), ("10", "degF")), ("110", "degF")),
+        ((("100", "delta_degF"), ("10", "degR")), ("110", "degR")),
+        ((("100", "delta_degF"), ("10", "delta_degC")), ("118", "delta_degF")),
+        ((("100", "delta_degF"), ("10", "delta_degF")), ("110", "delta_degF")),
+    ]
+
+    @ParameterizedTestCase.parameterize(("input", "expected_output"), additions)
+    def test_addition(self, input_tuple, expected):
+        self.ureg.autoconvert_offset_to_baseunit = False
+        qin1, qin2 = input_tuple
+        q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+        # update input tuple with new values to have correct values on failure
+        input_tuple = q1, q2
+        if expected == "error":
+            self.assertRaises(OffsetUnitCalculusError, op.add, q1, q2)
+        else:
+            expected = self.QP_(*expected)
+            self.assertEqual(op.add(q1, q2).units, expected.units)
+            self.assertQuantityAlmostEqual(op.add(q1, q2), expected, atol="0.01")
+
+    subtractions = [
+        ((("100", "kelvin"), ("10", "kelvin")), ("90", "kelvin")),
+        ((("100", "kelvin"), ("10", "degC")), ("-183.15", "kelvin")),
+        ((("100", "kelvin"), ("10", "degF")), ("-160.93", "kelvin")),
+        ((("100", "kelvin"), ("10", "degR")), ("94.44", "kelvin")),
+        ((("100", "kelvin"), ("10", "delta_degC")), ("90", "kelvin")),
+        ((("100", "kelvin"), ("10", "delta_degF")), ("94.44", "kelvin")),
+        ((("100", "degC"), ("10", "kelvin")), ("363.15", "delta_degC")),
+        ((("100", "degC"), ("10", "degC")), ("90", "delta_degC")),
+        ((("100", "degC"), ("10", "degF")), ("112.22", "delta_degC")),
+        ((("100", "degC"), ("10", "degR")), ("367.59", "delta_degC")),
+        ((("100", "degC"), ("10", "delta_degC")), ("90", "degC")),
+        ((("100", "degC"), ("10", "delta_degF")), ("94.44", "degC")),
+        ((("100", "degF"), ("10", "kelvin")), ("541.67", "delta_degF")),
+        ((("100", "degF"), ("10", "degC")), ("50", "delta_degF")),
+        ((("100", "degF"), ("10", "degF")), ("90", "delta_degF")),
+        ((("100", "degF"), ("10", "degR")), ("549.67", "delta_degF")),
+        ((("100", "degF"), ("10", "delta_degC")), ("82", "degF")),
+        ((("100", "degF"), ("10", "delta_degF")), ("90", "degF")),
+        ((("100", "degR"), ("10", "kelvin")), ("82", "degR")),
+        ((("100", "degR"), ("10", "degC")), ("-409.67", "degR")),
+        ((("100", "degR"), ("10", "degF")), ("-369.67", "degR")),
+        ((("100", "degR"), ("10", "degR")), ("90", "degR")),
+        ((("100", "degR"), ("10", "delta_degC")), ("82", "degR")),
+        ((("100", "degR"), ("10", "delta_degF")), ("90", "degR")),
+        ((("100", "delta_degC"), ("10", "kelvin")), ("90", "kelvin")),
+        ((("100", "delta_degC"), ("10", "degC")), ("90", "degC")),
+        ((("100", "delta_degC"), ("10", "degF")), ("170", "degF")),
+        ((("100", "delta_degC"), ("10", "degR")), ("170", "degR")),
+        ((("100", "delta_degC"), ("10", "delta_degC")), ("90", "delta_degC")),
+        ((("100", "delta_degC"), ("10", "delta_degF")), ("94.44", "delta_degC")),
+        ((("100", "delta_degF"), ("10", "kelvin")), ("45.56", "kelvin")),
+        ((("100", "delta_degF"), ("10", "degC")), ("45.56", "degC")),
+        ((("100", "delta_degF"), ("10", "degF")), ("90", "degF")),
+        ((("100", "delta_degF"), ("10", "degR")), ("90", "degR")),
+        ((("100", "delta_degF"), ("10", "delta_degC")), ("82", "delta_degF")),
+        ((("100", "delta_degF"), ("10", "delta_degF")), ("90", "delta_degF")),
+    ]
+
+    @ParameterizedTestCase.parameterize(("input", "expected_output"), subtractions)
+    def test_subtraction(self, input_tuple, expected):
+        self.ureg.autoconvert_offset_to_baseunit = False
+        qin1, qin2 = input_tuple
+        q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+        input_tuple = q1, q2
+        if expected == "error":
+            self.assertRaises(OffsetUnitCalculusError, op.sub, q1, q2)
+        else:
+            expected = self.QP_(*expected)
+            self.assertEqual(op.sub(q1, q2).units, expected.units)
+            self.assertQuantityAlmostEqual(op.sub(q1, q2), expected, atol=0.01)
+
+    multiplications = [
+        ((("100", "kelvin"), ("10", "kelvin")), ("1000", "kelvin**2")),
+        ((("100", "kelvin"), ("10", "degC")), "error"),
+        ((("100", "kelvin"), ("10", "degF")), "error"),
+        ((("100", "kelvin"), ("10", "degR")), ("1000", "kelvin*degR")),
+        ((("100", "kelvin"), ("10", "delta_degC")), ("1000", "kelvin*delta_degC")),
+        ((("100", "kelvin"), ("10", "delta_degF")), ("1000", "kelvin*delta_degF")),
+        ((("100", "degC"), ("10", "kelvin")), "error"),
+        ((("100", "degC"), ("10", "degC")), "error"),
+        ((("100", "degC"), ("10", "degF")), "error"),
+        ((("100", "degC"), ("10", "degR")), "error"),
+        ((("100", "degC"), ("10", "delta_degC")), "error"),
+        ((("100", "degC"), ("10", "delta_degF")), "error"),
+        ((("100", "degF"), ("10", "kelvin")), "error"),
+        ((("100", "degF"), ("10", "degC")), "error"),
+        ((("100", "degF"), ("10", "degF")), "error"),
+        ((("100", "degF"), ("10", "degR")), "error"),
+        ((("100", "degF"), ("10", "delta_degC")), "error"),
+        ((("100", "degF"), ("10", "delta_degF")), "error"),
+        ((("100", "degR"), ("10", "kelvin")), ("1000", "degR*kelvin")),
+        ((("100", "degR"), ("10", "degC")), "error"),
+        ((("100", "degR"), ("10", "degF")), "error"),
+        ((("100", "degR"), ("10", "degR")), ("1000", "degR**2")),
+        ((("100", "degR"), ("10", "delta_degC")), ("1000", "degR*delta_degC")),
+        ((("100", "degR"), ("10", "delta_degF")), ("1000", "degR*delta_degF")),
+        ((("100", "delta_degC"), ("10", "kelvin")), ("1000", "delta_degC*kelvin")),
+        ((("100", "delta_degC"), ("10", "degC")), "error"),
+        ((("100", "delta_degC"), ("10", "degF")), "error"),
+        ((("100", "delta_degC"), ("10", "degR")), ("1000", "delta_degC*degR")),
+        ((("100", "delta_degC"), ("10", "delta_degC")), ("1000", "delta_degC**2")),
+        (
+            (("100", "delta_degC"), ("10", "delta_degF")),
+            ("1000", "delta_degC*delta_degF"),
+        ),
+        ((("100", "delta_degF"), ("10", "kelvin")), ("1000", "delta_degF*kelvin")),
+        ((("100", "delta_degF"), ("10", "degC")), "error"),
+        ((("100", "delta_degF"), ("10", "degF")), "error"),
+        ((("100", "delta_degF"), ("10", "degR")), ("1000", "delta_degF*degR")),
+        (
+            (("100", "delta_degF"), ("10", "delta_degC")),
+            ("1000", "delta_degF*delta_degC"),
+        ),
+        ((("100", "delta_degF"), ("10", "delta_degF")), ("1000", "delta_degF**2")),
+    ]
+
+    @ParameterizedTestCase.parameterize(("input", "expected_output"), multiplications)
+    def test_multiplication(self, input_tuple, expected):
+        self.ureg.autoconvert_offset_to_baseunit = False
+        qin1, qin2 = input_tuple
+        q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+        input_tuple = q1, q2
+        if expected == "error":
+            self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2)
+        else:
+            expected = self.QP_(*expected)
+            self.assertEqual(op.mul(q1, q2).units, expected.units)
+            self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01)
+
+    divisions = [
+        ((("100", "kelvin"), ("10", "kelvin")), ("10", "")),
+        ((("100", "kelvin"), ("10", "degC")), "error"),
+        ((("100", "kelvin"), ("10", "degF")), "error"),
+        ((("100", "kelvin"), ("10", "degR")), ("10", "kelvin/degR")),
+        ((("100", "kelvin"), ("10", "delta_degC")), ("10", "kelvin/delta_degC")),
+        ((("100", "kelvin"), ("10", "delta_degF")), ("10", "kelvin/delta_degF")),
+        ((("100", "degC"), ("10", "kelvin")), "error"),
+        ((("100", "degC"), ("10", "degC")), "error"),
+        ((("100", "degC"), ("10", "degF")), "error"),
+        ((("100", "degC"), ("10", "degR")), "error"),
+        ((("100", "degC"), ("10", "delta_degC")), "error"),
+        ((("100", "degC"), ("10", "delta_degF")), "error"),
+        ((("100", "degF"), ("10", "kelvin")), "error"),
+        ((("100", "degF"), ("10", "degC")), "error"),
+        ((("100", "degF"), ("10", "degF")), "error"),
+        ((("100", "degF"), ("10", "degR")), "error"),
+        ((("100", "degF"), ("10", "delta_degC")), "error"),
+        ((("100", "degF"), ("10", "delta_degF")), "error"),
+        ((("100", "degR"), ("10", "kelvin")), ("10", "degR/kelvin")),
+        ((("100", "degR"), ("10", "degC")), "error"),
+        ((("100", "degR"), ("10", "degF")), "error"),
+        ((("100", "degR"), ("10", "degR")), ("10", "")),
+        ((("100", "degR"), ("10", "delta_degC")), ("10", "degR/delta_degC")),
+        ((("100", "degR"), ("10", "delta_degF")), ("10", "degR/delta_degF")),
+        ((("100", "delta_degC"), ("10", "kelvin")), ("10", "delta_degC/kelvin")),
+        ((("100", "delta_degC"), ("10", "degC")), "error"),
+        ((("100", "delta_degC"), ("10", "degF")), "error"),
+        ((("100", "delta_degC"), ("10", "degR")), ("10", "delta_degC/degR")),
+        ((("100", "delta_degC"), ("10", "delta_degC")), ("10", "")),
+        (
+            (("100", "delta_degC"), ("10", "delta_degF")),
+            ("10", "delta_degC/delta_degF"),
+        ),
+        ((("100", "delta_degF"), ("10", "kelvin")), ("10", "delta_degF/kelvin")),
+        ((("100", "delta_degF"), ("10", "degC")), "error"),
+        ((("100", "delta_degF"), ("10", "degF")), "error"),
+        ((("100", "delta_degF"), ("10", "degR")), ("10", "delta_degF/degR")),
+        (
+            (("100", "delta_degF"), ("10", "delta_degC")),
+            ("10", "delta_degF/delta_degC"),
+        ),
+        ((("100", "delta_degF"), ("10", "delta_degF")), ("10", "")),
+    ]
+
+    @ParameterizedTestCase.parameterize(("input", "expected_output"), divisions)
+    def test_truedivision(self, input_tuple, expected):
+        self.ureg.autoconvert_offset_to_baseunit = False
+        qin1, qin2 = input_tuple
+        q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+        input_tuple = q1, q2
+        if expected == "error":
+            self.assertRaises(OffsetUnitCalculusError, op.truediv, q1, q2)
+        else:
+            expected = self.QP_(*expected)
+            self.assertEqual(op.truediv(q1, q2).units, expected.units)
+            self.assertQuantityAlmostEqual(op.truediv(q1, q2), expected, atol=0.01)
+
+    multiplications_with_autoconvert_to_baseunit = [
+        ((("100", "kelvin"), ("10", "degC")), ("28315.0", "kelvin**2")),
+        ((("100", "kelvin"), ("10", "degF")), ("26092.78", "kelvin**2")),
+        ((("100", "degC"), ("10", "kelvin")), ("3731.5", "kelvin**2")),
+        ((("100", "degC"), ("10", "degC")), ("105657.42", "kelvin**2")),
+        ((("100", "degC"), ("10", "degF")), ("97365.20", "kelvin**2")),
+        ((("100", "degC"), ("10", "degR")), ("3731.5", "kelvin*degR")),
+        ((("100", "degC"), ("10", "delta_degC")), ("3731.5", "kelvin*delta_degC")),
+        ((("100", "degC"), ("10", "delta_degF")), ("3731.5", "kelvin*delta_degF")),
+        ((("100", "degF"), ("10", "kelvin")), ("3109.28", "kelvin**2")),
+        ((("100", "degF"), ("10", "degC")), ("88039.20", "kelvin**2")),
+        ((("100", "degF"), ("10", "degF")), ("81129.69", "kelvin**2")),
+        ((("100", "degF"), ("10", "degR")), ("3109.28", "kelvin*degR")),
+        ((("100", "degF"), ("10", "delta_degC")), ("3109.28", "kelvin*delta_degC")),
+        ((("100", "degF"), ("10", "delta_degF")), ("3109.28", "kelvin*delta_degF")),
+        ((("100", "degR"), ("10", "degC")), ("28315.0", "degR*kelvin")),
+        ((("100", "degR"), ("10", "degF")), ("26092.78", "degR*kelvin")),
+        ((("100", "delta_degC"), ("10", "degC")), ("28315.0", "delta_degC*kelvin")),
+        ((("100", "delta_degC"), ("10", "degF")), ("26092.78", "delta_degC*kelvin")),
+        ((("100", "delta_degF"), ("10", "degC")), ("28315.0", "delta_degF*kelvin")),
+        ((("100", "delta_degF"), ("10", "degF")), ("26092.78", "delta_degF*kelvin")),
+    ]
+
+    @ParameterizedTestCase.parameterize(
+        ("input", "expected_output"), multiplications_with_autoconvert_to_baseunit
+    )
+    def test_multiplication_with_autoconvert(self, input_tuple, expected):
+        self.ureg.autoconvert_offset_to_baseunit = True
+        qin1, qin2 = input_tuple
+        q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+        input_tuple = q1, q2
+        if expected == "error":
+            self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2)
+        else:
+            expected = self.QP_(*expected)
+            self.assertEqual(op.mul(q1, q2).units, expected.units)
+            self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01)
+
+    multiplications_with_scalar = [
+        ((("10", "kelvin"), "2"), ("20.0", "kelvin")),
+        ((("10", "kelvin**2"), "2"), ("20.0", "kelvin**2")),
+        ((("10", "degC"), "2"), ("20.0", "degC")),
+        ((("10", "1/degC"), "2"), "error"),
+        ((("10", "degC**0.5"), "2"), "error"),
+        ((("10", "degC**2"), "2"), "error"),
+        ((("10", "degC**-2"), "2"), "error"),
+    ]
+
+    @ParameterizedTestCase.parameterize(
+        ("input", "expected_output"), multiplications_with_scalar
+    )
+    def test_multiplication_with_scalar(self, input_tuple, expected):
+        self.ureg.default_as_delta = False
+        in1, in2 = input_tuple
+        if type(in1) is tuple:
+            in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2)
+        else:
+            in1, in2 = in1, self.QP_(*in2)
+        input_tuple = in1, in2  # update input_tuple for better tracebacks
+        if expected == "error":
+            self.assertRaises(OffsetUnitCalculusError, op.mul, in1, in2)
+        else:
+            expected = self.QP_(*expected)
+            self.assertEqual(op.mul(in1, in2).units, expected.units)
+            self.assertQuantityAlmostEqual(op.mul(in1, in2), expected, atol="0.01")
+
+    divisions_with_scalar = [  # without / with autoconvert to base unit
+        ((("10", "kelvin"), "2"), [("5.0", "kelvin"), ("5.0", "kelvin")]),
+        ((("10", "kelvin**2"), "2"), [("5.0", "kelvin**2"), ("5.0", "kelvin**2")]),
+        ((("10", "degC"), "2"), ["error", "error"]),
+        ((("10", "degC**2"), "2"), ["error", "error"]),
+        ((("10", "degC**-2"), "2"), ["error", "error"]),
+        (("2", ("10", "kelvin")), [("0.2", "1/kelvin"), ("0.2", "1/kelvin")]),
+        # (('2', ('10', "degC")), ["error", (2 / 283.15, "1/kelvin")]),
+        (("2", ("10", "degC**2")), ["error", "error"]),
+        (("2", ("10", "degC**-2")), ["error", "error"]),
+    ]
+
+    @ParameterizedTestCase.parameterize(
+        ("input", "expected_output"), divisions_with_scalar
+    )
+    def test_division_with_scalar(self, input_tuple, expected):
+        self.ureg.default_as_delta = False
+        in1, in2 = input_tuple
+        if type(in1) is tuple:
+            in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2)
+        else:
+            in1, in2 = self.NON_INT_TYPE(in1), self.QP_(*in2)
+        input_tuple = in1, in2  # update input_tuple for better tracebacks
+        expected_copy = expected[:]
+        for i, mode in enumerate([False, True]):
+            self.ureg.autoconvert_offset_to_baseunit = mode
+            if expected_copy[i] == "error":
+                self.assertRaises(OffsetUnitCalculusError, op.truediv, in1, in2)
+            else:
+                expected = self.QP_(*expected_copy[i])
+                self.assertEqual(op.truediv(in1, in2).units, expected.units)
+                self.assertQuantityAlmostEqual(op.truediv(in1, in2), expected)
+
+    exponentiation = [  # resuls without / with autoconvert
+        ((("10", "degC"), "1"), [("10", "degC"), ("10", "degC")]),
+        # ((('10', "degC"), 0.5), ["error", (283.15 ** '0.5', "kelvin**0.5")]),
+        ((("10", "degC"), "0"), [("1.0", ""), ("1.0", "")]),
+        # ((('10', "degC"), -1), ["error", (1 / (10 + 273.15), "kelvin**-1")]),
+        # ((('10', "degC"), -2), ["error", (1 / (10 + 273.15) ** 2.0, "kelvin**-2")]),
+        # ((('0', "degC"), -2), ["error", (1 / (273.15) ** 2, "kelvin**-2")]),
+        # ((('10', "degC"), ('2', "")), ["error", ((283.15) ** 2, "kelvin**2")]),
+        ((("10", "degC"), ("10", "degK")), ["error", "error"]),
+        (
+            (("10", "kelvin"), ("2", "")),
+            [("100.0", "kelvin**2"), ("100.0", "kelvin**2")],
+        ),
+        (("2", ("2", "kelvin")), ["error", "error"]),
+        # (('2', ('500.0', "millikelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
+        # (('2', ('0.5', "kelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
+        # (
+        #     (('10', "degC"), ('500.0', "millikelvin/kelvin")),
+        #     ["error", (283.15 ** '0.5', "kelvin**0.5")],
+        # ),
+    ]
+
+    @ParameterizedTestCase.parameterize(("input", "expected_output"), exponentiation)
+    def test_exponentiation(self, input_tuple, expected):
+        self.ureg.default_as_delta = False
+        in1, in2 = input_tuple
+        if type(in1) is tuple and type(in2) is tuple:
+            in1, in2 = self.QP_(*in1), self.QP_(*in2)
+        elif not type(in1) is tuple and type(in2) is tuple:
+            in1, in2 = self.NON_INT_TYPE(in1), self.QP_(*in2)
+        else:
+            in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2)
+        input_tuple = in1, in2
+        expected_copy = expected[:]
+        for i, mode in enumerate([False, True]):
+            self.ureg.autoconvert_offset_to_baseunit = mode
+            if expected_copy[i] == "error":
+                self.assertRaises(
+                    (OffsetUnitCalculusError, DimensionalityError), op.pow, in1, in2
+                )
+            else:
+                if type(expected_copy[i]) is tuple:
+                    expected = self.QP_(*expected_copy[i])
+                    self.assertEqual(op.pow(in1, in2).units, expected.units)
+                else:
+                    expected = expected_copy[i]
+                self.assertQuantityAlmostEqual(op.pow(in1, in2), expected)
+
+
+class NonIntTypeQuantityTestQuantityFloat(_TestBasic, NonIntTypeQuantityTestCase):
+
+    NON_INT_TYPE = float
+
+
+class NonIntTypeQuantityTestQuantityBasicMathFloat(
+    _TestQuantityBasicMath, NonIntTypeQuantityTestCase
+):
+
+    NON_INT_TYPE = float
+
+
+class NonIntTypeQuantityTestOffsetUnitMathFloat(
+    _TestOffsetUnitMath, NonIntTypeQuantityTestCase
+):
+
+    NON_INT_TYPE = float
+
+
+class NonIntTypeQuantityTestQuantityDecimal(_TestBasic, NonIntTypeQuantityTestCase):
+
+    NON_INT_TYPE = Decimal
+
+
+class NonIntTypeQuantityTestQuantityBasicMathDecimal(
+    _TestQuantityBasicMath, NonIntTypeQuantityTestCase
+):
+
+    NON_INT_TYPE = Decimal
+
+
+class NonIntTypeQuantityTestOffsetUnitMathDecimal(
+    _TestOffsetUnitMath, NonIntTypeQuantityTestCase
+):
+
+    NON_INT_TYPE = Decimal
+
+
+class NonIntTypeQuantityTestQuantityFraction(_TestBasic, NonIntTypeQuantityTestCase):
+
+    NON_INT_TYPE = Fraction
+
+
+class NonIntTypeQuantityTestQuantityBasicMathFraction(
+    _TestQuantityBasicMath, NonIntTypeQuantityTestCase
+):
+
+    NON_INT_TYPE = Fraction
+
+
+class NonIntTypeQuantityTestOffsetUnitMathFraction(
+    _TestOffsetUnitMath, NonIntTypeQuantityTestCase
+):
+
+    NON_INT_TYPE = Fraction
diff --git a/pint/testsuite/test_util.py b/pint/testsuite/test_util.py
index 52c7c82..5f71173 100644
--- a/pint/testsuite/test_util.py
+++ b/pint/testsuite/test_util.py
@@ -2,7 +2,6 @@ import collections
 import copy
 import math
 import operator as op
-from decimal import Decimal
 
 from pint.testsuite import BaseTestCase, QuantityTestCase
 from pint.util import (
@@ -51,8 +50,8 @@ class TestUnitsContainer(QuantityTestCase):
 
     def test_unitcontainer_creation(self):
         x = UnitsContainer(meter=1, second=2)
-        y = UnitsContainer({"meter": 1.0, "second": 2.0})
-        self.assertIsInstance(x["meter"], float)
+        y = UnitsContainer({"meter": 1, "second": 2})
+        self.assertIsInstance(x["meter"], int)
         self.assertEqual(x, y)
         self.assertIsNot(x, y)
         z = copy.copy(x)
@@ -68,10 +67,10 @@ class TestUnitsContainer(QuantityTestCase):
         self.assertEqual(repr(x), "<UnitsContainer({})>")
         x = UnitsContainer(meter=1, second=2)
         self.assertEqual(str(x), "meter * second ** 2")
-        self.assertEqual(repr(x), "<UnitsContainer({'meter': 1.0, 'second': 2.0})>")
+        self.assertEqual(repr(x), "<UnitsContainer({'meter': 1, 'second': 2})>")
         x = UnitsContainer(meter=1, second=2.5)
         self.assertEqual(str(x), "meter * second ** 2.5")
-        self.assertEqual(repr(x), "<UnitsContainer({'meter': 1.0, 'second': 2.5})>")
+        self.assertEqual(repr(x), "<UnitsContainer({'meter': 1, 'second': 2.5})>")
 
     def test_unitcontainer_bool(self):
         self.assertTrue(UnitsContainer(meter=1, second=2))
@@ -161,7 +160,7 @@ class TestParseHelper(BaseTestCase):
         self.assertEqual(x(), xp())
         self.assertNotEqual(x(), y())
         self.assertEqual(ParserHelper.from_string(""), ParserHelper())
-        self.assertEqual(repr(x()), "<ParserHelper(1, {'meter': 2.0})>")
+        self.assertEqual(repr(x()), "<ParserHelper(1, {'meter': 2})>")
 
         self.assertEqual(ParserHelper(2), 2)
 
@@ -199,10 +198,7 @@ class TestParseHelper(BaseTestCase):
     def test_eval_token(self):
         self._test_eval_token(1000.0, "1e3")
         self._test_eval_token(1000.0, "1E3")
-        self._test_eval_token(Decimal(1000), "1e3", use_decimal=True)
         self._test_eval_token(1000, "1000")
-        # integer numbers are represented as ints, not Decimals
-        self._test_eval_token(1000, "1000", use_decimal=True)
 
     def test_nan(self):
         for s in ("nan", "NAN", "NaN", "123 NaN nan NAN 456"):