import copy
import math
import operator as op
import pickle
from decimal import Decimal
from fractions import Fraction

import pytest

from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry
from pint.facets.plain.unit import UnitsContainer
from pint.testsuite import QuantityTestCase, helpers


# TODO: do not subclass from QuantityTestCase
class NonIntTypeTestCase(QuantityTestCase):
    def assert_quantity_almost_equal(
        self, first, second, rtol="1e-07", atol="0", msg=None
    ):
        if isinstance(first, self.Q_):
            assert isinstance(first.m, (self.kwargs["non_int_type"], int))
        else:
            assert isinstance(first, (self.kwargs["non_int_type"], int))

        if isinstance(second, self.Q_):
            assert isinstance(second.m, (self.kwargs["non_int_type"], int))
        else:
            assert isinstance(second, (self.kwargs["non_int_type"], int))
        helpers.assert_quantity_almost_equal(
            first,
            second,
            self.kwargs["non_int_type"](rtol),
            self.kwargs["non_int_type"](atol),
            msg,
        )

    def QP_(self, value, units):
        assert isinstance(value, str)
        return self.Q_(self.kwargs["non_int_type"](value), units)


class _TestBasic(NonIntTypeTestCase):
    def test_quantity_creation(self, caplog):
        value = self.kwargs["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)
            assert x.magnitude == value
            assert x.units == self.ureg.UnitsContainer(meter=1)

        x = self.Q_(value, UnitsContainer(length=1))
        y = self.Q_(x)
        assert x.magnitude == y.magnitude
        assert x.units == y.units
        assert x is not y

        x = self.Q_(value, None)
        assert x.magnitude == value
        assert x.units == UnitsContainer()

        caplog.clear()
        assert value * self.ureg.meter == self.Q_(
            value, self.kwargs["non_int_type"]("2") * self.ureg.meter
        )
        assert len(caplog.records) == 1
        assert (
            caplog.records[0].message
            == "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")
        z = self.QP_("5", "meter")
        j = self.QP_("5", "meter*meter")

        # identity for single object
        assert x == x
        assert not (x != x)

        # identity for multiple objects with same value
        assert x == y
        assert not (x != y)

        assert x <= y
        assert x >= y
        assert not (x < y)
        assert not (x > y)

        assert not (x == z)
        assert x != z
        assert x < z

        assert z != j

        assert z != j
        assert self.QP_("0", "meter") == self.QP_("0", "centimeter")
        assert self.QP_("0", "meter") != self.QP_("0", "second")

        assert self.QP_("10", "meter") < self.QP_("5", "kilometer")

    def test_quantity_comparison_convert(self):
        assert self.QP_("1000", "millimeter") == self.QP_("1", "meter")
        assert self.QP_("1000", "millimeter/min") == self.Q_(
            self.kwargs["non_int_type"]("1000") / self.kwargs["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")
        assert hash(x) == hash(x2)

        # Dimensionless equality
        assert hash(y * z) == hash(1.0)

        # Dimensionless equality from a different unit registry
        ureg2 = UnitRegistry()
        y2 = ureg2.Quantity(self.kwargs["non_int_type"]("2"), "second")
        z2 = ureg2.Quantity(self.kwargs["non_int_type"]("0.5"), "hertz")
        assert hash(y * z) == hash(y2 * z2)

    def test_to_base_units(self):
        x = self.Q_("1*inch")
        self.assert_quantity_almost_equal(
            x.to_base_units(), self.QP_("0.0254", "meter")
        )
        x = self.Q_("1*inch*inch")
        self.assert_quantity_almost_equal(
            x.to_base_units(),
            self.Q_(
                self.kwargs["non_int_type"]("0.0254")
                ** self.kwargs["non_int_type"]("2.0"),
                "meter*meter",
            ),
        )
        x = self.Q_("1*inch/minute")
        self.assert_quantity_almost_equal(
            x.to_base_units(),
            self.Q_(
                self.kwargs["non_int_type"]("0.0254")
                / self.kwargs["non_int_type"]("60"),
                "meter/second",
            ),
        )

    def test_convert(self):
        self.assert_quantity_almost_equal(
            self.Q_("2 inch").to("meter"),
            self.Q_(
                self.kwargs["non_int_type"]("2")
                * self.kwargs["non_int_type"]("0.0254"),
                "meter",
            ),
        )
        self.assert_quantity_almost_equal(
            self.Q_("2 meter").to("inch"),
            self.Q_(
                self.kwargs["non_int_type"]("2")
                / self.kwargs["non_int_type"]("0.0254"),
                "inch",
            ),
        )
        self.assert_quantity_almost_equal(
            self.Q_("2 sidereal_year").to("second"), self.QP_("63116297.5325", "second")
        )
        self.assert_quantity_almost_equal(
            self.Q_("2.54 centimeter/second").to("inch/second"),
            self.Q_("1 inch/second"),
        )
        assert round(abs(self.Q_("2.54 centimeter").to("inch").magnitude - 1), 7) == 0
        assert (
            round(abs(self.Q_("2 second").to("millisecond").magnitude - 2000), 7) == 0
        )

    def test_convert_from(self):
        x = self.Q_("2*inch")
        meter = self.ureg.meter

        # from quantity
        self.assert_quantity_almost_equal(
            meter.from_(x),
            self.Q_(
                self.kwargs["non_int_type"]("2")
                * self.kwargs["non_int_type"]("0.0254"),
                "meter",
            ),
        )
        self.assert_quantity_almost_equal(
            meter.m_from(x),
            self.kwargs["non_int_type"]("2") * self.kwargs["non_int_type"]("0.0254"),
        )

        # from unit
        self.assert_quantity_almost_equal(
            meter.from_(self.ureg.inch), self.QP_("0.0254", "meter")
        )
        self.assert_quantity_almost_equal(
            meter.m_from(self.ureg.inch), self.kwargs["non_int_type"]("0.0254")
        )

        # from number
        self.assert_quantity_almost_equal(
            meter.from_(2, strict=False), self.QP_("2", "meter")
        )
        self.assert_quantity_almost_equal(
            meter.m_from(self.kwargs["non_int_type"]("2"), strict=False),
            self.kwargs["non_int_type"]("2"),
        )

        # from number (strict mode)
        with pytest.raises(ValueError):
            meter.from_(self.kwargs["non_int_type"]("2"))
        with pytest.raises(ValueError):
            meter.m_from(self.kwargs["non_int_type"]("2"))

    def test_context_attr(self):
        assert self.ureg.meter == self.QP_("1", "meter")

    def test_both_symbol(self):
        assert self.QP_("2", "ms") == self.QP_("2", "millisecond")
        assert self.QP_("2", "cm") == self.QP_("2", "centimeter")

    def test_dimensionless_units(self):
        twopi = self.kwargs["non_int_type"]("2") * self.ureg.pi
        assert (
            round(abs(self.QP_("360", "degree").to("radian").magnitude - twopi), 7) == 0
        )
        assert round(abs(self.Q_(twopi, "radian") - self.QP_("360", "degree")), 7) == 0
        assert self.QP_("1", "radian").dimensionality == UnitsContainer()
        assert self.QP_("1", "radian").dimensionless
        assert not self.QP_("1", "radian").unitless

        assert self.QP_("1", "meter") / self.QP_("1", "meter") == 1
        assert (self.QP_("1", "meter") / self.QP_("1", "mm")).to("") == 1000

        assert self.Q_(10) // self.QP_("360", "degree") == 1
        assert self.QP_("400", "degree") // self.Q_(twopi) == 1
        assert self.QP_("400", "degree") // twopi == 1
        assert 7 // self.QP_("360", "degree") == 1

    def test_offset(self):
        self.assert_quantity_almost_equal(
            self.QP_("0", "kelvin").to("kelvin"), self.QP_("0", "kelvin")
        )
        self.assert_quantity_almost_equal(
            self.QP_("0", "degC").to("kelvin"), self.QP_("273.15", "kelvin")
        )
        self.assert_quantity_almost_equal(
            self.QP_("0", "degF").to("kelvin"),
            self.QP_("255.372222", "kelvin"),
            rtol=0.01,
        )

        self.assert_quantity_almost_equal(
            self.QP_("100", "kelvin").to("kelvin"), self.QP_("100", "kelvin")
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "degC").to("kelvin"), self.QP_("373.15", "kelvin")
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "degF").to("kelvin"),
            self.QP_("310.92777777", "kelvin"),
            rtol=0.01,
        )

        self.assert_quantity_almost_equal(
            self.QP_("0", "kelvin").to("degC"), self.QP_("-273.15", "degC")
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "kelvin").to("degC"), self.QP_("-173.15", "degC")
        )
        self.assert_quantity_almost_equal(
            self.QP_("0", "kelvin").to("degF"), self.QP_("-459.67", "degF"), rtol=0.01
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "kelvin").to("degF"), self.QP_("-279.67", "degF"), rtol=0.01
        )

        self.assert_quantity_almost_equal(
            self.QP_("32", "degF").to("degC"), self.QP_("0", "degC"), atol=0.01
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "degC").to("degF"), self.QP_("212", "degF"), atol=0.01
        )

        self.assert_quantity_almost_equal(
            self.QP_("54", "degF").to("degC"), self.QP_("12.2222", "degC"), atol=0.01
        )
        self.assert_quantity_almost_equal(
            self.QP_("12", "degC").to("degF"), self.QP_("53.6", "degF"), atol=0.01
        )

        self.assert_quantity_almost_equal(
            self.QP_("12", "kelvin").to("degC"), self.QP_("-261.15", "degC"), atol=0.01
        )
        self.assert_quantity_almost_equal(
            self.QP_("12", "degC").to("kelvin"), self.QP_("285.15", "kelvin"), atol=0.01
        )

        self.assert_quantity_almost_equal(
            self.QP_("12", "kelvin").to("degR"), self.QP_("21.6", "degR"), atol=0.01
        )
        self.assert_quantity_almost_equal(
            self.QP_("12", "degR").to("kelvin"),
            self.QP_("6.66666667", "kelvin"),
            atol=0.01,
        )

        self.assert_quantity_almost_equal(
            self.QP_("12", "degC").to("degR"), self.QP_("513.27", "degR"), atol=0.01
        )
        self.assert_quantity_almost_equal(
            self.QP_("12", "degR").to("degC"),
            self.QP_("-266.483333", "degC"),
            atol=0.01,
        )

    def test_offset_delta(self):
        self.assert_quantity_almost_equal(
            self.QP_("0", "delta_degC").to("kelvin"), self.QP_("0", "kelvin")
        )
        self.assert_quantity_almost_equal(
            self.QP_("0", "delta_degF").to("kelvin"), self.QP_("0", "kelvin"), rtol=0.01
        )

        self.assert_quantity_almost_equal(
            self.QP_("100", "kelvin").to("delta_degC"), self.QP_("100", "delta_degC")
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "kelvin").to("delta_degF"),
            self.QP_("180", "delta_degF"),
            rtol=0.01,
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "delta_degF").to("kelvin"),
            self.QP_("55.55555556", "kelvin"),
            rtol=0.01,
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "delta_degC").to("delta_degF"),
            self.QP_("180", "delta_degF"),
            rtol=0.01,
        )
        self.assert_quantity_almost_equal(
            self.QP_("100", "delta_degF").to("delta_degC"),
            self.QP_("55.55555556", "delta_degC"),
            rtol=0.01,
        )

        self.assert_quantity_almost_equal(
            self.QP_("12.3", "delta_degC").to("delta_degF"),
            self.QP_("22.14", "delta_degF"),
            rtol=0.01,
        )

    def test_pickle(self, subtests):
        for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
            for magnitude, unit in (
                ("32", ""),
                ("2.4", ""),
                ("32", "m/s"),
                ("2.4", "m/s"),
            ):
                with subtests.test(protocol=protocol, magnitude=magnitude, unit=unit):
                    q1 = self.QP_(magnitude, unit)
                    q2 = pickle.loads(pickle.dumps(q1, protocol))
                    assert q1 == q2

    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 pytest.raises(TypeError):
            iter(x)


