From 3fc38246f11cf7fdbbe6bf305f38c7f4194b1756 Mon Sep 17 00:00:00 2001 From: francof2a Date: Fri, 13 May 2022 08:54:21 -0300 Subject: [PATCH 01/24] Fix wrap fuction over n_word_max (issue #41) --- changelog.txt | 4 ++++ fxpmath/__init__.py | 2 +- fxpmath/utils.py | 2 +- tests/test_issues.py | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index a9adff8..d91d696 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +version 0.4.9 +-------------------------------------------------- +* Fix wrap fuction over n_word_max (issue #41). + version 0.4.8 -------------------------------------------------- * Fix value dtype handling for windows OS and uint as 32 bits. diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index fbe5a84..98a732a 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.8' +__version__ = '0.4.9-dev0' import sys import os diff --git a/fxpmath/utils.py b/fxpmath/utils.py index f751d31..99db2ec 100644 --- a/fxpmath/utils.py +++ b/fxpmath/utils.py @@ -349,7 +349,7 @@ def wrap(x, signed, n_word): m = (1 << n_word) if signed: - x = np.array(x).astype(dtype) & (m - 1) + x = int_array(x).astype(dtype) & (m - 1) x = np.asarray(x).astype(dtype) x = np.where(x < (1 << (n_word-1)), x, x | (-m)) else: diff --git a/tests/test_issues.py b/tests/test_issues.py index 69ba000..15cec87 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -152,6 +152,25 @@ def test_issue_41_v0_4_2(): assert x() == 2 assert y() == 2 + x = Fxp(2, False, 31, 0, overflow='wrap') + y = Fxp(2, False, 32, 0, overflow='wrap') + + assert x() == 2 + assert y() == 2 + + x = Fxp(2.5, signed=True, n_word=31, n_frac=24, overflow='wrap') + y = Fxp(2.5, signed=True, n_word=32, n_frac=24, overflow='wrap') + + assert x() == 2.5 + assert y() == 2.5 + + x = Fxp(2.5, signed=True, n_word=63, n_frac=48, overflow='wrap') + y = Fxp(2.5, signed=True, n_word=64, n_frac=48, overflow='wrap') + + assert x() == 2.5 + assert y() == 2.5 + + def test_issue_42_v0_4_2(): b = Fxp(2, True, 4, 0, overflow='wrap') assert (b + 8)() == -6.0 From 89dc03ade9e3312dce807d71be08af45998aea3f Mon Sep 17 00:00:00 2001 From: francof2a Date: Fri, 13 May 2022 14:06:42 -0300 Subject: [PATCH 02/24] changes in utils.wrap function to apply int_array to signed and not signed values. --- fxpmath/utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/fxpmath/utils.py b/fxpmath/utils.py index 99db2ec..3ea2494 100644 --- a/fxpmath/utils.py +++ b/fxpmath/utils.py @@ -342,19 +342,20 @@ def int_clip(x, val_min, val_max): return x_clipped def wrap(x, signed, n_word): + + m = (1 << n_word) if n_word >= _n_word_max: dtype = object + x = int_array(x).astype(dtype) & (m - 1) else: dtype = int + x = np.array(x).astype(dtype) & (m - 1) + + x = np.asarray(x).astype(dtype) - m = (1 << n_word) if signed: - x = int_array(x).astype(dtype) & (m - 1) - x = np.asarray(x).astype(dtype) - x = np.where(x < (1 << (n_word-1)), x, x | (-m)) - else: - x = np.array(x).astype(dtype) & (m - 1) - x = np.asarray(x).astype(dtype) + x = np.where(x < (1 << (n_word-1)), x, x | (-m)) + return x def get_sizes_from_dtype(dtype): From 0dac8b8ab2cb8148787f6d3c8d761e279dbdba40 Mon Sep 17 00:00:00 2001 From: francof2a Date: Thu, 10 Nov 2022 13:06:18 -0300 Subject: [PATCH 03/24] Fix complex dtype detection (#67) and index in equal (#66) change _qfmt and _fxpfmt to methods --- fxpmath/objects.py | 55 ++++++++++++++++++++++++++------------------ tests/test_issues.py | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/fxpmath/objects.py b/fxpmath/objects.py index 723955e..2a149cc 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -315,12 +315,14 @@ def get_dtype(self, notation=None): self._update_dtype(notation) # update dtype return self._dtype - _qfmt = re.compile(r'(s|u|q|uq|qu)(\d+)(\.\d+)?') - _fxpfmt = re.compile(r'fxp-(s|u)(\d+)/(\d+)(-complex)?') + def _qfmt(self): + return re.compile(r'(s|u|q|uq|qu)(\d+)(\.\d+)?') + def _fxpfmt(self): + return re.compile(r'fxp-(s|u)(\d+)/(\d+)(-complex)?') def _parseformatstr(self, fmt): fmt = fmt.casefold() - mo = self._qfmt.match(fmt) + mo = self._qfmt().match(fmt) if mo: # Q/S notation counts the sign bit as an integer bit, such that # the total number of bits is always int+frac @@ -333,7 +335,7 @@ def _parseformatstr(self, fmt): n_word = n_frac + n_int complex_dtype = False else: - mo = self._fxpfmt.match(fmt) + mo = self._fxpfmt().match(fmt) if mo: signed = mo.group(1) == 's' n_word = int(mo.group(2)) @@ -570,12 +572,12 @@ def reshape(self, shape, order='C'): If an integer, then the result will be a 1-D array of that length. One shape dimension can be -1. In this case, the value is inferred from the length of the array and remaining dimensions. - order : {‘C’, ‘F’, ‘A’}, optional + order : {'C', 'F', 'A'}, optional Read the elements of a using this index order, and place the elements into the reshaped array using this index order. - ‘C’ means to read / write the elements using C-like index order, with the last axis index changing fastest, back to the first axis index changing slowest. - ‘F’ means to read / write the elements using Fortran-like index order, with the first index changing fastest, and the last index changing slowest. - Note that the ‘C’ and ‘F’ options take no account of the memory layout of the underlying array, and only refer to the order of indexing. - ‘A’ means to read / write the elements in Fortran-like index order if a is Fortran contiguous in memory, C-like order otherwise. + 'C' means to read / write the elements using C-like index order, with the last axis index changing fastest, back to the first axis index changing slowest. + 'F' means to read / write the elements using Fortran-like index order, with the first index changing fastest, and the last index changing slowest. + Note that the 'C' and 'F' options take no account of the memory layout of the underlying array, and only refer to the order of indexing. + 'A' means to read / write the elements in Fortran-like index order if a is Fortran contiguous in memory, C-like order otherwise. Returns --- @@ -594,12 +596,12 @@ def flatten(self, order='C'): Parameters --- - order{‘C’, ‘F’, ‘A’, ‘K’}, optional - ‘C’ means to flatten in row-major (C-style) order. - ‘F’ means to flatten in column-major (Fortran- style) order. - ‘A’ means to flatten in column-major order if a is Fortran contiguous in memory, row-major order otherwise. - ‘K’ means to flatten a in the order the elements occur in memory. - The default is ‘C’. + order{'C', 'F', 'A', 'K'}, optional + 'C' means to flatten in row-major (C-style) order. + 'F' means to flatten in column-major (Fortran- style) order. + 'A' means to flatten in column-major order if a is Fortran contiguous in memory, row-major order otherwise. + 'K' means to flatten a in the order the elements occur in memory. + The default is 'C'. Returns --- @@ -750,7 +752,9 @@ def _update_dtype(self, notation=None): self._dtype = 'fxp-{sign}{nword}/{nfrac}{comp}'.format(sign='s' if self.signed else 'u', nword=self.n_word, nfrac=self.n_frac, - comp='-complex' if (self.val.dtype == complex or self.vdtype == complex) else '') + comp='-complex' if (isinstance(self.val, complex) or \ + self.val.dtype == complex or \ + self.vdtype == complex) else '') else: self._dtype = 'fxp-{sign}{nword}/{nfrac}'.format(sign='s' if self.signed else 'u', nword=self.n_word, @@ -1014,7 +1018,7 @@ def uraw(self): """ return np.where(self.val < 0, (1 << self.n_word) + self.val, self.val) - def equal(self, x): + def equal(self, x, index=None): """ Sets the value of the Fxp using the value of other Fxp object. If `x` is not a Fxp, this method set the value just like `set_val` method. @@ -1025,6 +1029,9 @@ def equal(self, x): x : Fxp object, None, int, float, complex, list of numbers, numpy array, str (bin, hex, dec) Value(s) to be stored in fractional fixed-point (base 2) format. + index : int, optional, default=None + Index of the element to be overwritten in list or array of values by `val` input. + Returns --- @@ -1033,10 +1040,15 @@ def equal(self, x): """ if isinstance(x, Fxp): - new_val_raw = x.val * 2**(self.n_frac - x.n_frac) - self.set_val(new_val_raw, raw=True) + if index is None: + raw_val = x.val[index] + else: + raw_val = x.val + + new_val_raw = raw_val * 2**(self.n_frac - x.n_frac) + self.set_val(new_val_raw, raw=True, index=index) else: - self.set_val(x) + self.set_val(x, index=index) return self # behaviors @@ -1857,9 +1869,6 @@ def item(self, *args): items = args[0] return self.astype(item=items) - # ToDo: - # nonzero - def clip(self, a_min=None, a_max=None, **kwargs): from .functions import clip diff --git a/tests/test_issues.py b/tests/test_issues.py index 15cec87..b980158 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -274,3 +274,55 @@ def test_issue_62_v0_4_7(): y[0][0] = y[0][0] + 1.0 assert y[0][0]() == 0.0 + +def test_issue_66_v0_4_8(): + x = Fxp(np.array([1.25, 0.5]), dtype='S8.4') + y = Fxp(np.array([2.25, 1.5]), dtype='S16.6') + # x[0].equal(y[0]) # it does NOT work + # x[0] = y[0] # it works + x.equal(y[0], index=0) # it works + + assert x[0]() == y[0]() + +def test_issue_67_v0_4_8(): + input_size = Fxp(None, dtype='fxp-s32/23') + f = [0,10+7j,20-0.65j,30] + f = Fxp(f, like = input_size) + + def FFT(f): + N = len(f) + if N <= 1: + return f + + # division: decompose N point FFT into N/2 point FFT + even= FFT(f[0::2]) + odd = FFT(f[1::2]) + + # store combination of results + temp = np.zeros(N, dtype=complex) + # temp = Fxp(temp, dtype='fxp-s65/23') + temp = Fxp(temp, dtype='fxp-s65/46') + + for u in range(N//2): + W = Fxp(np.exp(-2j*np.pi*u/N), like=input_size) + temp[u] = even[u] + W* odd[u] + temp[u+N//2] = even[u] - W*odd[u] + + return temp + + # testing the function to see if it matches the manual computation + F_fft = FFT(f) + +def test_issue_73_v0_4_8(): + # single unsigned value does work + a = Fxp(10, False, 14, 3) + b = Fxp(15, False, 14, 3) + c = a - b + assert c() == 0.0 # 0.0 --> correct + + # unsigned list does not work + d = Fxp([10, 21], False, 14, 3) + e = Fxp([15, 15], False, 14, 3) + f = d - e + assert f[0]() == 0.0 # [4095.875 6.0] --> 4095.875 is the upper limit + assert f[1]() == 6.0 \ No newline at end of file From cfc1f9af75b27a8d85b48bb7201800b9381e9c58 Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 20 Dec 2023 17:39:25 -0300 Subject: [PATCH 04/24] add rounding methods notebook to examples --- examples/rounding_methods.ipynb | 456 ++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100755 examples/rounding_methods.ipynb diff --git a/examples/rounding_methods.ipynb b/examples/rounding_methods.ipynb new file mode 100755 index 0000000..7347e2e --- /dev/null +++ b/examples/rounding_methods.ipynb @@ -0,0 +1,456 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 175, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from fxpmath import Fxp" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\tdtype\t\t=\tfxp-s3/2\n", + "\tValue\t\t=\t0.0\n", + "\n", + "\tSigned\t\t=\tTrue\n", + "\tWord bits\t=\t3\n", + "\tFract bits\t=\t2\n", + "\tInt bits\t=\t0\n", + "\tVal data type\t=\t\n", + "\n", + "\tUpper\t\t=\t0.75\n", + "\tLower\t\t=\t-1.0\n", + "\tPrecision\t=\t0.25\n", + "\tOverflow\t=\tsaturate\n", + "\tRounding\t=\ttrunc\n", + "\tShifting\t=\texpand\n", + "\n" + ] + } + ], + "source": [ + "n_frac = 2\n", + "n_int = 0\n", + "n_word = n_int + n_frac\n", + "overflow = 'saturate'\n", + "\n", + "fxp_ref = Fxp(None, signed=True, n_int=n_int, n_frac=n_frac, overflow=overflow)\n", + "\n", + "fxp_ref.info(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 177, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[-1.25 -1.1875 -1.125 -1.0625 -1. -0.9375 -0.875 -0.8125 -0.75\n", + " -0.6875 -0.625 -0.5625 -0.5 -0.4375 -0.375 -0.3125 -0.25 -0.1875\n", + " -0.125 -0.0625 0. 0.0625 0.125 0.1875 0.25 0.3125 0.375\n", + " 0.4375 0.5 0.5625 0.625 0.6875 0.75 0.8125 0.875 0.9375]\n" + ] + } + ], + "source": [ + "ratio = 4\n", + "float_precision = fxp_ref.precision / ratio\n", + "x = np.arange(fxp_ref.lower - (ratio*float_precision), fxp_ref.upper + ratio*float_precision, step=float_precision)\n", + "print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## around" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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dJuBRN9P9T3tc9/TPvet2u5vX50lng50Kfe4M7DfR9v2uldeeYVPP/2UZ5555pz7iI+P54cffvBiVSIiUp/5KrA/BG4xbnFArrX2rOnwupKUlsPL3+4iKS2n1n3dddddJCcnc+2115KTc6q/tLQ0xo0bx4UXXsi4cePYs2cPAB999BEjRoxg8ODBXHbZZRw8eJDU1FReffVV/va3vzFo0CCWL19e67pERKR+89bXuhYBY4G2xph04AkgDMBa+yrwKe6vdO3C/bWu6d7Y7h8+2sLW/UerbJNXWML2jDxcFhwG+raPJDKi8q8r9e/YnCeuOb/S11999VU+//xzvv32Wz7++OOTz//mN7/hlltu4dZbb2XevHnce++9vP/++4waNYrExESMMcyZM4fnnnuOv/71r9x11100a9aM3/72tzX/xUVEpMHxSmBbaydV87oF7vbGtmrqaGEprrKj5S7rflxVYJ+rFStW8N577wEwdepUHnroIcD9NbSbbrqJAwcOUFxcrK9KiYjIOQnoi39Up6qR8AlJaTlMmZNISamLsFAHL04czNBureq8thNfO7vnnnt44IEHuPbaa4mPj+fJJ5+s822LiEj9U++XJh3arRUL74jjgSv6sPCOuDoL64suuojFixcDsHDhQkaNGgVAbm4unTp1AuD1118/2T4yMtLnX9USEZHgVe8DG9yhffelvep0ZP3SSy8xf/58LrzwQt58801efPFFAJ588kluuOEGRo8eTdu2bU+2v+aaa1i6dKlOOhMREY8E9ZS4v6SmpgIwbdo0pk2bBriv3f3NN9+c1fa6667juuuuO+v58847j40bN9ZlmSIiUo8osEVERM7BJxv3k5pdQFyPtj45N0qBLSIiUkOv/5DKEx9uwQCNwnbV6TlSJzSIY9giIiLesnznIf748VbAvcZ2SamLxOTsOt+uAltERMRD327P5PbX19CpZWMahToIMRAW6iCuR5s637amxEVERDzw3y0Z3P32Wvq0j+TN20aQnFVAYnI2cT3a6Bi2iIhIIPhk4wHuW7yOCzq14PXbhtOicRhDm4b7JKhP0JT4OXjppZfo168fU6ZM8XcptRYTE0NWVpa/yxARCVgfrN/HPYvWMqhLS9683R3W/tAwAjsvA+aPh7yDXunuxOU1Fy5c6JX+qmKtxeVy1fl2RETkbP9JSud/lqxnePfW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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'around'\n", + "fxp_var = Fxp(x, rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ceil" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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StdO3Kd1TfFFu39+bxOb9x2ps0xxWSVbDxTjsuAwcE7XNrSJPf1pTwO7t+VP15132tdnzJjB0qVL+eKLL3j//fePP//LX/6SiRMnMmnSJObOncs999zD22+/zahRo0hLS8MYw+zZs3nyySf5xz/+wZ133knr1q359a9/3fAPLiIizY5PAttae0s9r1vgLl+01VBHyqpw1ewtd1n347oCu7GWL1/OW2+9BcCtt97KAw88ALhPQ7v55pvZv38/FRUVOuVKREQapUlf/KM+dY2Ev5GRXcD42WlUVrmIinTw7NihJPTu4Pe+fXPa2d13383999/Pj370I5KTk3nkkUf83raIiISfsF+aNKF3BxbcnsT93x/AgtuT/BbWF110EYsXLwZgwYIFjBo1CoCioiJ69OgBwEsvvXS8fJs2bTw+VUtERCTsAxvcoX3XZWf7dWT93HPPMW/ePAYNGsQrr7zCs88+C8AjjzzCjTfeyOjRo+ncufPx8tdddx1LlizRQWciIuKRkJ4SD5asrCwAJk+ezOTJkwH3tbs///zzU8pef/31XH/99ac8379/f9avX+/PboqISBhRYIuIiDTCB+v3kZV3lKS+nQNybJQCW0REpIFe+jqLP727CQPERO306zFS32gW+7BFRER8JXXHIf7f+5sB9xrblVUu0jLz/N6uAltERMRDX2zNZepLq+jRvgUxkQ4iDERFOkjq28nvbWtKXERExAP/23SAuxauZkDXNrxy2wgyDx8lLTOPpL6dtA9bRESkKfhg/X7uXbyG83u046XbhtOuRRQJraIDEtTf0JR4Izz33HOce+65jB8/PqDtzpgxg5dffhlwn1L25ptvBrR9EZHm6J21e7l70WqG9GrPK1PdYR0MzWOEXXwA3pwCP5sPbbp4Xd0LL7zARx99FPB1we+8886Atici0ty9mZHDb95cx4j4jsyZNIxWMcGLzeYxwl72JOxOg2VPeF3ViZfXfOaZZ7jnnnt49NFHAfj444+55JJLcLlcTJ48mTvvvJPRo0fTv3//k67sdaInn3ySCy64gMGDB/Pggw8CsGvXLq666ioSEhIYPXo027dvB9yrpj311FNefwYREanfohW7+c2b6xh1dmfmTR4e1LCGUB9hf/QgHNhw+td3fwXWfvt41Rz3jzFw5sW1v6frBXD146et8sTLa3bu3Jljx44xbNgw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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'ceil'\n", + "fxp_var = Fxp(x, rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## floor" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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nj3ceOONZGf/WF9KSgqXX345AwYM4PLLL2fv3r0AfPTRR4wYMYLBgwdzxRVXcPDgQZKTk3nttdf45z//yaBBg1ixYkWt+yUiIvWbr27rWgSMBtoYY1KBPwIRANba14AleG7p2oXntq7Jvmj3qY+2sDXtyBnL5BWVsj09D7cFh4Fz2jWjWXTltzj169CcP95wbqXbX3vtNT777DO++eYbPv744+Ov//rXv2bixInccccdzJ49m/vvv5/333+fiy66iISEBIwxzJw5k+eee45//OMf3HPPPTRt2pTf/va31X/jIiLS4PgksK21Y6vYboF7fdFWdR0pKsNdfrbcbT3PzxTYNbVy5UreffddACZMmMDDDz8MeG5Du+222zhw4AAlJSW6vUpERGqkTn/5R1XONBI+Jiklm9tnJlBa5iYi3MG/xwxmaNdWfu/bsdvO7rvvPh588EFuvPFGli1bxpNPPun3tkVEpP6p90uTDu3aigVT4njwqj4smBLnt7C+4IILWLx4MQALFizgoosuAiA3N5eOHTsCMG/evOPlmzVrVqtbtUREpGGp94ENntC+99Kefh1Zv/jii8yZM4cBAwYwf/58/v3vfwPw5JNPcsstt3DxxRfTpk2b4+VvuOEG3nvvPV10JiIiXgnpKfFgSU5OBmDSpElMmjQJ8Hx399dff31a2ZtuuombbrrptNd79+7Nxo0b/dlNERGpRxTYIiIiNfDJxjSSswqI69EmINdGKbBFRESqad73yfzxwy0YICpil1+vkTqmQZzDFhER8ZUVOw/xp4+3Ap41tkvL3CTsyfJ7uwpsERERL32zPYO75q2hY8tGRIU7CDMQEe4grkdrv7etKXEREREvfLElnXsXrqVPu2bMv3MEezILSNiTRVyP1jqHLSIiUhd8svEAv1m8jv4dWzDvzuG0aBTB0CaRAQnqYxTYNXDs6zWHDBnCggULalXXQw89xJIlS7juuuto0qSJ1hcXEaljPli/nwfeWs+QLq2YM/l8vyxv7Y2GEdh56fDOZPjFXGgWW+vqXnnlFT799FOfrAv++uuvc+jQIaKiony2bGlZWRnh4Q3jP62IiD+9k5TKQ+9sYET3s5h1x/k0iQrev60N46Kz5c/B3gRY/rdaV3Xi12v+85//5P777+fpp58G4PPPP+eSSy7B7XYzadIk7rnnHi6++GJ69+590jd7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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'floor'\n", + "fxp_var = Fxp(x, rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## trunc" + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'trunc'\n", + "fxp_var = Fxp(x, rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fix" + ] + }, + { + "cell_type": "code", + "execution_count": 182, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'fix'\n", + "fxp_var = Fxp(x, rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## nearest\n", + "\n", + "Rounding to plus infinity" + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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z2ld432x3j99soGNe45U2Sa/qJTN+/JxW3AY6N2+Gc1iKz/tqW+H5vzxhrMrXT558mS+/PJLvvvuOz799NPj7//qV7/ijjvu4M4772TGjBk8+OCDfPjhhwwdOpSUlBSMMUybNo3nnnuOF154gXvvvZemTZvym9/8pva/uIiIhB2fBLa1dlQ1yy1wvy/WVVtHispwl+8td1vP66oCu66WLl3KBx98AMDYsWN55JFHAM9paLfddht79+6lpKREp1yJiEidNOibf1SnqpHwj9Iy8xgzLYXSMjdRkQ7+MXIAiV1b1XttP5529sADD/Dwww9z4403snDhQp588sl6X7eIiISekL80aWLXVsy+O4mHr+zF7LuT6i2sL7zwQubNmwfA7NmzGTp0KACHDx+mY8eOALzxxhvH2zdr1qzGp2qJiIiEfGCDJ7Tvv6RHvY6sX375ZWbOnMl5553HW2+9xT/+8Q8AnnzySW655RaGDRtG27Ztj7e/4YYbmD9/vg46ExGRGgnqKfFAycjIAGDcuHGMGzcO8Ny7+9tvvz2t7U033cRNN9102vtnnXUWa9eurc8yRUQkhCiwRURE6uCztXvIyC0kqXtbvxwbpcAWERGppTeWZPDHjzdggJio7fV6jNSPwmIftoiIiK8s3naAP3+6EfBcY7u0zE1Kem69r1eBLSIiUkPfbc5mwhsr6NiyETGRDiIMREU6SOrept7XrSlxERGRGvjPhn3cP2clvdo34627BpOeU0hKei5J3dtoH7aIiEhD8Nnavfx63irO6diCN+4aRItGUSQ2ifZLUP9IU+J18PLLL9OnTx/GjBnj1/VOnjyZN998E/CcUvbee+/5df0iIuHoo9W7eWDuSvp3bslbEzxhHQjhMcLO3wfvjYdfzIJm8V5399prr/HFF1/4/brg9957r1/XJyIS7t5Ly+K3761hcEJrpt95AU1iAheb4THCXvQc7EyBRX/1uquTb6/50ksv8eCDD/LUU08B8NVXXzF8+HDcbjfjxo3j3nvvZdiwYZx11lmn3NnrZM899xznnnsu/fr149FHHwVgx44dXH311SQmJjJs2DC2bt0KeK6a9vzzz3v9O4iISPXmLtvJb99bw9AebZk5blBAwxqCfYT9xaOwb13ly3f+ANaeeL1iuufHGOhyUcWfaX8uXPNspV2efHvNtm3bcvToUS644AKG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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'ceil'\n", + "fxp_var = Fxp(x-2**(-2*n_frac), rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rounding to zero" + ] + }, + { + "cell_type": "code", + "execution_count": 184, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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PvroI4YNG8bgwYO56qqrOHToEKmpqUybNo2XXnqJQYMGsXz58lrXJSIiDZuvfta1EBgJtDXGpANPAeEA1tppwBI8P+naiednXRN98b5//GgzW/Yfq7RNbkEx2w7m4rbgMNC3fRRRkRX/pKlfx+Y8ddP5FT4/bdo0PvvsM77++ms+/vjjU48/8MAD3HXXXdx9993Mnj2bhx56iPfff59LLrmEpKQkjDHMnDmT559/nr///e/cd999NGvWjF//+tfV/+AiIhJyfBLY1toxVTxvgft98V7VdaygBHfp3nK39dyvLLBrasWKFbz33nsAjB8/nsceewzw/Aztjjvu4MCBAxQVFennVCIiUiP1+uQfValsJHxSclo242YmUVziJjzMwT9HDya2W6s6r+3kz84efPBBfvWrX3HzzTeTkJDA008/XefvLSIiDU+DX5o0tlsr5k+O51fX9GH+5Pg6C+uLLrqIRYsWATB//nwuueQSAHJycujUqRMA8+bNO9U+KirKpz/VEhGRhq3BBzZ4Qvv+y3vV6cj65ZdfZs6cOQwYMIA333yTf/7znwA8/fTT3HbbbYwYMYK2bduean/TTTexePFiHXQmIiJeCeop8UBJTU0FYMKECUyYMAHwnLv7q6++OqvtLbfcwi233HLW4+eeey4bNmyoyzJFRKQBUWCLiIjUwCcb9pOalU98z7Z+OTZKgS0iIlJN875L5akPN2OARuE76/QYqZNCYh+2iIiIryzfcZg/fbwF8KyxXVziJml3Vp2/rwJbRETES19vy2DSvDV0atmYRmEOnAbCwxzE92xT5++tKXEREREv/HfzQe5fsJY+7aN4855h7M7MJ2l3FvE922gftoiISH3wyYYDPLzoey7o1IJ59wylReNwYptG+CWoT9KUeA28/PLLnHfeeYwbN65W/aSmprJgwQIfVSUiInXhg3X7eHDhWgZ1acmbkzxhHQihEdi5B2HOKMg95JPuTp5ec/78+bXqp7LALikpqVXfIiJSe/9JTud/3l7H0B6tmXfP0Do5F4W3QiOwlz0Pe5Jg2d9q3VXZ02u+9NJLPPTQQzzzzDMAfP7551x66aW43W4mTJjAfffdx4gRIzj33HPPOLPXSY8//jjLly9n0KBBvPTSS8ydO5fbbruNm266iWuuuYaEhARuvPHGU+0feOAB5s6dC3gWannqqacYMmQI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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'trunc'\n", + "fxp_var = Fxp(x + np.sign(x)*2**(-2*n_frac), rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rounding to infinity" + ] + }, + { + "cell_type": "code", + "execution_count": 185, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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kiWMHj2a4cOHc9lll3HgwAFSU1OZPn06L774IsOGDWP58uV1rktEREKbr07rWgiMBdobY9KBJ4AIAGvtdOATPKd07cJzWtdkX3zuH5dsYWvGkSrb5BeVsj0zH7cFh4GBHaOJjqr8lKbBnVvyxPVnVfr69OnT+eyzz/jmm2/46KOPTjz/y1/+kjvuuIM777yTOXPm8MADD/D+++9zwQUXkJSUhDGGWbNm8eyzz/L8889z77330qJFC37961/X/IuLiEij45PAttbeVs3rFrjPF59VU0eKynCX7y13W8/jqgK7tlasWMF7770HwMSJE3n44YcBz2lot956K/v376ekpESnU4mISK006It/VKeqkfBxyWm5TJiVRGmZm4hwB/8cN5yRPdrUe23HTzu7//77eeihh7jhhhtISEjgySefrPfPFhGR0BPyS5OO7NGG+VPjeeiKAcyfGl9vYX3eeeexaNEiAObPn88FF1wAQF5eHl26dAHgtddeO9E+Ojrap6dqiYhIaAv5wAZPaN93Sd96HVm/9NJLzJ07lyFDhvDGG2/wz3/+E4Ann3ySm2++mQsvvJD27dufaH/99dezePFiHXQmIiJeCeop8UBJTU0FYNKkSUyaNAnwXLv766+/PqPtjTfeyI033njG8/3792fjxo31WaaIiIQQBbaIiEgtfLwxg9ScQuJ7t/fLsVEKbBERkRp67btUnvhwCwZoErGrXo+ROq5R7MMWERHxleU7D/Knj7YCnjW2S8vcJKXk1PvnKrBFRES89M32LKa8toYurZvSJNxBmIGIcAfxvdvV+2drSlxERMQLX2zJ5L4FaxnQMZo37hpNSnYhSSk5xPdup33YIiIiDcHHG/fzq0XrOLtLK167axStmkYwsnmkX4L6OE2J18JLL73EoEGDmDBhQp36SU1NZcGCBT6qSkRE6sMH6/dx/8K1DOvWmjemeMI6EBpHYOdnwtyrIf+AT7o7fnnN+fPn16mfqgK7rKysTn2LiEjdvZOczv+9tZ5Rvdry2l2j6uVaFN5qHIG97FnYkwTL/lbnrk6+vOaLL77IAw88wFNPPQXA559/zkUXXYTb7WbSpEnce++9XHjhhfTv3/+UK3sd98gjj7B8+XKGDRvGiy++yLx587j55pu5/vrrueKKK0hISOC666470f6Xv/wl8+bNAzwLtTzxxBOMGDGCc845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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'trunc'\n", + "fxp_var = Fxp(x + 2*np.sign(x)*2**(-2*n_frac), rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rounding to minus infinity" + ] + }, + { + "cell_type": "code", + "execution_count": 186, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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wo033khOznf9paWlcfnll3PhhRdy+eWXc+DAAQDee+89xo4dy4gRI7jiiis4evQoqampvPDCC/ztb39j+PDhrFmzpsF1iYhI0+ary7pWAJOAzsaYdOC3QASAtfYFYBWeS7r24bmsa7Yv1vu793by9eETNbbJLypld0Y+bgsOA+fERhMdVf0lTkO7t+W33zuv2uUvvPACH330Ef/97395//33T7/+05/+lBkzZjBz5kwWLVrEAw88wNtvv83FF19MUlISxhgWLFjAU089xV//+lfuvfde2rRpw89//vO6f3AREWl2fBLY1toptSy3wH2+WFddnSgqw11+tNxtPc9rCuz6Wrt2LW+++SYA06dP55FHHgE8l6HddtttHDlyhJKSEl1eJSIi9RLUN/+oTU17wt9KTsth2oIkSsvcRIQ7+MfkEYzq06HRa/v2srP777+fn/3sZ9x4440kJCTw+OOPN/q6RUSk6WnyU5OO6tOB5XfF87OrhrD8rvhGC+vx48ezcuVKAJYvX87FF18MQF5eHj169ABg6dKlp9tHR0c36FItERFpXpp8YIMntO+7dGCj7lk/88wzLF68mAsvvJBly5bxj3/8A4DHH3+cW265hQkTJtC5c+fT7b/3ve/x1ltv6aQzERHxSkgPiQdKamoqALNmzWLWrFmA597dn3/+eaW2N910EzfddFOl1wcPHsy2bdsas0wREWlCFNgiIiL18MG2w6RmFxLfv7Nfzo1SYIuIiNTR0q9S+e27OzFAi4h9jXqO1LeaxTFsERERX1mz9xi/f/9rwDPHdmmZm6SU7EZfrwJbRETES//dncmdSzfSo31LWoQ7CDMQEe4gvn+nRl+3hsRFRES88J+dGdz3yiaGxEaz7I6xpGQVkpSSTXz/TjqGLSIiEgw+2HaEB1du5vwe7Vh6xxjatYxgVOtIvwT1txTY9fDt7TVHjhzJ8uXLG9TXww8/zKpVq7juuuto3bq15hcXEQky72w5xEOvbmFk7w4snj26Uaa39kbzCOz8DHh9NvxoCUTHNLi7559/ng8//NAn84K/+OKLHDt2jBYtWvhs2tKysjLCw5vHf1oRkcb0enI6D7++lbH9OrJw5mhatwjcv63N46Sz1U/BgSRY/ecGd1Xx9pp/+9vfeOCBB5g7dy4AH3/8MZdccglut5tZs2Zx7733MmHCBAYPHnzGnb2+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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rounding = 'floor'\n", + "fxp_var = Fxp(x+2**(-2*n_frac), rounding=rounding, like=fxp_ref)\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "plt.plot(x, x, marker='.', label='float')\n", + "plt.plot(x, fxp_var, marker='*', label=f'fxp {rounding}')\n", + "plt.grid()\n", + "plt.title(f'{rounding} rounding')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.13 ('dev')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "e1489a44d9d2cc8502fc38ac3fffdaa99c0b3f818da830c696c3587ea31e036b" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From a497743c372bd66bf3eb00ab5baea602ea773c4c Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 20 Dec 2023 18:03:01 -0300 Subject: [PATCH 05/24] update gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index b50126a..6449502 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,7 @@ results/* # !.vscode/launch.json # !.vscode/extensions.json *.code-workspace +.devcontainer # Local History for Visual Studio Code .history/ @@ -157,4 +158,9 @@ docs/figs/*.svg temp.py dev_* +# temp +temp/ +*.temp +# docker +docker/ From 0b7d3261748a12d31a758a8c8cf41476948417b9 Mon Sep 17 00:00:00 2001 From: francof2a Date: Thu, 21 Dec 2023 10:53:36 -0300 Subject: [PATCH 06/24] v0.4.9-dev1: Inaccuracy flag is propagated to a new Fxp or resulting Fxp if one or more input Fxp are inaccurate (issue #48) --- changelog.txt | 1 + fxpmath/__init__.py | 2 +- fxpmath/functions.py | 8 ++++++++ fxpmath/objects.py | 9 +++++++-- tests/test_issues.py | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index d91d696..a4d14aa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ version 0.4.9 -------------------------------------------------- * Fix wrap fuction over n_word_max (issue #41). +* Inaccuracy flag is propagated to a new Fxp or resulting Fxp if one or more input Fxp are inaccurate (issue #48). version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index 98a732a..443f904 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev0' +__version__ = '0.4.9-dev1' import sys import os diff --git a/fxpmath/functions.py b/fxpmath/functions.py index c95c1f7..9551141 100644 --- a/fxpmath/functions.py +++ b/fxpmath/functions.py @@ -129,6 +129,10 @@ def _function_over_one_var(repr_func, raw_func, x, out=None, out_like=None, sizi else: z = Fxp(val, signed=signed, n_int=n_int, n_frac=n_frac, like=out_like, raw=raw) + # propagate inaccuracy from argument + if x.status['inaccuracy']: + z.status['inaccuracy'] = True + return z def _function_over_two_vars(repr_func, raw_func, x, y, out=None, out_like=None, sizing='optimal', method='raw', optimal_size=None, **kwargs): @@ -177,6 +181,10 @@ def _function_over_two_vars(repr_func, raw_func, x, y, out=None, out_like=None, else: z = Fxp(val, signed=signed, n_int=n_int, n_frac=n_frac, like=out_like, raw=raw, config=config) + # propagate inaccuracy from arguments + if x.status['inaccuracy'] or y.status['inaccuracy']: + z.status['inaccuracy'] = True + return z def fxp_like(x, val=None): diff --git a/fxpmath/objects.py b/fxpmath/objects.py index 2a149cc..b3e43bb 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -317,6 +317,7 @@ def get_dtype(self, notation=None): def _qfmt(self): return re.compile(r'(s|u|q|uq|qu)(\d+)(\.\d+)?') + def _fxpfmt(self): return re.compile(r'fxp-(s|u)(\d+)/(\d+)(-complex)?') @@ -616,7 +617,7 @@ def flatten(self, order='C'): # methods about value - def _format_inupt_val(self, val, return_sizes=False, raw=False): + def _format_inupt_val(self, val, return_sizes=False, raw=False, set_inaccuracy=True): vdtype = None signed = self.signed n_word = self.n_word @@ -637,6 +638,11 @@ def _format_inupt_val(self, val, return_sizes=False, raw=False): if self.signed is None: self.signed = val.signed if self.n_word is None: self.n_word = val.n_word if self.n_frac is None: self.n_frac = val.n_frac + + # check inaccuracy + if set_inaccuracy and val.status['inaccuracy']: + self.status['inaccuracy'] = True + # force return raw value for better precision val = val.val * 2**(self.n_frac - val.n_frac) raw = True @@ -691,7 +697,6 @@ def _format_inupt_val(self, val, return_sizes=False, raw=False): val = int(val * 2**(self.n_frac)) raw = True - else: raise ValueError('Not supported input type: {}'.format(type(val))) diff --git a/tests/test_issues.py b/tests/test_issues.py index b980158..c84957d 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -208,6 +208,24 @@ def test_issue_44_v0_4_3(): b = Fxp(2**64+6, False, 64, 0, overflow='wrap', scaling=2, bias=8) assert b() == 2**64+6 +def test_issue_48_v0_4_8(): + """ + https://github.com/francof2a/fxpmath/issues/48 + Flags not propagated + """ + a = Fxp(-2., dtype="fxp-s24/8") + b = Fxp(2.15, dtype="fxp-s24/8") + assert b.status['inaccuracy'] + + # inaccuracy in b must be propagated to c + c = a + b + assert c.status['inaccuracy'] + + # add extra test using a inaccurate Fxp to set a new Fxp + d = Fxp(c) + assert d.status['inaccuracy'] + + def test_issue_53_v0_4_5(): x = Fxp(2j, dtype = 'fxp-u4/0-complex') z = x/2 From 9742a1d8775977932f99fcada51b79ae2b2e9cfb Mon Sep 17 00:00:00 2001 From: francof2a Date: Thu, 21 Dec 2023 13:39:20 -0300 Subject: [PATCH 07/24] v0.4.9-dev2: New `from_bin` method and function. Set value or create new Fxp from a binary string (issue #49). --- changelog.txt | 1 + fxpmath/__init__.py | 3 ++- fxpmath/functions.py | 3 +++ fxpmath/objects.py | 30 ++++++++++++++++++++++- fxpmath/utils.py | 22 ++++++++++++++++- tests/test_functions.py | 34 ++++++++++++++++++++++++++ tests/test_issues.py | 54 ++++++++++++++++++++++++++++++++++++++++- tests/test_utils.py | 40 +++++++++++++++++++++++++++++- 8 files changed, 182 insertions(+), 5 deletions(-) diff --git a/changelog.txt b/changelog.txt index a4d14aa..2513d28 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ version 0.4.9 -------------------------------------------------- * Fix wrap fuction over n_word_max (issue #41). * Inaccuracy flag is propagated to a new Fxp or resulting Fxp if one or more input Fxp are inaccurate (issue #48). +* New `from_bin` method and function. Set value or create new Fxp from a binary string (issue #49). version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index 443f904..9503232 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev1' +__version__ = '0.4.9-dev2' import sys import os @@ -58,6 +58,7 @@ from .functions import ( fxp_like, fxp_sum, + from_bin, fxp_max, fxp_min, add, diff --git a/fxpmath/functions.py b/fxpmath/functions.py index 9551141..a6df81c 100644 --- a/fxpmath/functions.py +++ b/fxpmath/functions.py @@ -285,6 +285,9 @@ def fxp_sum(x, sizes='best_sizes', axis=None, dtype=None, out=None, vdtype=None) return sum_along_axis +def from_bin(x, **kwargs): + return Fxp(utils.add_binary_prefix(x), **kwargs) + @implements(np.max) def fxp_max(x, axis=None, out=None, out_like=None, sizing='optimal', method='raw', **kwargs): """ diff --git a/fxpmath/objects.py b/fxpmath/objects.py index b3e43bb..6016deb 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -1518,7 +1518,7 @@ def info(self, verbose=1): # base representations - def bin(self, frac_dot=False): + def bin(self, frac_dot=False, prefix=None): if frac_dot: n_frac_dot = self.n_frac else: @@ -1538,6 +1538,12 @@ def bin(self, frac_dot=False): rval = utils.complex_repr(real_val, imag_val) else: rval = utils.binary_repr(int(self.val), n_word=self.n_word, n_frac=n_frac_dot) + + # add prefix if it's necessary + bin_prefix = prefix if prefix is not None else self.config.bin_prefix + if bin_prefix is not None: + rval = str(bin_prefix) + rval + return rval def hex(self, padding=True): @@ -1582,6 +1588,10 @@ def base_repr(self, base, frac_dot=False): rval = utils.base_repr(int(self.val), base=base, n_frac=n_frac_dot) return rval + def from_bin(self, val, raw=False): + self.set_val(utils.add_binary_prefix(val), raw=raw) + return self + # copy def copy(self): return copy.copy(self) @@ -1972,6 +1982,9 @@ def __init__(self, **kwargs): if isinstance(self.template, Config): self.__dict__ = copy.deepcopy(self.template.__dict__) + # prefixes + self.bin_prefix = kwargs.pop('bin_prefix', None) + # --- # properties # --- @@ -2210,6 +2223,21 @@ def dtype_notation(self, val): else: raise ValueError('dtype_notation must be str type with following valid values: {}'.format(self._dtype_notation_list)) + @property + def bin_prefix(self): + return self._bin_prefix + + @bin_prefix.setter + def bin_prefix(self, prefix): + if prefix is not None and not isinstance(prefix, str): + print("Warning: the prefix should be a string, converted to string automatically!") + prefix = str(prefix) + + if prefix not in [None, 'b', '0b', 'B', '0B']: + print(f"Warning: the prefix {prefix} is not a common prefix for binary values!") + + self._bin_prefix = prefix + # endregion # --- diff --git a/fxpmath/utils.py b/fxpmath/utils.py index 3ea2494..a86a659 100644 --- a/fxpmath/utils.py +++ b/fxpmath/utils.py @@ -39,7 +39,7 @@ #%% def array_support(func): def iterator(*args, **kwargs): - if isinstance(args[0], (list, np.ndarray)) and args[0].ndim > 0: + if isinstance(args[0], (list, np.ndarray)) and np.asarray(args[0]).ndim > 0: vals = [] for v in args[0]: vals.append(iterator(v, *args[1:], **kwargs)) @@ -263,6 +263,26 @@ def base_repr(x, n_word=None, base=2, n_frac=None): val = np.base_repr(x, base=base) return val +@array_support +def add_binary_prefix(x): + if isinstance(x, str): + # add prefix + if x[0].lower() == 'b': + x = '0b' + x[1:] + elif len(x) > 1 and x[1].lower() == 'b': + x = '0b' + x[2:] + else: + x = '0b' + x + + # check valid characters + invalid_chars = set(x[2:]) - {'0', '1', '.'} + if len(invalid_chars) > 0: + raise ValueError(f"Binary string has invalid characters: {invalid_chars}") + else: + raise ValueError("Binary value must be a string!") + + return x + def complex_repr(r, i): r = np.asarray(r) i = np.asarray(i) diff --git a/tests/test_functions.py b/tests/test_functions.py index 1922ae2..93180f1 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -49,3 +49,37 @@ def test_fxp_sum(): y = fxp.fxp_sum(x, axis=1) assert (y() == np.sum(vals, axis=1)).all() + +def test_from_bin(): + x = from_bin('0', signed=False) + assert x() == 0 + + x = fxp.from_bin('1', signed=False) + assert x() == 1 + + x = fxp.from_bin('011') + assert x() == 3 + + x = fxp.from_bin('111') + assert x() == -1 + + x = fxp.from_bin('111', signed=False) + assert x() == 7 + + x = fxp.from_bin('1.11') + assert x() == -0.25 + + x = fxp.from_bin('0b1.11', signed=False) + assert x() == 1.75 + + x = fxp.from_bin('0.11') + assert x() == 0.75 + + x = fxp.from_bin('01100100.01') + assert x() == 100.25 + assert x.n_word == 10 and x.n_frac == 2 + + x = fxp.from_bin('01100100.01', dtype='fxp-s16/4') + assert x() == 100.25 + assert x.n_word == 16 and x.n_frac == 4 + \ No newline at end of file diff --git a/tests/test_issues.py b/tests/test_issues.py index c84957d..daab2ab 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -4,6 +4,7 @@ import fxpmath as fxp from fxpmath.objects import Fxp, Config +from fxpmath import functions import numpy as np @@ -210,8 +211,8 @@ def test_issue_44_v0_4_3(): def test_issue_48_v0_4_8(): """ - https://github.com/francof2a/fxpmath/issues/48 Flags not propagated + https://github.com/francof2a/fxpmath/issues/48 """ a = Fxp(-2., dtype="fxp-s24/8") b = Fxp(2.15, dtype="fxp-s24/8") @@ -225,6 +226,57 @@ def test_issue_48_v0_4_8(): d = Fxp(c) assert d.status['inaccuracy'] +def test_issue_49_v0_4_8(): + """ + Reversal of .bin() + https://github.com/francof2a/fxpmath/issues/49 + """ + # Method 1 + x1 = Fxp(3.4) + x_bin = x1.bin() + x2 = Fxp('0b' + x_bin, like=x1) + assert x1 == x2 + + # Method 2 + x_bin = x1.bin(frac_dot=True) + x2 = Fxp('0b' + x_bin) + assert x1 == x2 + + # Method 3 + x_bin = x1.bin() + x2 = Fxp(like=x1).from_bin(x_bin) + assert x1 == x2 + + x_bin = x1.bin(frac_dot=True) + x2 = Fxp(like=x1).from_bin(x_bin) + assert x1 == x2 + + # Method 4 + x_bin = x1.bin(frac_dot=True) + x2 = functions.from_bin(x_bin) + assert x1 == x2 + + # alternatives to get binary string with prefix + x_bin = x1.bin(frac_dot=True, prefix='0b') + x2 = Fxp(x_bin) + assert x1 == x2 + + x1.config.bin_prefix = '0b' + x_bin = x1.bin(frac_dot=True) + x2 = Fxp(x_bin) + assert x1 == x2 + + # test negative value + x1 = Fxp(-3.4) + x_bin = x1.bin(frac_dot=True) + x2 = functions.from_bin(x_bin) + assert x1 == x2 + + # test raw value + x_bin = x1.bin() + x2 = functions.from_bin(x_bin, raw=True, like=x1) + assert x1 == x2 + def test_issue_53_v0_4_5(): x = Fxp(2j, dtype = 'fxp-u4/0-complex') diff --git a/tests/test_utils.py b/tests/test_utils.py index a596b35..3ba0cad 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -105,7 +105,6 @@ def test_base_repr(): assert base_repr(30, base=16) == '1E' assert base_repr(-30, base=16) == '-1E' - def test_bits_len(): assert bits_len(1) == 1 assert bits_len(-1) == 1 @@ -114,3 +113,42 @@ def test_bits_len(): assert bits_len(32) == 6 assert bits_len(-32) == 6 assert bits_len(-33) == 7 + +def test_add_binary_prefix(): + # single values + assert add_binary_prefix('0') == '0b0' + assert add_binary_prefix('1') == '0b1' + assert add_binary_prefix('b0') == '0b0' + assert add_binary_prefix('b1') == '0b1' + assert add_binary_prefix('0b0') == '0b0' + assert add_binary_prefix('0b1') == '0b1' + + assert add_binary_prefix('0110') == '0b0110' + assert add_binary_prefix('b1111') == '0b1111' + + assert add_binary_prefix('01.001') == '0b01.001' + assert add_binary_prefix('00.000') == '0b00.000' + + # list and arrays + assert np.all(add_binary_prefix(['110', '001']) == np.array(['0b110', '0b001'])) + assert np.all( + add_binary_prefix([['110', '001'], ['b111', '0b101']]) == \ + np.array([['0b110', '0b001'], ['0b111', '0b101']]) + ) + assert np.all(add_binary_prefix(np.array(['110', '001'])) == np.array(['0b110', '0b001'])) + assert np.all( + add_binary_prefix(np.array([['110', '001'], ['b111', '0b101']])) == \ + np.array([['0b110', '0b001'], ['0b111', '0b101']]) + ) + + # test wrong input formats + inputs_list = [0, 1, 3, '3', '102', '0b1102'] + for i in inputs_list: + try: + _ = add_binary_prefix(i) + except: + assert True + else: + print(f"input processed right when should be wrong: {i}") + assert False + From c5a8da3a2e2998d2362970c2f5be416ef6d9cc5b Mon Sep 17 00:00:00 2001 From: francof2a Date: Thu, 21 Dec 2023 16:38:38 -0300 Subject: [PATCH 08/24] Support to complex binary strings as input format --- fxpmath/objects.py | 25 ++++++------- fxpmath/utils.py | 86 +++++++++++++++++++++++++++++++++++++------- tests/test_basic.py | 15 ++++++++ tests/test_issues.py | 6 ++++ tests/test_utils.py | 21 +++++++++++ 5 files changed, 128 insertions(+), 25 deletions(-) diff --git a/fxpmath/objects.py b/fxpmath/objects.py index 6016deb..c294931 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -1523,30 +1523,31 @@ def bin(self, frac_dot=False, prefix=None): n_frac_dot = self.n_frac else: n_frac_dot = None + + # set prefix if it's necessary + prefix = prefix if prefix is not None else self.config.bin_prefix + if prefix is not None: + if isinstance(prefix, bool) and prefix == True: + prefix = '0b' # default binary prefix if isinstance(self.val, (list, np.ndarray)) and self.val.ndim > 0: if self.vdtype == complex: - real_val = [utils.binary_repr(utils.int_array(val.real), n_word=self.n_word, n_frac=n_frac_dot) for val in self.val] - imag_val = [utils.binary_repr(utils.int_array(val.imag), n_word=self.n_word, n_frac=n_frac_dot) for val in self.val] + real_val = [utils.binary_repr(utils.int_array(val.real), n_word=self.n_word, n_frac=n_frac_dot, prefix=prefix) for val in self.val] + imag_val = [utils.binary_repr(utils.int_array(val.imag), n_word=self.n_word, n_frac=n_frac_dot, prefix=prefix) for val in self.val] rval = utils.complex_repr(real_val, imag_val) else: - rval = [utils.binary_repr(utils.int_array(val), n_word=self.n_word, n_frac=n_frac_dot) for val in self.val] + rval = [utils.binary_repr(utils.int_array(val), n_word=self.n_word, n_frac=n_frac_dot, prefix=prefix) for val in self.val] else: if self.vdtype == complex: - real_val = utils.binary_repr(utils.int_array(self.val.real), n_word=self.n_word, n_frac=n_frac_dot) - imag_val = utils.binary_repr(utils.int_array(self.val.imag), n_word=self.n_word, n_frac=n_frac_dot) + real_val = utils.binary_repr(utils.int_array(self.val.real), n_word=self.n_word, n_frac=n_frac_dot, prefix=prefix) + imag_val = utils.binary_repr(utils.int_array(self.val.imag), n_word=self.n_word, n_frac=n_frac_dot, prefix=prefix) rval = utils.complex_repr(real_val, imag_val) else: - rval = utils.binary_repr(int(self.val), n_word=self.n_word, n_frac=n_frac_dot) - - # add prefix if it's necessary - bin_prefix = prefix if prefix is not None else self.config.bin_prefix - if bin_prefix is not None: - rval = str(bin_prefix) + rval + rval = utils.binary_repr(int(self.val), n_word=self.n_word, n_frac=n_frac_dot, prefix=prefix) return rval - def hex(self, padding=True): + def hex(self, padding=True, prefix=None): if padding: hex_n_word = self.n_word else: diff --git a/fxpmath/utils.py b/fxpmath/utils.py index a86a659..d737bf9 100644 --- a/fxpmath/utils.py +++ b/fxpmath/utils.py @@ -64,8 +64,12 @@ def twos_complement_repr(val, nbits): def strbin2int(x, signed=True, n_word=None, return_sizes=False): - x = x.split('b')[-1] # remove 0b at the begining - x = x.replace(' ', '') # remove spacing + x = x.replace('0b', 'b').replace('b', '') # remove 0b at the begining + x = x.replace(' ', '').replace('+', '') # remove spacing and + + + # get original sign of number + sign = -1 if x[0] == '-' else 1 + x = x.replace('-', '') if n_word is None: n_word = len(x) @@ -78,12 +82,21 @@ def strbin2int(x, signed=True, n_word=None, return_sizes=False): raise ValueError('binary val has more bits ({}) than word ({})!'.format(len(x), n_word)) if signed: + if len(x) < 2: + raise('Signed binary with no enough amount of bits!') + val = int(x[1:], 2) if x[0] == '1': val = -1*( (1 << (n_word - 1)) - val) + + if sign == -1: + print('Warning: you are using a negative sign (-) with an already binary signed. The value conversion could be wrong!') else: val = int(x, 2) + # set same original sign + val = sign * val + if return_sizes: return val, signed, n_word else: @@ -110,6 +123,35 @@ def strbin2float(x, signed=True, n_word=None, n_frac=None, return_sizes=False): else: return val +def strbin2complex(x, signed=True, n_word=None, n_frac=None, return_sizes=False): + x = x.replace(' ', '').replace('+', '|').replace('-', '|-').split('|') + + if len(x) == 1 and isinstance(x[0], str) and 'j' in x[0]: + # imaginary number + val, signed, n_word, n_frac = strbin2float(x[0].replace('j', ''), signed, n_word, n_frac, return_sizes=True) + val = 1j*val + elif len(x) == 1 and isinstance(x[0], str) and not 'j' in x[0]: + # real number + val, signed, n_word, n_frac = strbin2float(x[0], signed, n_word, n_frac, return_sizes=True) + val = val + 1j*0 + elif len(x) == 2 and isinstance(x, list) and not 'j' in x[0] and 'j' in x[1]: + # complex + val_real, signed_real, n_word_real, n_frac_real = strbin2float(x[0], signed, n_word, n_frac, return_sizes=True) + val_imag, signed_imag, n_word_imag, n_frac_imag = strbin2float(x[1].replace('j', ''), signed, n_word, n_frac, return_sizes=True) + val = val_real + 1j*val_imag + + signed = signed_real or signed_imag + n_word = max(n_word_real, n_word_imag) + n_frac = max(n_frac_real, n_frac_imag) + else: + raise ValueError(f"Wrong complex format of binary string!") + + if return_sizes: + return val, signed, n_word, n_frac + else: + return val + + def strhex2int(x, signed=True, n_word=None, return_sizes=False): x = x.replace('0x', '') if n_word is None: @@ -171,9 +213,16 @@ def str2num(x, signed=True, n_word=None, n_frac=None, base=10, return_sizes=Fals # binary if '.' in x or (n_frac is not None and n_frac > 0): # fractional binary - val, signed, n_word, n_frac = strbin2float(x, signed, n_word, n_frac, return_sizes=True) + if 'j' in x: + val, signed, n_word, n_frac = strbin2complex(x, signed, n_word, n_frac, return_sizes=True) + else: + val, signed, n_word, n_frac = strbin2float(x, signed, n_word, n_frac, return_sizes=True) else: - val, signed, n_word = strbin2int(x, signed, n_word, return_sizes=True) + # integer binary + if 'j' in x: + val, signed, n_word = strbin2complex(x, signed, n_word, return_sizes=True) + else: + val, signed, n_word = strbin2int(x, signed, n_word, return_sizes=True) n_frac = 0 elif base == 16 or 'x' in x[:2]: @@ -228,11 +277,14 @@ def insert_frac_point(x_bin, n_frac): return x_bin @array_support -def binary_repr(x, n_word=None, n_frac=None): +def binary_repr(x, n_word=None, n_frac=None, prefix=None): if n_frac is None: val = np.binary_repr(int(x), width=n_word) else: val = insert_frac_point(np.binary_repr(x, width=n_word), n_frac=n_frac) + + if prefix is not None: + val = add_binary_prefix(val, prefix=prefix) return val @array_support @@ -264,18 +316,22 @@ def base_repr(x, n_word=None, base=2, n_frac=None): return val @array_support -def add_binary_prefix(x): +def add_binary_prefix(x, prefix='0b'): + if isinstance(x, np.ndarray) and x.ndim == 0: + x = x.item() + if isinstance(x, str): - # add prefix - if x[0].lower() == 'b': - x = '0b' + x[1:] - elif len(x) > 1 and x[1].lower() == 'b': - x = '0b' + x[2:] + # convert to easy format + x = x.lower().replace(' ', '').replace('i', 'j').replace('0b', '').replace('b', '') + + if ('+' in x or '-' in x) and 'j' in x: + # complex format + x = prefix + x.replace('+', '+' + prefix).replace('-', '-' + prefix) else: - x = '0b' + x + x = prefix + x # check valid characters - invalid_chars = set(x[2:]) - {'0', '1', '.'} + invalid_chars = set(x.replace(prefix, '')) - {'0', '1', '.', 'j', '+', '-'} if len(invalid_chars) > 0: raise ValueError(f"Binary string has invalid characters: {invalid_chars}") else: @@ -297,6 +353,10 @@ def complex_repr(r, i): c[idx] = str(r[idx]) + imag_sign_symbol + str(i[idx]) + 'j' else: raise ValueError('parameters must be a list of array of strings!') + + # return single element is array has one value + if c.size == 0: + c = c.item(0) return c def bits_len(x, signed=None): diff --git a/tests/test_basic.py b/tests/test_basic.py index dc3cddc..03ad7ac 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -132,6 +132,15 @@ def test_instances(): assert x.n_int == 4 assert x.n_word == 7 + x = Fxp('0b00000.01+0b01111.00j') + assert x() == 0.25 + 1j*15 + + x = Fxp('0b00000.01+0b11111.11j') + assert x() == 0.25 - 1j*0.25 + + x = Fxp('0b00000.01-0b0000.10j') + assert x() == 0.25 - 1j*0.5 + x = Fxp([[1.5, 2.25], [-0.125, -3.75]]) assert (x() == np.array([[1.5, 2.25], [-0.125, -3.75]])).all() @@ -202,6 +211,9 @@ def test_base_representations(): # decimal positive x(2.5) assert x.bin() == '00101000' + assert x.bin(frac_dot=True) == '0010.1000' + assert x.bin(prefix='0b') == '0b00101000' + assert x.bin(prefix=True) == '0b00101000' assert x.hex() == '0x28' assert x.hex(padding=False) == '0x28' assert x.base_repr(2) == '101000' @@ -211,6 +223,7 @@ def test_base_representations(): x(-7.25) assert x.bin() == '10001100' assert x.bin(frac_dot=True) == '1000.1100' + assert x.bin(frac_dot=True, prefix='b') == 'b1000.1100' assert x.hex() == '0x8C' assert x.hex(padding=False) == '0x8C' assert x.base_repr(2) == '-1110100' @@ -220,6 +233,8 @@ def test_base_representations(): # complex x(1.5 + 1j*0.75) assert x.bin() == '00011000+00001100j' + assert x.bin(frac_dot=True) == '0001.1000+0000.1100j' + assert x.bin(frac_dot=True, prefix=True) == '0b0001.1000+0b0000.1100j' assert x.hex() == '0x18+0x0Cj' assert x.hex(padding=False) == '0x18+0xCj' assert x.base_repr(2) == '11000+1100j' diff --git a/tests/test_issues.py b/tests/test_issues.py index daab2ab..7a7ea0e 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -277,6 +277,12 @@ def test_issue_49_v0_4_8(): x2 = functions.from_bin(x_bin, raw=True, like=x1) assert x1 == x2 + # test complex value + x1 = Fxp(-3.4 + 1j*0.25) + x_bin = x1.bin(frac_dot=True) + x2 = functions.from_bin(x_bin) + assert x1 == x2 + def test_issue_53_v0_4_5(): x = Fxp(2j, dtype = 'fxp-u4/0-complex') diff --git a/tests/test_utils.py b/tests/test_utils.py index 3ba0cad..e90b628 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -34,6 +34,13 @@ def test_strbin2float(): assert strbin2float('0001', n_frac=2) == 0.25 assert strbin2float('000.1', n_frac=4) == 0.5 +def test_strbin2complex(): + assert strbin2complex('0001') == 1.0 + 1j*0 + assert strbin2complex('0b10000000j') == -1j*128.0 + assert strbin2complex('0b01+0b10000000j') == 1.0 - 1j*128.0 + assert strbin2complex('0b1+0b1000 0000j', signed=False) == 1.0 + 1j*128.0 + assert strbin2complex('0b1 - 0b1000 0000j', signed=False) == 1.0 - 1j*128.0 + def test_strhex2int(): assert strhex2int('0x00') == 0 assert strhex2int('0x0A') == 10 @@ -140,6 +147,13 @@ def test_add_binary_prefix(): add_binary_prefix(np.array([['110', '001'], ['b111', '0b101']])) == \ np.array([['0b110', '0b001'], ['0b111', '0b101']]) ) + + # complex + assert add_binary_prefix('0110+1110j') == '0b0110+0b1110j' + assert add_binary_prefix('0110-1110j') == '0b0110-0b1110j' + assert add_binary_prefix('0b0110-1110j') == '0b0110-0b1110j' + assert add_binary_prefix('0110-0b1110j') == '0b0110-0b1110j' + assert np.all(add_binary_prefix(['110+101j', '001-111j']) == np.array(['0b110+0b101j', '0b001-0b111j'])) # test wrong input formats inputs_list = [0, 1, 3, '3', '102', '0b1102'] @@ -152,3 +166,10 @@ def test_add_binary_prefix(): print(f"input processed right when should be wrong: {i}") assert False +def test_complex_repr(): + assert complex_repr('2', '-4.5') == '2-4.5j' + assert complex_repr('2', '4.5') == '2+4.5j' + assert complex_repr('-2', '4.5') == '-2+4.5j' + + assert np.all(complex_repr(['1', '-2.5'], ['4.5', '0']) == np.array(['1+4.5j', '-2.5+0j'])) + \ No newline at end of file From d00f6c89e9d62cb8a44ca8b717f5e29d9181ac39 Mon Sep 17 00:00:00 2001 From: francof2a Date: Thu, 21 Dec 2023 16:39:06 -0300 Subject: [PATCH 09/24] update changelog and dev version --- changelog.txt | 1 + fxpmath/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 2513d28..8676c20 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ version 0.4.9 * Fix wrap fuction over n_word_max (issue #41). * Inaccuracy flag is propagated to a new Fxp or resulting Fxp if one or more input Fxp are inaccurate (issue #48). * New `from_bin` method and function. Set value or create new Fxp from a binary string (issue #49). +* Support to complex binary strings as input format. version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index 9503232..ff19578 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev2' +__version__ = '0.4.9-dev3' import sys import os From 44c7131155665df3509f56ae9e4c220073571007 Mon Sep 17 00:00:00 2001 From: francof2a Date: Fri, 22 Dec 2023 12:58:33 -0300 Subject: [PATCH 10/24] Force to `config.op_input_size='best'` when power operation has an constant operator. Add warning message. issue #89 --- changelog.txt | 1 + fxpmath/__init__.py | 2 +- fxpmath/objects.py | 25 +++++++++++++++---------- tests/test_basic.py | 20 ++++++++++++++++++++ 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/changelog.txt b/changelog.txt index 8676c20..eedbd5c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ version 0.4.9 * Inaccuracy flag is propagated to a new Fxp or resulting Fxp if one or more input Fxp are inaccurate (issue #48). * New `from_bin` method and function. Set value or create new Fxp from a binary string (issue #49). * Support to complex binary strings as input format. +* Force to `config.op_input_size='best'` when power operation has an constant operator (non-Fxp). Add warning message. (issue #89). version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index ff19578..c70544e 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev3' +__version__ = '0.4.9-dev4' import sys import os diff --git a/fxpmath/objects.py b/fxpmath/objects.py index c294931..02b7338 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -1321,7 +1321,10 @@ def __pow__(self, x): from .functions import pow if not isinstance(x, Fxp): - x = self._convert_op_input_value(x) + if self.config is not None and self.config.op_input_size != 'best': + print("Warning: using config.op_input_size != 'best' could lead to long execution times and huge memory usage! Forcing to config.op_input_size='best'") + print(f"Tip: force a explicit Fxp dtype for your exponent. Instead of x**{x} use x**Fxp({x}) or x**Fxp({x}, dtype='some fxp dtype')") + x = self._convert_op_input_value(x, op_input_size='best') _sizing = self.config.const_op_sizing else: _sizing = self.config.op_sizing @@ -1615,17 +1618,19 @@ def reset(self): 'underflow': False, 'inaccuracy': False} - def _convert_op_input_value(self, x): + def _convert_op_input_value(self, x, op_input_size=None): if not isinstance(x, Fxp): - if self.config is not None: - if self.config.op_input_size == 'best': - x_fxp = Fxp(x) - elif self.config.op_input_size == 'same': - x_fxp = Fxp(x, like=self) - else: - raise ValueError('Sizing parameter not supported: {}'.format(self.config.op_input_size)) - else: + if op_input_size is None and self.config is not None: + op_input_size = self.config.op_input_size + + if op_input_size is None: + x_fxp = Fxp(x) + elif op_input_size == 'best': x_fxp = Fxp(x) + elif op_input_size == 'same': + x_fxp = Fxp(x, like=self) + else: + raise ValueError('Sizing parameter not supported: {}'.format(op_input_size)) else: x_fxp = x diff --git a/tests/test_basic.py b/tests/test_basic.py index 03ad7ac..bb31ff4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -147,6 +147,26 @@ def test_instances(): x = Fxp([['0b1100', '0b0110'], ['0b0000', '0b1111']], signed=True, n_frac=2) assert (x() == np.array([[-1.0, 1.5], [0.0, -0.25]])).all() + # Fxp from a Fxp + x = Fxp(-1.75, dtype='fxp-s8/4') + y = Fxp(x) + assert x() == y() + + y = Fxp(x, like=x) + assert x() == y() and x.dtype == y.dtype + + y = Fxp(x, signed=False) + assert x() != y() and y() == 0 + + x = Fxp(1.75, dtype='fxp-u8/4') + y = Fxp(x) + assert x() == y() + + x1 = Fxp(4, False, 9, 3) + x2 = Fxp(5, False, 9, 3) + cast = Fxp(None, True, 9, 3) + y = cast(x1) - cast(x2) + def test_signed(): # signed x_fxp = Fxp(0.0, True, 8, 7) From 7b28db87b4148f5ec91d47c35c8ae813ffba7de2 Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 17 Jan 2024 08:59:01 -0300 Subject: [PATCH 11/24] Selection of `prefix` for binary and hexadecimal representations. --- changelog.txt | 1 + fxpmath/__init__.py | 2 +- fxpmath/objects.py | 35 +++++++++++++++++++++++++++++------ fxpmath/utils.py | 8 ++++---- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/changelog.txt b/changelog.txt index eedbd5c..1eaf1d0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,7 @@ version 0.4.9 * New `from_bin` method and function. Set value or create new Fxp from a binary string (issue #49). * Support to complex binary strings as input format. * Force to `config.op_input_size='best'` when power operation has an constant operator (non-Fxp). Add warning message. (issue #89). +* Selection of `prefix` for binary and hexadecimal representations. version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index c70544e..62cbc19 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev4' +__version__ = '0.4.9-dev5' import sys import os diff --git a/fxpmath/objects.py b/fxpmath/objects.py index 02b7338..0e62871 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -1556,20 +1556,26 @@ def hex(self, padding=True, prefix=None): else: hex_n_word = None + # set prefix if it's necessary + prefix = prefix if prefix is not None else self.config.hex_prefix + if prefix is not None: + if isinstance(prefix, bool) and prefix == True: + prefix = '0x' # default hexadecimal prefix + if isinstance(self.val, (list, np.ndarray)) and self.val.ndim > 0: if self.vdtype == complex: - real_val = [utils.hex_repr(utils.binary_repr(utils.int_array(val.real), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2) for val in self.val] - imag_val = [utils.hex_repr(utils.binary_repr(utils.int_array(val.imag), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2) for val in self.val] + real_val = [utils.hex_repr(utils.binary_repr(utils.int_array(val.real), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2, prefix=prefix) for val in self.val] + imag_val = [utils.hex_repr(utils.binary_repr(utils.int_array(val.imag), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2, prefix=prefix) for val in self.val] rval = utils.complex_repr(real_val, imag_val) else: - rval = [utils.hex_repr(val, n_word=hex_n_word, base=2) for val in self.bin()] + rval = [utils.hex_repr(val, n_word=hex_n_word, base=2, prefix=prefix) for val in self.bin()] else: if self.vdtype == complex: - real_val = utils.hex_repr(utils.binary_repr(utils.int_array(self.val.real), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2) - imag_val = utils.hex_repr(utils.binary_repr(utils.int_array(self.val.imag), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2) + real_val = utils.hex_repr(utils.binary_repr(utils.int_array(self.val.real), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2, prefix=prefix) + imag_val = utils.hex_repr(utils.binary_repr(utils.int_array(self.val.imag), n_word=self.n_word, n_frac=None), n_word=hex_n_word, base=2, prefix=prefix) rval = utils.complex_repr(real_val, imag_val) else: - rval = utils.hex_repr(self.bin(), n_word=hex_n_word, base=2) + rval = utils.hex_repr(self.bin(), n_word=hex_n_word, base=2, prefix=prefix) return rval def base_repr(self, base, frac_dot=False): @@ -1990,6 +1996,7 @@ def __init__(self, **kwargs): # prefixes self.bin_prefix = kwargs.pop('bin_prefix', None) + self.hex_prefix = kwargs.pop('hex_prefix', '0x') # --- # properties @@ -2229,6 +2236,7 @@ def dtype_notation(self, val): else: raise ValueError('dtype_notation must be str type with following valid values: {}'.format(self._dtype_notation_list)) + # prefixes @property def bin_prefix(self): return self._bin_prefix @@ -2244,6 +2252,21 @@ def bin_prefix(self, prefix): self._bin_prefix = prefix + @property + def hex_prefix(self): + return self._hex_prefix + + @hex_prefix.setter + def hex_prefix(self, prefix): + if prefix is not None and not isinstance(prefix, str): + print("Warning: the prefix should be a string, converted to string automatically!") + prefix = str(prefix) + + if prefix not in [None, 'x', '0x', 'X', '0X', 'h', '0h', 'H', '0H']: + print(f"Warning: the prefix {prefix} is not a common prefix for hexadecimal values!") + + self._hex_prefix = prefix + # endregion # --- diff --git a/fxpmath/utils.py b/fxpmath/utils.py index d737bf9..ba8367b 100644 --- a/fxpmath/utils.py +++ b/fxpmath/utils.py @@ -288,7 +288,7 @@ def binary_repr(x, n_word=None, n_frac=None, prefix=None): return val @array_support -def hex_repr(x, n_word=None, padding=None, base=10): +def hex_repr(x, n_word=None, padding=None, base=10, prefix='0x'): if base == 2: x = int(x, 2) elif base == 10: @@ -297,12 +297,12 @@ def hex_repr(x, n_word=None, padding=None, base=10): raise ValueError('base {base} for input value is not supported!') if n_word is not None: - val = '0x{0:0{1}X}'.format(x, int(np.ceil(n_word/4))) + val = prefix + '{0:0{1}X}'.format(x, int(np.ceil(n_word/4))) elif padding is not None: - val = '0x{0:0{1}X}'.format(x, padding) + val = prefix + '{0:0{1}X}'.format(x, padding) else: val = hex(x) - val = '0x'+val[2:].upper() + val = prefix + val[2:].upper() return val @array_support From ce55c30b6f085126ce55d74a5d4f2e17fd6177e8 Mon Sep 17 00:00:00 2001 From: francof2a Date: Thu, 18 Jan 2024 14:33:10 -0300 Subject: [PATCH 12/24] Fix `cumsum` function bug when dealing with sizes bigger than 32 bits (windows) / 64 bits (linux) (issue #76) --- changelog.txt | 1 + fxpmath/__init__.py | 2 +- fxpmath/functions.py | 10 +++++----- fxpmath/objects.py | 38 +++++++++++++++++++++++++++++++++++--- tests/test_issues.py | 22 +++++++++++++++++++++- 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/changelog.txt b/changelog.txt index 1eaf1d0..684b708 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,6 +6,7 @@ version 0.4.9 * Support to complex binary strings as input format. * Force to `config.op_input_size='best'` when power operation has an constant operator (non-Fxp). Add warning message. (issue #89). * Selection of `prefix` for binary and hexadecimal representations. +* Fix `cumsum` function bug when dealing with sizes bigger than 32 bits (windows) / 64 bits (linux) (issue #76). version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index 62cbc19..f69f56d 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev5' +__version__ = '0.4.9-dev6' import sys import os diff --git a/fxpmath/functions.py b/fxpmath/functions.py index a6df81c..e24e5f7 100644 --- a/fxpmath/functions.py +++ b/fxpmath/functions.py @@ -272,7 +272,7 @@ def fxp_sum(x, sizes='best_sizes', axis=None, dtype=None, out=None, vdtype=None) raise TypeError('out argument must be a Fxp object!') elif sizes == 'best_sizes': signed = x.signed - n_word = np.ceil(np.log2(x().size)).astype(int) + x.n_word + n_word = int(np.ceil(np.log2(x().size))) + x.n_word n_frac = x.n_frac sum_along_axis = Fxp(x_sum, signed=signed, n_word=n_word, n_frac=n_frac) @@ -554,7 +554,7 @@ def _sum_raw(x, n_frac, **kwargs): x = Fxp(x) signed = x.signed - n_word = np.ceil(np.log2(x.size)).astype(int) + x.n_word + n_word = int(np.ceil(np.log2(x.size))) + x.n_word n_frac = x.n_frac n_int = n_word - int(signed) - n_frac optimal_size = (signed, n_word, n_int, n_frac) @@ -574,7 +574,7 @@ def _cumsum_raw(x, n_frac, **kwargs): x = Fxp(x) signed = x.signed - n_word = np.ceil(np.log2(x.size)).astype(int) + x.n_word + n_word = int(np.ceil(np.log2(x.size))) + x.n_word n_frac = x.n_frac n_int = n_word - int(signed) - n_frac optimal_size = (signed, n_word, n_int, n_frac) @@ -683,7 +683,7 @@ def _trace_raw(x, n_frac, **kwargs): num_of_additions = np.diagonal(np.array(a), offset=offset, axis1=axis1, axis2=axis2).size signed = a.signed - n_word = np.ceil(np.log2(num_of_additions)).astype(int) + a.n_word + n_word = int(np.ceil(np.log2(num_of_additions))) + a.n_word n_frac = a.n_frac n_int = n_word - int(signed) - n_frac optimal_size = (signed, n_word, n_int, n_frac) @@ -731,7 +731,7 @@ def _dot_raw(x, y, n_frac, **kwargs): num_of_additions = x.shape[-1] signed = x.signed or y.signed n_frac = x.n_frac + y.n_frac - n_word = np.ceil(np.log2(num_of_additions)).astype(int) + x.n_word + y.n_word + n_word = int(np.ceil(np.log2(num_of_additions))) + x.n_word + y.n_word n_int = n_word - int(signed) - n_frac optimal_size = (signed, n_word, n_int, n_frac) diff --git a/fxpmath/objects.py b/fxpmath/objects.py index 0e62871..34f04cb 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -352,11 +352,25 @@ def _parseformatstr(self, fmt): return signed, n_word, n_frac, complex_dtype def _init_size(self, val=None, signed=None, n_word=None, n_frac=None, n_int=None, max_error=_max_error, n_word_max=_n_word_max, raw=False): + # check signed type + if not isinstance(signed, (type(None), bool, int)): + raise TypeError("signed must be boolean (True, False), int (1 or 0) or None!") + + # check n_word, n_frac, n_int type + if not isinstance(n_word, (type(None), int)): + raise TypeError("n_word must be integer or None!") + if not isinstance(n_frac, (type(None), int)): + raise TypeError("n_frac must be integer or None!") + if not isinstance(n_int, (type(None), int)): + raise TypeError("n_int must be integer or None!") + # sign by default if signed is None: self.signed = True else: - self.signed = signed + self.signed = bool(signed) + if self.signed != 0 and self.signed != 1: + raise ValueError("If signed is int, the valid values are 1 (True) and 0 (False)!") # n_int defined: if n_word is None and n_frac is not None and n_int is not None: @@ -402,6 +416,24 @@ def resize(self, signed=None, n_word=None, n_frac=None, n_int=None, restore_val= _old_val = self.val _old_n_frac = self.n_frac + # check signed type + if not isinstance(signed, (type(None), bool, int)): + raise TypeError("signed must be boolean (True, False), int (1 or 0) or None!") + + # check n_word, n_frac, n_int type + if not isinstance(n_word, (type(None), int)): + raise TypeError("n_word must be integer or None!") + if not isinstance(n_frac, (type(None), int)): + raise TypeError("n_frac must be integer or None!") + if not isinstance(n_int, (type(None), int)): + raise TypeError("n_int must be integer or None!") + + # sign by default + if signed is not None: + self.signed = bool(signed) + if self.signed != 0 and self.signed != 1: + raise ValueError("If signed is int, the valid values are 1 (True) and 0 (False)!") + # check if a string-based format has been provided if dtype is not None: if signed is not None or n_word is not None or n_frac is not None or n_int is not None: @@ -421,10 +453,10 @@ def resize(self, signed=None, n_word=None, n_frac=None, n_int=None, restore_val= self.signed = signed # word if n_word is not None: - self.n_word = n_word + self.n_word = int(n_word) # frac if n_frac is not None: - self.n_frac = n_frac + self.n_frac = int(n_frac) # n_int self.n_int = self.n_word - self.n_frac - (1 if self.signed else 0) diff --git a/tests/test_issues.py b/tests/test_issues.py index 7a7ea0e..88ba070 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -401,4 +401,24 @@ def test_issue_73_v0_4_8(): e = Fxp([15, 15], False, 14, 3) f = d - e assert f[0]() == 0.0 # [4095.875 6.0] --> 4095.875 is the upper limit - assert f[1]() == 6.0 \ No newline at end of file + assert f[1]() == 6.0 + +def test_issue_76_v0_4_8(): + # Numpy Issue with Bigger bit sizes + # Getting strange results when using larger bit sizes in numpy calls + + # This works + w = Fxp([1, 1, 1, 1], dtype='fxp-s29/0') + y = np.cumsum(w) + assert np.all(y() == np.array([1, 2, 3, 4])) + + # This doesn't + w = Fxp([1, 1, 1, 1], dtype='fxp-s32/0') + y = np.cumsum(w) + assert np.all(y() == np.array([1, 2, 3, 4])) # works in linux, not in windows + + # Increase word size above 64 bits + w = Fxp([1, 1, 1, 1], dtype='fxp-s64/0') + y = np.cumsum(w) + assert np.all(y() == np.array([1, 2, 3, 4])) + \ No newline at end of file From 542bdd66cdcbd97a313b0ba6cf3e17beb005934d Mon Sep 17 00:00:00 2001 From: francof2a Date: Fri, 19 Jan 2024 09:51:13 -0300 Subject: [PATCH 13/24] Fix `numpy.reshape` function handling. This function was returning optimal size instead of same by default (issue #77). --- changelog.txt | 1 + fxpmath/__init__.py | 5 +++-- fxpmath/functions.py | 13 ++++++++++++- tests/test_issues.py | 13 ++++++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 684b708..b8f3b7a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,7 @@ version 0.4.9 * Force to `config.op_input_size='best'` when power operation has an constant operator (non-Fxp). Add warning message. (issue #89). * Selection of `prefix` for binary and hexadecimal representations. * Fix `cumsum` function bug when dealing with sizes bigger than 32 bits (windows) / 64 bits (linux) (issue #76). +* Fix `numpy.reshape` function handling. This function was returning optimal size instead of same by default (issue #77). version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index f69f56d..75013d5 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev6' +__version__ = '0.4.9-dev7' import sys import os @@ -79,5 +79,6 @@ trace, prod, dot, - nonzero + nonzero, + reshape ) diff --git a/fxpmath/functions.py b/fxpmath/functions.py index e24e5f7..3fd98d4 100644 --- a/fxpmath/functions.py +++ b/fxpmath/functions.py @@ -746,4 +746,15 @@ def nonzero(x): if x.scaled: return np.nonzero(x.get_val()) else: - return np.nonzero(x.val) \ No newline at end of file + return np.nonzero(x.val) + +@implements(np.reshape) +def reshape(a, newshape, order='C', out=None, out_like=None, sizing='same', method='raw', **kwargs): + """ + """ + def _reshape_raw(x, newshape, order, **kwargs): + return np.reshape(x.val, newshape=newshape, order=order) + + kwargs['newshape'] = newshape + kwargs['order'] = order + return _function_over_one_var(repr_func=np.reshape, raw_func=_reshape_raw, x=a, out=out, out_like=out_like, sizing=sizing, method=method, optimal_size=None, **kwargs) diff --git a/tests/test_issues.py b/tests/test_issues.py index 88ba070..e4cfa03 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -421,4 +421,15 @@ def test_issue_76_v0_4_8(): w = Fxp([1, 1, 1, 1], dtype='fxp-s64/0') y = np.cumsum(w) assert np.all(y() == np.array([1, 2, 3, 4])) - \ No newline at end of file + +def test_issue_77_v0_4_8(): + # Precision error when numpy.reshape + + a = np.array([[0.762, 0.525], [0.345, 0.875]], dtype=complex) + x = Fxp(a, signed=True, n_word=5, n_frac=3) + # fxp-s5/3-complex + assert x.signed == True and x.n_word == 5 and x.n_frac == 3 + + y = np.reshape(x, (1, 4)) + # fxp-s4/3-complex + assert y.signed == True and y.n_word == 5 and y.n_frac == 3 From dd04714a62beeacfc49398051b0892bfc19239eb Mon Sep 17 00:00:00 2001 From: francof2a Date: Fri, 19 Jan 2024 10:11:32 -0300 Subject: [PATCH 14/24] Fix negative number parsing in `dtype` string (issue #80). --- changelog.txt | 1 + fxpmath/__init__.py | 2 +- fxpmath/objects.py | 4 ++-- tests/test_issues.py | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index b8f3b7a..2ad4b97 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,6 +8,7 @@ version 0.4.9 * Selection of `prefix` for binary and hexadecimal representations. * Fix `cumsum` function bug when dealing with sizes bigger than 32 bits (windows) / 64 bits (linux) (issue #76). * Fix `numpy.reshape` function handling. This function was returning optimal size instead of same by default (issue #77). +* Fix negative number parsing in `dtype` string (issue #80). version 0.4.8 -------------------------------------------------- diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index 75013d5..11d523f 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev7' +__version__ = '0.4.9-dev8' import sys import os diff --git a/fxpmath/objects.py b/fxpmath/objects.py index 34f04cb..e560a29 100644 --- a/fxpmath/objects.py +++ b/fxpmath/objects.py @@ -316,10 +316,10 @@ def get_dtype(self, notation=None): return self._dtype def _qfmt(self): - return re.compile(r'(s|u|q|uq|qu)(\d+)(\.\d+)?') + return re.compile(r'(s|u|q|uq|qu)(\d+)(\.[+-]?\d+)?') def _fxpfmt(self): - return re.compile(r'fxp-(s|u)(\d+)/(\d+)(-complex)?') + return re.compile(r'fxp-(s|u)(\d+)/([+-]?\d+)(-complex)?') def _parseformatstr(self, fmt): fmt = fmt.casefold() diff --git a/tests/test_issues.py b/tests/test_issues.py index e4cfa03..1041ba6 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -433,3 +433,19 @@ def test_issue_77_v0_4_8(): y = np.reshape(x, (1, 4)) # fxp-s4/3-complex assert y.signed == True and y.n_word == 5 and y.n_frac == 3 + +def test_issue_80_v0_4_8(): + # Creation of Fxp-object with negative n_frac + + # The following code results in unexpected behaviour + # when trying to specify the same type using alternative formats + x = Fxp(16, signed=True, n_word=8, n_frac=-2) + # -> x.dtype = 'fxp-s8/-2' , ok + assert x.dtype == 'fxp-s8/-2' + + x = Fxp(16, dtype='S10.-2') + assert x.dtype == 'fxp-s8/-2' + + x = Fxp(16, dtype='fxp-s8/-2') + assert x.dtype == 'fxp-s8/-2' + \ No newline at end of file From ec1beeaee8d1ee27b6161fc682649dae5a97519a Mon Sep 17 00:00:00 2001 From: francof2a Date: Fri, 19 Jan 2024 10:24:07 -0300 Subject: [PATCH 15/24] test_issue_85_v0_4_8 --- tests/test_issues.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_issues.py b/tests/test_issues.py index 1041ba6..cbfce6a 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -448,4 +448,21 @@ def test_issue_80_v0_4_8(): x = Fxp(16, dtype='fxp-s8/-2') assert x.dtype == 'fxp-s8/-2' - \ No newline at end of file + +def test_issue_85_v0_4_8(): + # Wrap overflow breaks on 0.0 value + + dt_values = ['fxp-s32/16', 'fxp-s64/32', 'fxp-s96/64'] + + for dt in dt_values: + x = Fxp(0, dtype=dt) # => Success + assert x() == 0.0 + + x = Fxp(0.0, dtype=dt) # => Success + assert x() == 0.0 + + x = Fxp(0, dtype=dt, overflow='wrap') # => Success + assert x() == 0.0 + + x = Fxp(0.0, dtype=dt, overflow='wrap') # EXCEPTION + assert x() == 0.0 From e95cdfb502c591f85610bf92ae68949044562eba Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 19:11:25 -0300 Subject: [PATCH 16/24] update docs: index, config, install, quick_start --- _config.yml | 4 + docs/config.md | 304 +++++++++++++++++++++++++++++ docs/install.md | 26 +++ docs/quick_start.md | 463 ++++++++++++++++++++++++++++++++++++++++++++ index.md | 26 +++ 5 files changed, 823 insertions(+) create mode 100644 _config.yml create mode 100644 docs/config.md create mode 100644 docs/install.md create mode 100644 docs/quick_start.md create mode 100644 index.md diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..f5707d2 --- /dev/null +++ b/_config.yml @@ -0,0 +1,4 @@ +title: fxpmath +description: A python library for fractional fixed-point (base 2) arithmetic and binary manipulation with Numpy compatibility. +theme: jekyll-theme-slate +future: true \ No newline at end of file diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..ff78fa7 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,304 @@ +# Config + +The class `config` is a parameters set which determines the behavior of a Fxp object in several processes. + +Let's take a look over this class: + +```python +x = Fxp(None, True, 16, 4) +x.config.print() +``` + +> max_error : 1.0842021724855044e-19 +> n_word_max : 64 +> overflow : saturate +> rounding : trunc +> shifting : expand +> op_method : raw +> op_input_size : same +> op_out : None +> op_out_like : None +> op_sizing : optimal +> const_op_sizing : same +> array_output_type : fxp +> array_op_out : None +> array_op_out_like : None +> array_op_method : repr +> dtype_notation : fxp +> bin_prefix : None +> hex_prefix : 0x + +The first 5 parameters were present in older versions, but they were move here. Note that size parameters don't live here. + +Following sections will explain breafly rest of parameters. + +# op_method + +This parameter defines if a operation involving its Fxp object is going to be performed using `raw` or `repr` method. + +The `raw` method use the `int` internal representation of data, whilst `repr` use the original type of data for the operation. The `raw` method is more accurate and emulate binary level operation, but is slower most of cases. This method is needed in cases of *extended precision*. + +```python +a = Fxp([-1.0, 0.0, 1.0], True, 128, 96) +b = Fxp([2**-64, -2**48, 2**32], True, 128, 96) + +c = a + b +print(c) +print(c-a) # equal to b +``` + +> [-1.0 -2147483648.0 2147483649.0] +> [5.421010862427522e-20 -2147483648.0 2147483648.0] + +```python +a.config.op_method = 'repr' +b.config.op_method = 'repr' + +c = a + b +print(c) +print(c-a) # should be equal to b, but it doesn't +``` + +> [-1.0 -2147483648.0 2147483649.0] +> [0.0 -2147483648.0 2147483648.0] + +Using `repr`, addition is equivalent to $c = \text{Fxp}(a() + b())$. The fist value of $c - a$ is not equal to first value of $b$ because $2^{-64}$ has been lost when it was added to $1.0$ becuase *float precision* limit. + +## op_input_size + +When an arithmetic operator is used between an Fxp and a non-Fxp object, last is going to be converted to Fxp before operation is performed. Sizing process of this non-Fxp object is specified by `op_input_size`. + +The values could be `'same'` (default, same size than Fxp) or `'best'` (best size according value(s) of non-Fxp). + +```python +x = Fxp(2.0, True, 16, 2) +x.config.op_input_size = 'same' + +z = x * 2.125 + +z.info() +``` + +> dtype = fxp-s16/2 +> Value = 4.0 + +Note that 2.125 is converted to Fxp(2.125, True, 16, 2), losing precision. + +```python +x = Fxp(2.0, True, 16, 2) +x.config.op_input_size = 'best' + +z = x * 2.125 + +z.info() +``` + +> dtype = fxp-s16/2 +> Value = 4.25 + +Now, the value 2.125 is converted to Fxp using the best resolution in order to represent the real value before the aritmetic operation. + +## op_out + +This can point to a specific Fxp object to store a result. By default this is `None`. + +If two Fxp objects are used the first will be the reference. + +```python +x = Fxp(2.0, True, 16, 2) +z = Fxp(0.0, True, 16, 4) + +x.config.op_out = z + +(x + 1).info() +z.info() +``` + +> dtype = fxp-s16/4 +> Value = 3.0 +> +> dtype = fxp-s16/4 +> Value = 3.0 + +## op_out_like + +This defines the Fxp type for result. By default this is `None`. + +If two Fxp objects are used the first will be the reference. + +```python +x = Fxp(2.0, True, 16, 2) +x.config.op_out = Fxp(None, True, 24, 4) + +z = x * 3.5 + +z.info() +``` + +> dtype = fxp-s24/4 +> Value = 7.0 + +## op_sizing + +This defines size of Fxp returned by an operation, applied to one or two Fxp objects. The option for config this parameters are: 'optimal', 'same', 'fit', 'largest', 'smallest'. Where: + +- `'optimal'`: size is theorical optimal according operation and sizes of operands. +- `'same'`: same size that firs operand. +- `'fit'`: best size is calculated according result value(s). +- `'largest'`: same size that largest (size) Fxp operand. +- `'smallest'`: same size that smallest (size) Fxp operand. + +```python +x = Fxp(3.5, True, 16, 4) +y = Fxp(-1.25, True, 24, 8) + +for s in x.config._op_sizing_list: + x.config.op_sizing = s + print('{}:'.format(x.config.op_sizing)) + (x + y).info() +``` +
+optimal:  
+    dtype		=	fxp-s25/8  
+    Value		=	2.25  
+
+same:  
+    dtype		=	fxp-s16/4  
+    Value		=	2.25  
+
+fit:  
+    dtype		=	fxp-s11/8  
+    Value		=	2.25  
+
+largest:  
+    dtype		=	fxp-s24/8  
+    Value		=	2.25  
+
+smallest:  
+    dtype		=	fxp-s16/4  
+    Value		=	2.25  
+
+ +## const_op_sizing + +This defines the same behavior that `op_sizing` parameter, but when a constant is used as operand instead other Fxp object. First, constat is converted to Fxp according to `op_input_size` and then output Fxp (result) size is choosen by `const_op_sizing`. + +```python +x = Fxp(3.5, True, 16, 4) + +for s in x.config._const_op_sizing_list: + x.config.const_op_sizing = s + print('{}:'.format(x.config.const_op_sizing)) + (x + 2).info() +``` +
+optimal:
+	dtype		=	fxp-s17/4
+	Value		=	5.5
+
+same:
+	dtype		=	fxp-s16/4
+	Value		=	5.5
+
+fit:
+	dtype		=	fxp-s8/4
+	Value		=	5.5
+
+largest:
+	dtype		=	fxp-s16/4
+	Value		=	5.5
+
+smallest:
+	dtype		=	fxp-s16/4
+	Value		=	5.5
+
+ +## array_output_type + +This defines the type of the object get as result when use array (numpy) functions. The options are: 'fxp' (defautl, return an Fxp) or 'array' (return an numpy n-dimensional array). + +```python +x = Fxp([1.0, 2.5, 3.0, 4.5], dtype='fxp-s16/4') +x.config.array_output_type = 'fxp' + +z = np.sum(x) +z.info() + +x.config.array_output_type = 'array' +z = np.sum(x) +print(z, type(z)) +``` +
+	dtype		=	fxp-s18/4
+	Value		=	11.0
+
+11.0 <class 'numpy.ndarray'>
+
+ +## array_op_out + +Same as `op_output` but used with numpy functions: + +```python +w = Fxp([1.0, -2.5, 3.0, -4.5], dtype='fxp-s16/4') +z = Fxp(0.0, True, 16, 4) +w.config.array_op_out = z + +y = np.sum(w) +y.info() +z.info() +print(y is z) +``` +
+	dtype		=	fxp-s16/4
+	Value		=	-3.0
+
+	dtype		=	fxp-s16/4
+	Value		=	-3.0
+
+True
+
+ +## array_op_out_like + +Same as `op_out_like` but used with numpy functions: + +```python +w = Fxp([1.0, -2.5, 3.0, -4.5], dtype='fxp-s16/4') +w.config.array_op_out_like = Fxp(0.0, True, 32, 8) + +y = np.sum(w) +y.info() + +y = np.cumsum(w) +y.info() +``` +
+	dtype		=	fxp-s32/8
+	Value		=	-3.0
+
+	dtype		=	fxp-s32/8
+	Value		=	[ 1.  -1.5  1.5 -3. ]
+
+ +## array_op_method + +This property defines what values are used for (numpy) arrays created from Fxp objects. If 'raw', arrays have *raw values* of Fxp object, whilst 'repr', arrays have values in original (representation) type. + +This parameter is used also to defined kernel op_method when array conversion is involved. + +```python +w = Fxp([1.0, -2.5, 3.0, -4.5], dtype='fxp-s8/2') +w.config.array_op_method = 'repr' + +w_array = np.asarray(w) +print(w_array, type(w_array)) + +w.config.array_op_method = 'raw' +w_array = np.asarray(w) +print(w_array, type(w_array)) +``` +
+[ 1.  -2.5  3.  -4.5] <class 'numpy.ndarray'>
+[  4 -10  12 -18] <class 'numpy.ndarray'>
+
diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..996632f --- /dev/null +++ b/docs/install.md @@ -0,0 +1,26 @@ +# install + +To install from *pip* just do the next: + +```bash +pip install fxpmath +``` + +To install with *conda* just do the next: + +```bash +conda install -c francof2a fxpmath +``` + +Or you can clone the repository doing in your console: + +```bash +git clone https://github.com/francof2a/fxpmath.git +``` + +and then go to the fxpmath folder and install it: + +```bash +cd fxpmath +pip install . +``` diff --git a/docs/quick_start.md b/docs/quick_start.md new file mode 100644 index 0000000..0cdee4d --- /dev/null +++ b/docs/quick_start.md @@ -0,0 +1,463 @@ +# quick start + +## creation + +Let's jump into create our new **fractional fixed-point** variable: + +```python +from fxpmath import Fxp + +x = Fxp(-7.25) # create fxp variable with value 7.25 +x.info() +``` + +> dtype = fxp-s6/2 +> Value = -7.25 + +We have created a variable of 6 bits, where 1 bit has been reserved for sign, 2 bits for fractional part, and 3 remains for integer part. Here, bit sizes had been calculated to just satisfy the value you want to save. + +But the most common way to create a **fxp** variable beside the its value is defining explicitly if it is signed, the number of bits for whole word and for the fractional part. + +**Note**: `dtype` of Fxp object is a propietary *type* of each element stored in it. The format is: + +**`fxp-/-{complex}`** + +i.e.: `fxp-s16/15`, `fxp-u8/1`, `fxp-s32/24-complex` + +```python +x = Fxp(-7.25, signed=True, n_word=16, n_frac=8) +``` + +or just + +```python +x = Fxp(-7.25, True, 16, 8) +``` + +Formats can also be specified using a string, either in the fxp `dtype` format, +or by using `Qm.n` or `UQm.n` notation (or the equivalent `Sm.n`/`Um.n` notation). + +```python +x = Fxp(-7.25, dtype='fxp-s16/8') +x = Fxp(-7.25, dtype='S8.8') +``` + +You can print more information only changing the verbosity of *info* method. + +```python +x.info(verbose=3) +``` + +> dtype = fxp-s16/8 +> Value = -7.25 +> +> Signed = True +> Word bits = 16 +> Fract bits = 8 +> Int bits = 7 +> Val data type = `` +> +> Upper = 127.99609375 +> Lower = -128.0 +> Precision = 0.00390625 +> Overflow = saturate +> Rounding = trunc +> Shifting = expand + +## Representations + +We can representate the value stored en `x` in several ways: + +```python +x +``` + +> fxp-s16/8(-7.25) + +```python +x.get_val() # return a Numpy array with the val/values in original data type representation +x() # equivalent to x.get_val() or x.astype(self.vdtype) +``` + +> -7.25 + +In different bases: + +```python +x.bin() +x.bin(frac_dot=True) # binary with fractional dot +x.base_repr(2) # binary with sign symbol (not complement) +x.hex() +x.base_repr(16) # hex with sign symbol (not complement) +``` + +> '1111100011000000' +> '11111000.11000000' +> '-11101000000' +> '0xF8C0' +> '-740' + +In different types: + +```python +x.astype(int) +x.astype(float) +x.astype(complex) +``` + +> -8 +> -7.25 +> (-7.25+0j) + +**Note** that if we do: + +```python +x.val +``` + +we will get the fixed point value stored in memory, like an integer value. Don't use this value for calculations, but in some cases you may need it. + +## changing values + +We can change the value of the variable in several ways: + +```python +x(10.75) # the simpliest way +x.set_val(2.125) # another option +``` + +**DO NOT DO THIS**: + +```python +x = 10.75 # wrong +``` + +because you are just modifying `x` type... it isn't a *Fxp* anymore, just a simple *float* right now. + +The same as `x.val` gives you the raw underlying value, you can set that value with + +```python +x.set_val(43, raw=True) +``` + +## changing size + +If we want to resize our fxp variable we can do: + +```python +x.resize(True, 8, 6) # signed=True, n_word=8, n_frac=6 +``` + +## data types supported + +Fxp can handle following input data types: + +* int, uint +* float +* complex +* list +* ndarrays (n-dimensional numpy arrays) +* strings (bin, hex, dec) +* Fxp objects + +Here some examples: + +```python +x(2) +x(-1.75) +x(-2.5 + 1j*0.25) +x([1.0, 1.5, 2.0]) +x(np.random.uniform(size=(2,4))) +x('3.5') +x('0b11001010') +x('0xA4') +``` + +## indexing + +If we had been save a list or array, we can use indexing just like: + +```python +x[2] = 1.0 # modify the value in the index 2 +print(x[2]) +``` + +--- + +## arithmetic + +*Fxp* supports some basic math operations like: + +```python +0.75 + x # add a constant +x - 0.125 # substract a constant +3 * x # multiply by a constant +x / 1.5 # division by a constant +x // 1.5 # floor division by a constant +x % 2 # modulo +x ** 3 # power +``` + +This math operations using a Fxp and a constant returns a **new Fxp** object with a precision that depends of configuration of Fxp object, `x.config` for examples above. + +The constant is converted into a new Fxp object before math operation, where the Fxp size for the constant operand is defined by `x.config.op_input_size` in examples above. The default value for `op_input_size` is 'best' (best enoguh precision to represent the constant value), but it could be used 'same' to force the constant's size equals to Fxp object size (x in the examples). + +The result of math operation is returned as a new Fxp object with a precision defined according to `x.config.const_op_sizing`. This parameter could be configured with following options: 'optimal', 'same' (default), 'fit', 'largest', 'smallest'. For math operations with constants, by default (`config.const_op_sizing = 'same'`), a Fxp with same size is returned. + +In all these cases we can assign the result to a (Fxp) variable, or to the same (overwritting the old Fxp object). + +```python +y = 3.25 * (x - 0.5) # y is a new Fxp object +``` + +Math operations using **two or more Fxp** variables is also supported, returning a new Fxp object like before cases. The size of returned Fxp object depends of both Fxp operand's sizes and the `config.op_sizing` parameter of the first (left) Fxp object. By default, `config.op_sizing = 'optimal'`, so, the returned size depends also of the math operation type. For example, in the addition case, the integer size of returned Fxp is 1 bit larger than largest integer size of operands, and size of fractional part of returned Fxp is equal to largest fractional size of operands. + +```python +# Fxp as operands +x1 = Fxp(-7.25, signed=True, n_word=16, n_frac=8) +x2 = Fxp(1.5, signed=True, n_word=16, n_frac=8) +x3 = Fxp(-0.5, signed=True, n_word=8, n_frac=7) + +y = 2*x1 + x2 - 0.5 # y is a new Fxp object + +y = x1*x3 - 3*x2 # y is a new Fxp object, again +``` + +If we need to model that the result of a math operation is stored in other fractional fixed-point variable with a particular format we should do the following: + +```python +# variable to store a result +y = Fxp(None, signed=True, n_word=32, n_frac=16) + +y.equal(x1*x3 - 3*x2) +``` + +At the end, we also have the possibility of get the value of a math operation and set that val in the varible created to store the result. + +```python +y.set_val( (x1*x3 - 3*x2).get_val() ) # equivalent to y.equal(x1*x3 - 3*x2), but less elegant +y( (x1*x3 - 3*x2)() ) # just a little more elegant +``` + +Another example could be a sin wave function represented in Fxp: + +```python +import numpy as np + +f = 5.0 # signal frequency +fs = 400.0 # sampling frequency +N = 1000 # number of samples + +n = Fxp( list(range(N)) ) # sample indices +y( 0.5 * np.sin(2 * np.pi * f * n() / fs) ) # a sin wave with 5.0 Hz of frequecy sampled at 400 samples per second +``` + +### logical (bitwise) operators + +*Fxp* supports logical (bitwise) operations like *not* (*inverse*), *and*, *or*, *xor* with constants or others Fxp variables. It also supports bits *shifting* to the right and left. + +```python +x & 0b1100110011110000 # Fxp var AND constant +x & Fxp('0b11001100.11110000') # Fxp var AND other Fxp with same constant +x & y # x AND y, both previoulsy defined + +~x # bits inversion +x | y # x OR y +x ^ y # x XOR y + +x << 5 # x shifted 5 bits to the left +x >> 3 # x shifted 3 bits to the right (filled with sign bit) +``` + +When logical operations are performed with a constant, this constant is converted to a Fxp with de same characteristics of Fxp operand. + +### Comparisons + +*Fxp* supoorts comparison operators with constants, other variables, or another Fxp. + +```python +x > 5 +x == y +# ... and other comparison availables +``` + +--- + +## behaviors + +Fxp has embedded some behaviors to process the value to store. + +## overflow / underflow + +A Fxp has upper and lower limits to representate a fixed point value, those limits are define by fractional format (bit sizes). When we want to store a value that is outside those limits, Fxp has an **overflow** y process the value depending the behavior configured for this situation. The options are: + +* *saturate* (default): the stored value is clipped to *upper* o *lower* level, as appropiate. For example, if upper limit is 15.75 and I'd want to store 18.00, the stored value will be 15.75. +* *wrap* : the stored value is wrapped inside valid range. For example, if we have a `fxp-s7/2` the lower limit is -16.00 and the upper +15.75, and I'd want to store 18.00, the stored value will be -14.00 (18.00 is 2.00 above upper limit, so is stored 2.00 above lower limit). + +We can change this behavior doing: + +```python +# at instantiation +x = Fxp(3.25, True, 16, 8, overflow='saturate') + +# afer ... +x.overflow = 'saturate' +# or +x.overflow = 'wrap' +``` + +If we need to know which are the *upper* and *lower* limits, Fxp have those stored inside: + +```python +print(x.upper) +print(x.lower) +``` + +It is important to know the Fxp doesn't raise a warning if *overflow* or *underflow* happens. The way to know that is checking field `status['overflow']` and `status['underflow']` of each Fxp. + +## rounding + +Until now we had been storing values in our Fxp that were represented without loss of precision, and that was because we defined enough amount of bit for word and fractional part. In other words, if we want to save the value -7.25, we need 1 bit for sign, at least 3 bits for integer (2^**3** = 8), and at least 2 bits for fractional (2^-**2** = 0.25). In this case our Fxp would have `fxp-s6/2` format. + +But, if we want to change the value of our Fxp to -7.3, the precision is not enough and Fxp will store -7.25 again. That is because Fxp is **rounding** the value before storing as a fractional fixed point value. Fxp allows different types of rounding methods: + +* *trunc* (default): The truncated value of the scalar (let's say `x`) will be the nearest fractional supported value which is closer to zero than `x` is. In short, the fractional part of the signed number `x` that is not supported, is discarded. Round to nearest fractional supported value towards zero. +* *around* : Evenly round of the given value to the nearest fractional supported value, for example: 1.5 is rounded to 2.0. +* *floor* : The floor of the scalar `x` is the largest fractional supported value `i`, such that i <= x. It is often denoted as $\lfloor x \rfloor$. +* *ceil* : The ceil of the scalar `x` is the smallest fractional supported value `i`, such that i >= x. It is often denoted as \lceil x \rceil. +* *fix* : Round to nearest fractional supported value towards zero. + +We can change this behavior doing: + +```python +# at instantiation +x = Fxp(3.25, True, 16, 8, rounding='floor') + +# after ... +x.rounding = 'trunc' +# or ... +x.rounding = 'around' +x.rounding = 'floor' +x.rounding = 'ceil' +x.rounding = 'fix' +``` + +If we want to know what is the **precision** of our Fxp, we can do: + +```python +print(x.precision) # print the precision of x + +# or, in a generic way: +print(Fxp(n_frac=7).precision) # print the precision of a fxp with 7 bits for fractional part. +``` + +## inaccuracy + +When the input value couldn't be represented exactly as a fixed-point, a **inaccuracy** flag is raised in the status of Fxp variable. You can check this flag to know if you are carrying a precision error. + +## Status flags + +*Fxp* have **status flags** to show that some events have occured inside the variable. The status flags are: + +* overflow +* underflow +* inaccuracy + +Those can be checked using: + +```python +x.get_status() # returns a dictionary with the flags + +# or +x.get_status(format=str) # return a string with flags RAISED only +``` + +The method **reset** can be call to reset status flags raised. + +```python +x.reset() +``` + +--- + +## copy + +We can copy a Fxp just like: + +```python +y = x.copy() # copy also the value stored +# or +y = x.deepcopy() + +# if you want to preserve a value previously stored in `y` and only copy the properties from `x`: +y = y.like(x) +``` + +This prevent to redefine once and once again a Fxp object with same properties. If we want to modify the value en same line, we can do: + +```python +y = x.copy()(-1.25) # where -1.25 y the new value for `y` after copying `x`. It isn't necessary the `y` exists previously. +# or +y = Fxp(-1.25).like(x) +# or +y = Fxp(-1.25, like=x) + +# be careful with: +y = y(-1.25).like(x) # value -1.25 could be modify by overflow or rounding before considerating `x` properties. +y = y.like(x)(-1.25) # better! +``` + +It is a good idea create Fxp objects like **template**: + +```python +# Fxp like templates +DATA = Fxp(None, True, 24, 15) +ADDERS = Fxp(None, True, 40, 16) +MULTIPLIERS = Fxp(None, True, 24, 8) +CONSTANTS = Fxp(None, True, 8, 4) + +# init +x1 = Fxp(-3.2).like(DATA) +x2 = Fxp(25.5).like(DATA) +c = Fxp(2.65).like(CONSTANTS) +m = Fxp().like(MULTIPLIERS) +y = Fxp().like(ADDERS) + +# do the calc! +m.equal(c*x2) +y.equal(x1 + m) + +``` + +## Scaling + +*Fxp* implements an alternative way to input data and represent it, as an linear transformation through *scale* and *bias*. In this way, the raw fracitonal value stored in Fxp variable is "scaled down" during input and "scaled up" during output or operations. + +It allows to use less bits to represent numbers in a huge range and/or offset. + +For example, suppose that the set of numbers to represent are in [10000, 12000] range, and the precision needed is 0.5. We have 4000 numbers to represent, at least. Using scaling we can avoid to represent 12000 number or more. So, we only need 12 bits (4096) values. + +```python +x = Fxp(10128.5, signed=False, n_word=12, scale=1, bias=10000) + +x.info(3) +``` + +> dtype = fxp-u12/1 +> Value = 10128.5 +> Scaling = 1 * val + 10000 +> +> Signed = False +> Word bits = 12 +> Fract bits = 1 +> Int bits = 11 +> Val data type = `` +> +> Upper = 12047.5 +> Lower = 10000.0 +> Precision = 0.5 +> Overflow = saturate +> Rounding = trunc +> Shifting = expand + +Note that *upper* and *lower* limits are correct, and that the *precision* is what we needed. diff --git a/index.md b/index.md new file mode 100644 index 0000000..0e709ce --- /dev/null +++ b/index.md @@ -0,0 +1,26 @@ + + +A python library for fractional fixed-point (base 2) arithmetic and binary manipulation with Numpy compatibility. + +Some key features: + +* Fixed-point signed and unsigned numbers representation. +* Arbitrary word and fractional sizes. Auto sizing capability. Extended precision capability. +* Arithmetic and logical (bitwise) operations supported. +* Input values can be: int, float, complex, list, numpy arrays, strings (bin, hex, dec), Decimal type. +* Input rounding methods, overflow and underflow behaviors and flags. +* Binary, Hexadecimal, and other bases representations (like strings). +* Indexing supported. +* Linear scaling: scale and bias. +* Numpy backend. +* Suppport for Numpy functions. They can take and return Fxp objects. +* Internal behavior configurable: inputs/outputs formating, calculation methods. + +--- + +## Table of content + +- [install](docs/install) +- [quick start](docs/quick_start) +- [behavioral configuration](docs/config) + From c248d20f66d1d4f89914f9d193064ea0ebfcb036 Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 19:12:46 -0300 Subject: [PATCH 17/24] v0.4.9 - ready for release --- fxpmath/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fxpmath/__init__.py b/fxpmath/__init__.py index 11d523f..34872e7 100644 --- a/fxpmath/__init__.py +++ b/fxpmath/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.9-dev8' +__version__ = '0.4.9' import sys import os From c0a9b2153342cd91e0592cfab3618f8b26c3ee2d Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 19:33:32 -0300 Subject: [PATCH 18/24] update os list and python versions in github workflows --- .github/workflows/python-app.yml | 4 ++-- .github/workflows/python-os.yml | 4 ++-- .github/workflows/python-package.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index bf53086..6e6a1bc 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -15,8 +15,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] - python-version: [3.6, 3.7, 3.8, 3.9] + os: [ubuntu-22.04] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index 03f0deb..4d74bd8 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -15,8 +15,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04, ubuntu-20.04, windows-latest, macos-latest] - python-version: [3.6, 3.7, 3.8, 3.9] + os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 3982790..b3af6eb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -12,10 +12,10 @@ on: jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] steps: - uses: actions/checkout@v2 From f0222d0961f35e343d7d8429b60830c652f03303 Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 19:39:06 -0300 Subject: [PATCH 19/24] change python versions in github workflows to strings list --- .github/workflows/python-app.yml | 2 +- .github/workflows/python-os.yml | 2 +- .github/workflows/python-package.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 6e6a1bc..ebc6f50 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index 4d74bd8..67c041a 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b3af6eb..ebdd146 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 From 905c06a4aa58c60eb9e2f7a0c55dba8a31e6955c Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 19:47:32 -0300 Subject: [PATCH 20/24] remove check for python 3.6 version --- .github/workflows/python-app.yml | 4 ++-- .github/workflows/python-os.yml | 4 ++-- .github/workflows/python-package.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index ebc6f50..380654a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -11,12 +11,12 @@ on: jobs: build: - + name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-22.04] - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index 67c041a..5e9db24 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -11,12 +11,12 @@ on: jobs: build: - + name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ebdd146..6f92c29 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -11,11 +11,11 @@ on: jobs: build: - + name: Test on ubuntu-22.04 with Python ${{ matrix.python-version }} runs-on: ubuntu-22.04 strategy: matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 From 2708410982fb2f6ad651e623c2ae795e4bbbb9c4 Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 19:51:32 -0300 Subject: [PATCH 21/24] github workflows continue-on-error: true --- .github/workflows/python-app.yml | 2 ++ .github/workflows/python-os.yml | 2 ++ .github/workflows/python-package.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 380654a..721506e 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -38,3 +38,5 @@ jobs: - name: Test with pytest run: | pytest + continue-on-error: true + diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index 5e9db24..70d3bfc 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -38,3 +38,5 @@ jobs: - name: Test with pytest run: | pytest + continue-on-error: true + \ No newline at end of file diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6f92c29..74bbb07 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,3 +37,5 @@ jobs: - name: Test with pytest run: | pytest + continue-on-error: true + \ No newline at end of file From 61e8a01bf9c8e6c3261812d7d1b584a18edc802c Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 20:01:46 -0300 Subject: [PATCH 22/24] github workflows remove ubuntu 18.04 --- .github/workflows/python-app.yml | 2 +- .github/workflows/python-os.yml | 5 ++--- .github/workflows/python-package.yml | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 721506e..6afde1e 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -10,6 +10,7 @@ on: branches: [ master ] jobs: + if: always() build: name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} @@ -38,5 +39,4 @@ jobs: - name: Test with pytest run: | pytest - continue-on-error: true diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index 70d3bfc..b980ec8 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -10,12 +10,13 @@ on: branches: [ master ] jobs: + if: always() build: name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] + os: [ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: @@ -38,5 +39,3 @@ jobs: - name: Test with pytest run: | pytest - continue-on-error: true - \ No newline at end of file diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 74bbb07..d553dfb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,6 +10,7 @@ on: branches: [ master ] jobs: + if: always() build: name: Test on ubuntu-22.04 with Python ${{ matrix.python-version }} runs-on: ubuntu-22.04 @@ -37,5 +38,3 @@ jobs: - name: Test with pytest run: | pytest - continue-on-error: true - \ No newline at end of file From 3d4c9dc8945c041b7df03b1f09479562dde26832 Mon Sep 17 00:00:00 2001 From: Franco Alcaraz Date: Wed, 7 Feb 2024 20:07:54 -0300 Subject: [PATCH 23/24] Update python-os.yml --- .github/workflows/python-os.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index b980ec8..d1ec13f 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -10,15 +10,14 @@ on: branches: [ master ] jobs: - if: always() build: name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} + if: ${{ always() }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 859a2136b2eca46ca5145e2f9aeae65620e96481 Mon Sep 17 00:00:00 2001 From: francof2a Date: Wed, 7 Feb 2024 20:22:25 -0300 Subject: [PATCH 24/24] workflows: test non blockin jobs when failing --- .github/workflows/python-app.yml | 3 ++- .github/workflows/python-os.yml | 3 ++- .github/workflows/python-package.yml | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 6afde1e..8f75a06 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -10,11 +10,12 @@ on: branches: [ master ] jobs: - if: always() build: name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} + continue-on-error: true strategy: + fail-fast: false matrix: os: [ubuntu-22.04] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] diff --git a/.github/workflows/python-os.yml b/.github/workflows/python-os.yml index d1ec13f..ededfd0 100644 --- a/.github/workflows/python-os.yml +++ b/.github/workflows/python-os.yml @@ -12,9 +12,10 @@ on: jobs: build: name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} - if: ${{ always() }} runs-on: ${{ matrix.os }} + continue-on-error: true strategy: + fail-fast: false matrix: os: [ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d553dfb..e17275f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,11 +10,12 @@ on: branches: [ master ] jobs: - if: always() build: name: Test on ubuntu-22.04 with Python ${{ matrix.python-version }} runs-on: ubuntu-22.04 + continue-on-error: true strategy: + fail-fast: false matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']