Repository: hgrecco/pint

Issue:
UnitRegistry with non-float non_int_type creates float NaN magnitudes.
When using a UnitRegistry with a non_int_type other than float, normal values are created correctly, but NaN values are not:

```python
import pint
from decimal import Decimal
ureg = pint.UnitRegistry(non_int_type=Decimal)
a = ureg("5.0 m")
type(a.m)
#[Out]# decimal.Decimal
b = ureg("NaN m")
type(b.m)
#[Out]# float
c = ureg.Quantity(Decimal("NaN"), "m")
type(c.m)
#[Out]# decimal.Decimal
```

Discussion:
cgevans: In working on this, I've realized that none of the tests in test_non_int.py are actually being run, because the class names prevent the tests from being discovered by pytest.

Should I create a new issue for this, or just fix it here?  Or is it intentional?
keewis: this is probably an oversight from the transition from `unittest` to `pytest`, where before `QuantityTestCase` would inherit from `unittest.TestCase` and thus would be detected by `pytest`. So definitely not intentional!

In any case, good catch! I think renaming the final tests (e.g. `NonIntTypeQuantityTestQuantityFloat` → `TestNonIntTypeQuantityFloat`) would suffice... another option would be to adjust the pattern `pytest` uses to detect test classes, but renaming is probably cleaner.

what do you think, @hgrecco, @jules-ch?
cgevans: It's easy enough for me to change the names of the tests, which is what I've been doing (some of the tests are also broken in various ways, and need to be updated a bit).  But it may be that there are other tests that are not being run, unrelated to non_int_type, that I'm not aware of.
keewis: from a search, it seems that there are no other test classes in the testsuite that don't have a prefix of `Test`, so thankfully those seem to be the only ones that were missed.

For easier reviewing, I'd probably advise to open a new PR with just the renaming and any eventual fixes, if you're up for that?
jules-ch: Good catch, can you make another PR fixing this ? :)
cgevans: Yes: I'm working on it right now, actually.
cgevans: See #1603.  My guess is that the safest thing to do here would be to deal with that PR, then rebase this after merging that in order to actually ensure the tests for this change run.
jules-ch: Yep I'll merge #1603, then you can rebase on master when it's done.

Patch:
diff --git a/CHANGES b/CHANGES
index f3a85a3..6cccf06 100644
--- a/CHANGES
+++ b/CHANGES
@@ -14,6 +14,7 @@ Pint Changelog
 - Fix a recursion error that would be raised when passing quantities to `cond` and `x`.
   (Issue #1510, #1530)
 - Update test_non_int tests for pytest.
+- Create NaN-value quantities of appropriate non-int-type (Issue #1570).
 
 0.19.2 (2022-04-23)
 -------------------
diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py
index 8572fec..4c62ad1 100644
--- a/pint/facets/plain/registry.py
+++ b/pint/facets/plain/registry.py
@@ -1226,9 +1226,9 @@ class PlainRegistry(metaclass=RegistryMeta):
             if token_text == "dimensionless":
                 return 1 * self.dimensionless
             elif token_text.lower() in ("inf", "infinity"):
-                return float("inf")
+                return self.non_int_type("inf")
             elif token_text.lower() == "nan":
-                return float("nan")
+                return self.non_int_type("nan")
             elif token_text in values:
                 return self.Quantity(values[token_text])
             else:
diff --git a/pint/util.py b/pint/util.py
index 54a7755..0152e66 100644
--- a/pint/util.py
+++ b/pint/util.py
@@ -641,7 +641,7 @@ class ParserHelper(UnitsContainer):
         for k in list(ret):
             if k.lower() == "nan":
                 del ret._d[k]
-                ret.scale = math.nan
+                ret.scale = non_int_type(math.nan)
 
         return ret
 


Test patch:
diff --git a/pint/testsuite/test_non_int.py b/pint/testsuite/test_non_int.py
index 5ca6c52..f616622 100644
--- a/pint/testsuite/test_non_int.py
+++ b/pint/testsuite/test_non_int.py
@@ -77,6 +77,27 @@ class _TestBasic(NonIntTypeTestCase):
             == "Creating new PlainQuantity using a non unity PlainQuantity as units."
         )
 
+    def test_nan_creation(self):
+        if self.SUPPORTS_NAN:
+            value = self.kwargs["non_int_type"]("nan")
+
+            for args in (
+                (value, "meter"),
+                (value, UnitsContainer(meter=1)),
+                (value, self.ureg.meter),
+                ("NaN*meter",),
+                ("nan/meter**(-1)",),
+                (self.Q_(value, "meter"),),
+            ):
+                x = self.Q_(*args)
+                assert math.isnan(x.magnitude)
+                assert type(x.magnitude) == self.kwargs["non_int_type"]
+                assert x.units == self.ureg.UnitsContainer(meter=1)
+
+        else:
+            with pytest.raises(ValueError):
+                self.Q_("NaN meters")
+
     def test_quantity_comparison(self):
         x = self.QP_("4.2", "meter")
         y = self.QP_("4.2", "meter")
@@ -1137,6 +1158,7 @@ class _TestOffsetUnitMath(NonIntTypeTestCase):
 class TestNonIntTypeQuantityFloat(_TestBasic):
 
     kwargs = dict(non_int_type=float)
+    SUPPORTS_NAN = True
 
 
 class TestNonIntTypeQuantityBasicMathFloat(_TestQuantityBasicMath):
@@ -1152,6 +1174,7 @@ class TestNonIntTypeOffsetUnitMathFloat(_TestOffsetUnitMath):
 class TestNonIntTypeQuantityDecimal(_TestBasic):
 
     kwargs = dict(non_int_type=Decimal)
+    SUPPORTS_NAN = True
 
 
 class TestNonIntTypeQuantityBasicMathDecimal(_TestQuantityBasicMath):
@@ -1167,6 +1190,7 @@ class TestNonIntTypeOffsetUnitMathDecimal(_TestOffsetUnitMath):
 class TestNonIntTypeQuantityFraction(_TestBasic):
 
     kwargs = dict(non_int_type=Fraction)
+    SUPPORTS_NAN = False
 
 
 class TestNonIntTypeQuantityBasicMathFraction(_TestQuantityBasicMath):