Skip to content

Commit

Permalink
Merge pull request #59 from francof2a/dev-0.4.6
Browse files Browse the repository at this point in the history
Dev 0.4.6
  • Loading branch information
francof2a authored Apr 1, 2022
2 parents 82f9728 + 256028a commit dd5b476
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 19 deletions.
7 changes: 7 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
version 0.4.6
--------------------------------------------------
* Fix complex `truediv` and `floordiv` methods (issue #53).
* Fix complex binary, hexadecimal and base representation for arrays (issue #56).
* Tests updates for complex representations.
* Solve init by dtype when it is complex (issue #58).

version 0.4.5
--------------------------------------------------
* Fix FutureWarning in subdtype 'str' comparison (issue #45).
Expand Down
2 changes: 1 addition & 1 deletion fxpmath/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.4.5'
__version__ = '0.4.6'

import sys
import os
Expand Down
34 changes: 34 additions & 0 deletions fxpmath/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,35 @@ def floordiv(x, y, out=None, out_like=None, sizing='optimal', method='raw', **kw
"""
def _floordiv_repr(x, y):
return x // y

def _floordiv_repr_complex(x, y):
y_norm = y.real ** 2 + y.imag ** 2
real_part = (x.real * y.real + x.imag * y.imag) // y_norm
imag_part = (x.imag * y.real - x.real * y.imag) // y_norm
return real_part + 1j*imag_part

def _floordiv_raw(x, y, n_frac):
precision_cast = (lambda m: np.array(m, dtype=object)) if n_frac >= _n_word_max else (lambda m: m)
return ((x.val * precision_cast(2**(n_frac - x.n_frac))) // (y.val * precision_cast(2**(n_frac - y.n_frac)))) * precision_cast(2**n_frac)

def _floordiv_raw_complex(x, y, n_frac):
precision_cast = (lambda m: np.array(m, dtype=object)) if n_frac >= _n_word_max else (lambda m: m)
y_norm = (y.val.real ** 2 + y.val.imag ** 2) * precision_cast(2**(n_frac - 2*y.n_frac))
real_part = (x.val.real * y.val.real + x.val.imag * y.val.imag) * precision_cast(2**(n_frac - x.n_frac - y.n_frac)) // y_norm
imag_part = (x.val.imag * y.val.real - x.val.real * y.val.imag) * precision_cast(2**(n_frac - x.n_frac - y.n_frac)) // y_norm

return (real_part + 1j*imag_part) * precision_cast(2**n_frac)


if not isinstance(x, Fxp):
x = Fxp(x)
if not isinstance(y, Fxp):
y = Fxp(y)

if x.vdtype == complex or y.vdtype == complex:
_floordiv_repr = _floordiv_repr_complex
_floordiv_raw = _floordiv_raw_complex

signed = x.signed or y.signed
n_int = x.n_int + y.n_frac + signed
n_frac = 0
Expand All @@ -392,16 +412,30 @@ def truediv(x, y, out=None, out_like=None, sizing='optimal', method='raw', **kwa
"""
def _truediv_repr(x, y):
return x / y

def _truediv_raw(x, y, n_frac):
precision_cast = (lambda m: np.array(m, dtype=object)) if n_frac >= _n_word_max else (lambda m: m)
return (x.val * precision_cast(2**(n_frac - x.n_frac + y.n_frac))) // y.val
# return np.floor_divide(np.multiply(x.val, precision_cast(2**(n_frac - x.n_frac + y.n_frac))), y.val)

def _truediv_raw_complex(x, y, n_frac):
precision_cast = (lambda m: np.array(m, dtype=object)) if n_frac >= _n_word_max else (lambda m: m)

y_norm = y.val.real ** 2 + y.val.imag ** 2
real_part = (x.val.real * y.val.real + x.val.imag * y.val.imag) * precision_cast(2**(n_frac - x.n_frac + y.n_frac)) // y_norm
imag_part = (x.val.imag * y.val.real - x.val.real * y.val.imag) * precision_cast(2**(n_frac - x.n_frac + y.n_frac)) // y_norm

return real_part + 1j*imag_part


if not isinstance(x, Fxp):
x = Fxp(x)
if not isinstance(y, Fxp):
y = Fxp(y)

if x.vdtype == complex or y.vdtype == complex:
_truediv_raw = _truediv_raw_complex

signed = x.signed or y.signed
n_int = x.n_int + y.n_frac + signed
n_frac = x.n_frac + y.n_int
Expand Down
55 changes: 40 additions & 15 deletions fxpmath/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ def __init__(self, val=None, signed=None, n_word=None, n_frac=None, n_int=None,

# check if a string-based format has been provided
if dtype is not None:
signed, n_word, n_frac = self._parseformatstr(dtype)
signed, n_word, n_frac, complex_flag = self._parseformatstr(dtype)

self.vdtype = complex if complex_flag else self.vdtype

# size
if not _initialized:
Expand Down Expand Up @@ -329,15 +331,22 @@ def _parseformatstr(self, fmt):
else:
n_frac = int(mo.group(3)[1:])
n_word = n_frac + n_int
complex_dtype = False
else:
mo = self._fxpfmt.match(fmt)
if mo:
signed = mo.group(1) == 's'
n_word = int(mo.group(2))
n_frac = int(mo.group(3))
complex_dtype = False
if mo.lastindex > 3:
_complex_str = str(mo.group(4))
if _complex_str == '-complex':
complex_dtype = True

else:
raise ValueError('unrecognized format string')
return signed, n_word, n_frac
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):
# sign by default
Expand Down Expand Up @@ -394,7 +403,9 @@ def resize(self, signed=None, n_word=None, n_frac=None, n_int=None, restore_val=
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:
raise ValueError('If dtype is specified, other sizing parameters must be `None`!')
signed, n_word, n_frac = self._parseformatstr(dtype)
signed, n_word, n_frac, complex_flag = self._parseformatstr(dtype)

self.vdtype = complex if complex_flag else self.vdtype

# n_int defined:
if n_word is None and n_frac is not None and n_int is not None:
Expand Down Expand Up @@ -611,7 +622,11 @@ def _format_inupt_val(self, val, return_sizes=False, raw=False):

if val is None:
val = 0
vdtype = int

if self.vdtype is None:
vdtype = int if n_frac < 1 else float
else:
vdtype = self.vdtype

elif isinstance(val, Fxp):
# if val is an Fxp object
Expand Down Expand Up @@ -729,7 +744,7 @@ 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 else '')
comp='-complex' if (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,
Expand Down Expand Up @@ -1479,12 +1494,16 @@ def bin(self, frac_dot=False):

if isinstance(self.val, (list, np.ndarray)) and self.val.ndim > 0:
if self.vdtype == complex:
rval = [ utils.binary_repr(int(val.real), n_word=self.n_word, n_frac=n_frac_dot) + '+' + utils.binary_repr(int(val.imag), n_word=self.n_word, n_frac=n_frac_dot) + 'j' 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) 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]
rval = utils.complex_repr(real_val, imag_val)
else:
rval = [utils.binary_repr(int(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) for val in self.val]
else:
if self.vdtype == complex:
rval = utils.binary_repr(int(self.val.real), n_word=self.n_word, n_frac=n_frac_dot) + '+' + utils.binary_repr(int(self.val.imag), n_word=self.n_word, n_frac=n_frac_dot) + 'j'
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)
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)
return rval
Expand All @@ -1497,14 +1516,18 @@ def hex(self, padding=True):

if isinstance(self.val, (list, np.ndarray)) and self.val.ndim > 0:
if self.vdtype == complex:
rval = [ utils.hex_repr(int(val.split('+')[0], 2), n_word=hex_n_word) + '+' + utils.hex_repr(int(val.split('+')[1][:-1], 2), n_word=hex_n_word) + 'j' for val in self.bin()]
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]
rval = utils.complex_repr(real_val, imag_val)
else:
rval = [utils.hex_repr(int(val, 2), n_word=hex_n_word) for val in self.bin()]
rval = [utils.hex_repr(val, n_word=hex_n_word, base=2) for val in self.bin()]
else:
if self.vdtype == complex:
rval = utils.hex_repr(int(self.bin().split('+')[0], 2), n_word=hex_n_word) + '+' + utils.hex_repr(int(self.bin().split('+')[1][:-1], 2), n_word=hex_n_word) + 'j'
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)
rval = utils.complex_repr(real_val, imag_val)
else:
rval = utils.hex_repr(int(self.bin(), 2), n_word=hex_n_word)
rval = utils.hex_repr(self.bin(), n_word=hex_n_word, base=2)
return rval

def base_repr(self, base, frac_dot=False):
Expand All @@ -1515,12 +1538,14 @@ def base_repr(self, base, frac_dot=False):

if isinstance(self.val, (list, np.ndarray)) and self.val.ndim > 0:
if self.vdtype == complex:
rval = [utils.base_repr(int(val.real), base=base, n_frac=n_frac_dot) + ('+' if val.imag >= 0 else '') + utils.base_repr(int(val.imag), base=base, n_frac=n_frac_dot) + 'j' for val in self.val]
real_val = [utils.base_repr(utils.int_array(val.real), base=base, n_frac=n_frac_dot) for val in self.val]
imag_val = [utils.base_repr(utils.int_array(val.imag), base=base, n_frac=n_frac_dot) for val in self.val]
rval = utils.complex_repr(real_val, imag_val)
else:
rval = [utils.base_repr(int(val), base=base, n_frac=n_frac_dot) for val in self.val]
rval = [utils.base_repr(utils.int_array(val), base=base, n_frac=n_frac_dot) for val in self.val]
else:
if self.vdtype == complex:
rval = utils.base_repr(int(self.val.real), base=base, n_frac=n_frac_dot) + ('+' if self.val.imag >= 0 else '') + utils.base_repr(int(self.val.imag), base=base, n_frac=n_frac_dot) + 'j'
rval = utils.complex_repr(utils.base_repr(int(self.val.real), base=base, n_frac=n_frac_dot), utils.base_repr(int(self.val.imag), base=base, n_frac=n_frac_dot))
else:
rval = utils.base_repr(int(self.val), base=base, n_frac=n_frac_dot)
return rval
Expand Down
32 changes: 30 additions & 2 deletions fxpmath/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,23 @@ def insert_frac_point(x_bin, n_frac):

return x_bin

@array_support
def binary_repr(x, n_word=None, n_frac=None):
if n_frac is None:
val = np.binary_repr(x, width=n_word)
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)
return val

def hex_repr(x, n_word=None, padding=None):
@array_support
def hex_repr(x, n_word=None, padding=None, base=10):
if base == 2:
x = int(x, 2)
elif base == 10:
pass
else:
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)))
elif padding is not None:
Expand All @@ -244,13 +253,32 @@ def hex_repr(x, n_word=None, padding=None):
val = '0x'+val[2:].upper()
return val

