From f29edc637ca7809b93018cb186258de0bd216c4b Mon Sep 17 00:00:00 2001 From: John Mamish Date: Fri, 8 Dec 2023 02:36:42 -0500 Subject: [PATCH 1/4] division operator wip --- fixedpoint/fixedpoint.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fixedpoint/fixedpoint.py b/fixedpoint/fixedpoint.py index 6f30c20..9d8100e 100644 --- a/fixedpoint/fixedpoint.py +++ b/fixedpoint/fixedpoint.py @@ -814,6 +814,24 @@ 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""" + 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 __truediv__(self: FixedPointType, other: Numeric) -> FixedPointType: + denominator, props = self.__to_FixedPoint_resolved(other) + return self.__class__.__new(*self.__floordiv(denominator), **props) + def __pow(self: FixedPointType, exponent: int) -> AttrReturn: """Perform exponentiation and return attributes of the result.""" if not (isinstance(exponent, int) and exponent > 0): From 7acfa3a333bd868a1e1e992e604d29e4252899f5 Mon Sep 17 00:00:00 2001 From: John Mamish Date: Fri, 8 Dec 2023 11:32:49 -0500 Subject: [PATCH 2/4] add idiv and rdiv operators --- fixedpoint/fixedpoint.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/fixedpoint/fixedpoint.py b/fixedpoint/fixedpoint.py index 9d8100e..ef4ff95 100644 --- a/fixedpoint/fixedpoint.py +++ b/fixedpoint/fixedpoint.py @@ -678,8 +678,6 @@ def __unsupported(*args: Any, **kwargs: Any) -> 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 @@ -821,17 +819,51 @@ def __floordiv(numerator: FixedPointType, n: int = numerator._n + denominator._m signed: bool = bool(numerator._signed or denominator._signed) - bits = int(((numerator._signedint) << (denominator._n + denominator._m)) // denominator._signedint) + 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: + """Full precision reflected subtraction.""" + 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) + def __truediv__(self: FixedPointType, other: Numeric) -> FixedPointType: denominator, props = self.__to_FixedPoint_resolved(other) return self.__class__.__new(*self.__floordiv(denominator), **props) + def __itruediv__(self: FixedPointType, other: Numeric) -> FixedPointType: + denominator, props = self.__to_FixedPoint_resolved(other) + return self.__class__.__new(*self.__floordiv(denominator), **props) + + def __rtruediv__(self: FixedPointType, numerator: Numeric) -> FixedPointType: + """Full precision reflected subtraction.""" + 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) + + 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 __pow(self: FixedPointType, exponent: int) -> AttrReturn: """Perform exponentiation and return attributes of the result.""" if not (isinstance(exponent, int) and exponent > 0): From 870328f92dc2418bc96b9f934729a4e1b43e0b4c Mon Sep 17 00:00:00 2001 From: John Mamish Date: Fri, 8 Dec 2023 12:48:45 -0500 Subject: [PATCH 3/4] clean up true vs floor div --- fixedpoint/fixedpoint.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/fixedpoint/fixedpoint.py b/fixedpoint/fixedpoint.py index ef4ff95..23d04d3 100644 --- a/fixedpoint/fixedpoint.py +++ b/fixedpoint/fixedpoint.py @@ -676,8 +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 __matmul__ = __rmatmul__ = __imatmul__ = __unsupported __mod__ = __rmod__ = __imod__ = __unsupported __rlshift__ = __rrshift__ = __unsupported @@ -814,7 +812,9 @@ def __imul__(self: FixedPointType, multiplier: Numeric) -> FixedPointType: def __floordiv(numerator: FixedPointType, denominator: FixedPointType) -> AttrReturn: - """Perform division and return attributes of the result""" + """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) @@ -832,7 +832,6 @@ def __ifloordiv__(self: FixedPointType, denominator: Numeric) -> FixedPointType: return self def __rfloordiv__(self: FixedPointType, numerator: Numeric) -> FixedPointType: - """Full precision reflected subtraction.""" other = self.__to_FixedPoint(numerator, self._signed) return self.__class__.__new(*other.__floordiv(self, self.overflow, self._owarn), @@ -841,29 +840,28 @@ def __rfloordiv__(self: FixedPointType, numerator: Numeric) -> FixedPointType: 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.__floordiv(denominator), **props) + return self.__class__.__new(*self.__truediv(denominator), **props) - def __itruediv__(self: FixedPointType, other: Numeric) -> FixedPointType: - denominator, props = self.__to_FixedPoint_resolved(other) - return self.__class__.__new(*self.__floordiv(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: - """Full precision reflected subtraction.""" other = self.__to_FixedPoint(numerator, self._signed) - return self.__class__.__new(*other.__floordiv(self, self.overflow, + 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 __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 __pow(self: FixedPointType, exponent: int) -> AttrReturn: """Perform exponentiation and return attributes of the result.""" if not (isinstance(exponent, int) and exponent > 0): From eb439dcbe6870468bf83a212cd6af87ff2ad59be Mon Sep 17 00:00:00 2001 From: John Mamish Date: Fri, 8 Dec 2023 12:49:22 -0500 Subject: [PATCH 4/4] add a cursory division test --- tests/test_operators/test_division.py | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/test_operators/test_division.py 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()