diff --git a/fixedpoint/fixedpoint.py b/fixedpoint/fixedpoint.py index 6f30c20..23d04d3 100644 --- a/fixedpoint/fixedpoint.py +++ b/fixedpoint/fixedpoint.py @@ -676,10 +676,6 @@ def __unsupported(*args: Any, **kwargs: Any) -> NotImplemented: """Unsupported FixedPoint method.""" return NotImplemented - # For future reference, in case division is needed: - # https://courses.cs.washington.edu/courses/cse467/08au/labs/l5/fp.pdf - __floordiv__ = __rfloordiv__ = __ifloordiv__ = __unsupported - __truediv__ = __rtruediv__ = __itruediv__ = __unsupported __matmul__ = __rmatmul__ = __imatmul__ = __unsupported __mod__ = __rmod__ = __imod__ = __unsupported __rlshift__ = __rrshift__ = __unsupported @@ -814,6 +810,58 @@ def __imul__(self: FixedPointType, multiplier: Numeric) -> FixedPointType: self._bits, self._signed, self._m, self._n = self.__mul(other) return self + def __floordiv(numerator: FixedPointType, + denominator: FixedPointType) -> AttrReturn: + """Perform division and return attributes of the result. + This is the + """ + m: int = numerator._m + denominator._n + 1 + n: int = numerator._n + denominator._m + signed: bool = bool(numerator._signed or denominator._signed) + + bits = int((numerator._signedint << (denominator._n + denominator._m)) // denominator._signedint) + return bits & (2**(m + n) - 1), signed, m, n + + def __floordiv__(self: FixedPointType, other: Numeric) -> FixedPointType: + denominator, props = self.__to_FixedPoint_resolved(other) + return self.__class__.__new(*self.__floordiv(denominator), **props) + + def __ifloordiv__(self: FixedPointType, denominator: Numeric) -> FixedPointType: + other = self.__to_FixedPoint(denominator) + self._bits, self._signed, self._m, self._n = self.__floordiv(other) + return self + + def __rfloordiv__(self: FixedPointType, numerator: Numeric) -> FixedPointType: + other = self.__to_FixedPoint(numerator, self._signed) + return self.__class__.__new(*other.__floordiv(self, self.overflow, + self._owarn), + self.overflow, self.rounding, self.str_base, + self.overflow_alert, + self.implicit_cast_alert, + self.mismatch_alert) + + # Both division operators use the maximum precision possible - it's up to the user to truncate + # the result how they see fit. + __truediv = __floordiv + + def __truediv__(self: FixedPointType, other: Numeric) -> FixedPointType: + denominator, props = self.__to_FixedPoint_resolved(other) + return self.__class__.__new(*self.__truediv(denominator), **props) + + def __itruediv__(self: FixedPointType, denominator: Numeric) -> FixedPointType: + other = self.__to_FixedPoint(denominator) + self._bits, self._signed, self._m, self._n = self.__truediv(other) + return self + + def __rtruediv__(self: FixedPointType, numerator: Numeric) -> FixedPointType: + other = self.__to_FixedPoint(numerator, self._signed) + return self.__class__.__new(*other.__truediv(self, self.overflow, + self._owarn), + self.overflow, self.rounding, self.str_base, + self.overflow_alert, + self.implicit_cast_alert, + self.mismatch_alert) + def __pow(self: FixedPointType, exponent: int) -> AttrReturn: """Perform exponentiation and return attributes of the result.""" if not (isinstance(exponent, int) and exponent > 0): diff --git a/tests/test_operators/test_division.py b/tests/test_operators/test_division.py new file mode 100644 index 0000000..7105fad --- /dev/null +++ b/tests/test_operators/test_division.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# This test was not written by the original authors of the fixedpoint library and does not conform +# to its quality standards. + +import pathlib +import sys +sys.path.insert(1, str(pathlib.Path('./../..').resolve())); +import fixedpoint as uut + +from random import * +import math + +# fuzz test divide +for i in range(1000): + x = uut.FixedPoint(0, m=int(random() * 100)+1, n=int(random() * 100)+1, signed=1) + y = uut.FixedPoint(0, m=int(random() * 100)+1, n=int(random() * 100)+1, signed=1) + + # because we are choosing only numbers that are precisely representable in fixed-point, + # no error is introduced at these steps. + x._bits = getrandbits(x._m + x._n + int(x._signed)) + y._bits = getrandbits(y._m + y._n + int(y._signed)) + if (y._bits == 0): y._bits = 1 + d = x / y + + xf = float(x) + yf = float(y) + df = xf / yf + + # TODO: establish actual error bounds + MANTISSABITS = 52 + exp = math.frexp(df)[1] + tol = (2**(exp - MANTISSABITS)) + err = df - float(d) + if (math.fabs(err) > tol): + print(f"division of {x.qformat} / {y.qformat} has error {err}") + print(f"d = {float(d)}, df = {xf / yf}, tol = {tol}") + print()