@array_support
def base_repr(x, n_word=None, base=2, n_frac=None):
if n_frac is None:
val = np.base_repr(x, base=base)
elif base == 2:
val = insert_frac_point(np.base_repr(x, base=base), n_frac=n_frac)
else:
val = np.base_repr(x, base=base)
return val

def complex_repr(r, i):
r = np.asarray(r)
i = np.asarray(i)

assert r.shape == i.shape

c = np.empty(r.shape, dtype=object)

if r.dtype.type is np.str_ and i.dtype.type is np.str_:
for idx in np.ndindex(r.shape):
imag_sign_symbol = '' if ('-' in str(i[idx]) or '+' in str(i[idx])) else '+'
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 c

def bits_len(x, signed=None):
if signed is None and x < 0:
signed = True
Expand Down
15 changes: 15 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ def test_base_representations():
assert x.hex() == '0x000000064'
assert y.hex() == '0x1FFFFFF9C'

# arrays
arr_fxp = Fxp(np.array([[1, 2]]))
assert np.all(arr_fxp.bin() == np.array(['001', '010']))
assert np.all(arr_fxp.hex() == np.array(['0x1', '0x2']))
assert np.all(arr_fxp.base_repr(base=2) == np.array(['1', '10']))
assert np.all(arr_fxp.base_repr(base=10) == np.array(['1', '2']))
assert np.all(arr_fxp.base_repr(base=10) == np.array(['1', '2']))

arr_fxp = Fxp(np.array([[15, -16], [-1, 0]]))
assert np.all(arr_fxp.bin() == np.array([['01111', '10000'], ['11111', '00000']]))
assert np.all(arr_fxp.hex() == np.array([['0x0F', '0x10'], ['0x1F', '0x00']]))
assert np.all(arr_fxp.base_repr(base=2) == np.array([['1111', '-10000'], ['-1', '0']]))
assert np.all(arr_fxp.base_repr(base=10) == np.array([['15', '-16'], ['-1', '0']]))
assert np.all(arr_fxp.base_repr(base=16) == np.array([['F', '-10'], ['-1', '0']]))


def test_like():
ref = Fxp(0.0, True, 16, 4)
Expand Down
Loading

0 comments on commit dd5b476

Please sign in to comment.