class _TestQuantityBasicMath(NonIntTypeTestCase):
    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.assert_quantity_almost_equal(value1, expected_result)
        assert id1 == id(value1)
        self.assert_quantity_almost_equal(value2, value2_cpy)
        assert 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.assert_quantity_almost_equal(expected_result, result)
        self.assert_quantity_almost_equal(value1, value1_cpy)
        self.assert_quantity_almost_equal(value2, value2_cpy)
        assert id(result) != id1
        assert 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.kwargs["non_int_type"]("2.54") * unit, "centimeter"),
        )
        func(
            op.add,
            y,
            x,
            self.Q_(unit + unit / (self.kwargs["non_int_type"]("2.54") * unit), "inch"),
        )
        func(op.add, a, unit, self.Q_(unit + unit, None))
        with pytest.raises(DimensionalityError):
            op.add(self.kwargs["non_int_type"]("10"), x)
        with pytest.raises(DimensionalityError):
            op.add(x, self.kwargs["non_int_type"]("10"))
        with pytest.raises(DimensionalityError):
            op.add(x, z)

        func(op.sub, x, x, self.Q_(unit - unit, "centimeter"))
        func(
            op.sub,
            x,
            y,
            self.Q_(unit - self.kwargs["non_int_type"]("2.54") * unit, "centimeter"),
        )
        func(
            op.sub,
            y,
            x,
            self.Q_(unit - unit / (self.kwargs["non_int_type"]("2.54") * unit), "inch"),
        )
        func(op.sub, a, unit, self.Q_(unit - unit, None))
        with pytest.raises(DimensionalityError):
            op.sub(self.kwargs["non_int_type"]("10"), x)
        with pytest.raises(DimensionalityError):
            op.sub(x, self.kwargs["non_int_type"]("10"))
        with pytest.raises(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.kwargs["non_int_type"]("2.54") * unit, "centimeter"),
        )
        func(
            op.iadd,
            y,
            x,
            self.Q_(unit + unit / self.kwargs["non_int_type"]("2.54"), "inch"),
        )
        func(op.iadd, a, unit, self.Q_(unit + unit, None))
        with pytest.raises(DimensionalityError):
            op.iadd(self.kwargs["non_int_type"]("10"), x)
        with pytest.raises(DimensionalityError):
            op.iadd(x, self.kwargs["non_int_type"]("10"))
        with pytest.raises(DimensionalityError):
            op.iadd(x, z)

        func(op.isub, x, x, self.Q_(unit - unit, "centimeter"))
        func(
            op.isub,
            x,
            y,
            self.Q_(unit - self.kwargs["non_int_type"]("2.54"), "centimeter"),
        )
        func(
            op.isub,
            y,
            x,
            self.Q_(unit - unit / self.kwargs["non_int_type"]("2.54"), "inch"),
        )
        func(op.isub, a, unit, self.Q_(unit - unit, None))
        with pytest.raises(DimensionalityError):
            op.sub(self.kwargs["non_int_type"]("10"), x)
        with pytest.raises(DimensionalityError):
            op.sub(x, self.kwargs["non_int_type"]("10"))
        with pytest.raises(DimensionalityError):
            op.sub(x, z)

    def _test_quantity_mul_div(self, unit, func):
        func(
            op.mul,
            unit * self.kwargs["non_int_type"]("10"),
            "4.2*meter",
            "42*meter",
            unit,
        )
        func(
            op.mul,
            "4.2*meter",
            unit * self.kwargs["non_int_type"]("10"),
            "42*meter",
            unit,
        )
        func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit)
        func(
            op.truediv,
            unit * self.kwargs["non_int_type"]("42"),
            "4.2*meter",
            "10/meter",
            unit,
        )
        func(
            op.truediv,
            "4.2*meter",
            unit * self.kwargs["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.kwargs["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.kwargs["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")
        with pytest.raises(DimensionalityError):
            op.floordiv(a, b)
        with pytest.raises(DimensionalityError):
            op.floordiv(self.kwargs["non_int_type"]("3"), b)
        with pytest.raises(DimensionalityError):
            op.floordiv(a, self.kwargs["non_int_type"]("3"))
        with pytest.raises(DimensionalityError):
            op.ifloordiv(a, b)
        with pytest.raises(DimensionalityError):
            op.ifloordiv(self.kwargs["non_int_type"]("3"), b)
        with pytest.raises(DimensionalityError):
            op.ifloordiv(a, self.kwargs["non_int_type"]("3"))
        func(
            op.floordiv,
            unit * self.kwargs["non_int_type"]("10"),
            "4.2*meter/meter",
            self.kwargs["non_int_type"]("2"),
            unit,
        )
        func(
            op.floordiv, "10*meter", "4.2*inch", self.kwargs["non_int_type"]("93"), unit
        )

    def _test_quantity_mod(self, unit, func):
        a = self.Q_("10*meter")
        b = self.Q_("3*second")
        with pytest.raises(DimensionalityError):
            op.mod(a, b)
        with pytest.raises(DimensionalityError):
            op.mod(3, b)
        with pytest.raises(DimensionalityError):
            op.mod(a, 3)
        with pytest.raises(DimensionalityError):
            op.imod(a, b)
        with pytest.raises(DimensionalityError):
            op.imod(3, b)
        with pytest.raises(DimensionalityError):
            op.imod(a, 3)
        func(
            op.mod,
            unit * self.kwargs["non_int_type"]("10"),
            "4.2*meter/meter",
            self.kwargs["non_int_type"]("1.6"),
            unit,
        )

    def _test_quantity_ifloordiv(self, unit, func):
        func(
            op.ifloordiv,
            self.kwargs["non_int_type"]("10"),
            "4.2*meter/meter",
            self.kwargs["non_int_type"]("2"),
            unit,
        )
        func(
            op.ifloordiv,
            "10*meter",
            "4.2*inch",
            self.kwargs["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)
        assert q == a // b
        assert r == a % b
        helpers.assert_quantity_equal(a, (q * b) + r)
        assert q == math.floor(q)
        if b > (0 * b):
            assert (0 * b) <= r < b
        else:
            assert (0 * b) >= r > b
        if isinstance(a, self.Q_):
            assert r.units == a.units
        else:
            assert r.unitless
        assert q.unitless

        copy_a = copy.copy(a)
        a %= b
        assert a == r
        copy_a //= b
        assert 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")
        with pytest.raises(DimensionalityError):
            divmod(a, b)
        with pytest.raises(DimensionalityError):
            divmod(3, b)
        with pytest.raises(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.kwargs["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)
            assert rx == zx, f"while testing {fun}"
            assert ry == zy, f"while testing {fun}"
            assert rx is not zx, f"while testing {fun}"
            assert ry is not zy, f"while testing {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):
            assert fun(x) == fun(x.magnitude)
            assert fun(y) == fun(y.magnitude)
            with pytest.raises(DimensionalityError):
                fun(z)

    def test_not_inplace(self):
        self._test_numeric(self.kwargs["non_int_type"]("1.0"), self._test_not_inplace)


class _TestOffsetUnitMath(NonIntTypeTestCase):
    @classmethod
    def setup_class(cls):
        super().setup_class()
        cls.ureg.autoconvert_offset_to_baseunit = False
        cls.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")),
    ]

    @pytest.mark.parametrize(("input_tuple", "expected_output"), additions)
    def test_addition(self, input_tuple, expected_output):
        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_output == "error":
            with pytest.raises(OffsetUnitCalculusError):
                op.add(q1, q2)
        else:
            expected_output = self.QP_(*expected_output)
            assert op.add(q1, q2).units == expected_output.units
            self.assert_quantity_almost_equal(
                op.add(q1, q2), expected_output, 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")),
    ]

    @pytest.mark.parametrize(("input_tuple", "expected_output"), subtractions)
    def test_subtraction(self, input_tuple, expected_output):
        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_output == "error":
            with pytest.raises(OffsetUnitCalculusError):
                op.sub(q1, q2)
        else:
            expected_output = self.QP_(*expected_output)
            assert op.sub(q1, q2).units == expected_output.units
            self.assert_quantity_almost_equal(
                op.sub(q1, q2), expected_output, 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")),
    ]

    @pytest.mark.parametrize(("input_tuple", "expected_output"), multiplications)
    def test_multiplication(self, input_tuple, expected_output):
        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_output == "error":
            with pytest.raises(OffsetUnitCalculusError):
                op.mul(q1, q2)
        else:
            expected_output = self.QP_(*expected_output)
            assert op.mul(q1, q2).units == expected_output.units
            self.assert_quantity_almost_equal(
                op.mul(q1, q2), expected_output, 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", "")),
    ]

    @pytest.mark.parametrize(("input_tuple", "expected_output"), divisions)
    def test_truedivision(self, input_tuple, expected_output):
        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_output == "error":
            with pytest.raises(OffsetUnitCalculusError):
                op.truediv(q1, q2)
        else:
            expected_output = self.QP_(*expected_output)
            assert op.truediv(q1, q2).units == expected_output.units
            self.assert_quantity_almost_equal(
                op.truediv(q1, q2), expected_output, 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")),
    ]

    @pytest.mark.parametrize(
        ("input_tuple", "expected_output"), multiplications_with_autoconvert_to_baseunit
    )
    def test_multiplication_with_autoconvert(self, input_tuple, expected_output):
        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_output == "error":
            with pytest.raises(OffsetUnitCalculusError):
                op.mul(q1, q2)
        else:
            expected_output = self.QP_(*expected_output)
            assert op.mul(q1, q2).units == expected_output.units
            self.assert_quantity_almost_equal(
                op.mul(q1, q2), expected_output, 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"),
    ]

    @pytest.mark.parametrize(
        ("input_tuple", "expected_output"), multiplications_with_scalar
    )
    def test_multiplication_with_scalar(self, input_tuple, expected_output):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is tuple:
            in1, in2 = self.QP_(*in1), self.kwargs["non_int_type"](in2)
        else:
            in1, in2 = in1, self.QP_(*in2)
        input_tuple = in1, in2  # update input_tuple for better tracebacks
        if expected_output == "error":
            with pytest.raises(OffsetUnitCalculusError):
                op.mul(in1, in2)
        else:
            expected_output = self.QP_(*expected_output)
            assert op.mul(in1, in2).units == expected_output.units
            self.assert_quantity_almost_equal(
                op.mul(in1, in2), expected_output, atol="0.01"
            )

    divisions_with_scalar = [  # without / with autoconvert to plain 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"]),
    ]

    @pytest.mark.parametrize(("input_tuple", "expected_output"), divisions_with_scalar)
    def test_division_with_scalar(self, input_tuple, expected_output):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is tuple:
            in1, in2 = self.QP_(*in1), self.kwargs["non_int_type"](in2)
        else:
            in1, in2 = self.kwargs["non_int_type"](in1), self.QP_(*in2)
        input_tuple = in1, in2  # update input_tuple for better tracebacks
        expected_copy = expected_output.copy()
        for i, mode in enumerate([False, True]):
            self.ureg.autoconvert_offset_to_baseunit = mode
            if expected_copy[i] == "error":
                with pytest.raises(OffsetUnitCalculusError):
                    op.truediv(in1, in2)
            else:
                expected_output = self.QP_(*expected_copy[i])
                assert op.truediv(in1, in2).units == expected_output.units
                self.assert_quantity_almost_equal(op.truediv(in1, in2), expected_output)

    exponentiation = [  # results 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")],
        # ),
    ]

    @pytest.mark.parametrize(("input_tuple", "expected_output"), exponentiation)
    def test_exponentiation(self, input_tuple, expected_output):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is type(in2) is tuple:
            in1, in2 = self.QP_(*in1), self.QP_(*in2)
        elif type(in1) is not tuple and type(in2) is tuple:
            in1, in2 = self.kwargs["non_int_type"](in1), self.QP_(*in2)
        else:
            in1, in2 = self.QP_(*in1), self.kwargs["non_int_type"](in2)
        input_tuple = in1, in2
        expected_copy = expected_output.copy()
        for i, mode in enumerate([False, True]):
            self.ureg.autoconvert_offset_to_baseunit = mode
            if expected_copy[i] == "error":
                with pytest.raises((OffsetUnitCalculusError, DimensionalityError)):
                    op.pow(in1, in2)
            else:
                if type(expected_copy[i]) is tuple:
                    expected_output = self.QP_(*expected_copy[i])
                    assert op.pow(in1, in2).units == expected_output.units
                else:
                    expected_output = expected_copy[i]
                self.assert_quantity_almost_equal(op.pow(in1, in2), expected_output)


class TestNonIntTypeQuantityFloat(_TestBasic):
    kwargs = dict(non_int_type=float)
    SUPPORTS_NAN = True


class TestNonIntTypeQuantityBasicMathFloat(_TestQuantityBasicMath):
    kwargs = dict(non_int_type=float)


class TestNonIntTypeOffsetUnitMathFloat(_TestOffsetUnitMath):
    kwargs = dict(non_int_type=float)


class TestNonIntTypeQuantityDecimal(_TestBasic):
    kwargs = dict(non_int_type=Decimal)
    SUPPORTS_NAN = True


class TestNonIntTypeQuantityBasicMathDecimal(_TestQuantityBasicMath):
    kwargs = dict(non_int_type=Decimal)


class TestNonIntTypeOffsetUnitMathDecimal(_TestOffsetUnitMath):
    kwargs = dict(non_int_type=Decimal)


class TestNonIntTypeQuantityFraction(_TestBasic):
    kwargs = dict(non_int_type=Fraction)
    SUPPORTS_NAN = False


class TestNonIntTypeQuantityBasicMathFraction(_TestQuantityBasicMath):
    kwargs = dict(non_int_type=Fraction)


class TestNonIntTypeOffsetUnitMathFraction(_TestOffsetUnitMath):
    kwargs = dict(non_int_type=Fraction)
