# Friendliest python library for fixed point algorithm simulation

My primary programming language is python. Is anyone aware of solid python libraries to simulate fixed point algorithms in python? A quick google search revealed this:

https://pypi.org/project/spfpm/

Does anyone have experience with this?

The libraries I tried out are:

I was specifically looking for a non-resizing fixed point library. I went through and ran some simple tests on them targeting a signed 16.16. Some of the operators are not defined for some libraries, those have the error as the result. The data type for each operation shows the representation of the result.

from IPython.display import display, Markdown


## Introduction

• For each library show simple and compound calculations of in fixed point.
• Show the contruction of a signed 32-bit value with 16 bit fractional precision.
• Show most positive and most negative value for each
bits = 32
int_bits = 16
frac_bits = bits-int_bits

def testbench(value, type_to_str_f=lambda x : str(type(x))):
def display_result(result):
try:
normal_repr = float(result)
except Exception as e:
normal_repr = str(result)

display(Markdown(f"+ Result: {repr(result)}\n+ float(result): {normal_repr}\n+ type(result): {type_to_str_f(result)}"))

one = (value + 1)-value

display(Markdown("### a"))
display_result(value)

# multiplication
result = value * value
display(Markdown("### a * a"))
display_result(result)

# division
display(Markdown("### a / a"))
try:
result = value / value
display_result(result)
except Exception as e:
display(Markdown(f"+ Result: {e}\n+ type(result): {type(e)}"))

result = value + value
display(Markdown("### a + a"))
display_result(result)

# subtraction
result = value - value
display(Markdown("### a - a"))
display_result(result)

# power
display(Markdown("### $$a^{a}$$"))
try:
result = value ** value
display_result(result)
except Exception as e:
display(Markdown(f"+ Result: {e}\n+ type(result): {type(e)}"))

# absolute value
display(Markdown("### |a|"))
try:
result = abs(value)
display_result(result)
except Exception as e:
display(Markdown(f"+ Result: {e}\n+ type(result): {type(e)}"))

# most positive number
display(Markdown("### Most Positive Value"))
i = 1
try:
most_positive = one*((1<<(int_bits-1))-i)
display_result(most_positive)
except Exception as e:
display(e, type(e))

# positive overflow
display(Markdown("### Most Positive Value + 1"))
try:
result = most_positive + 1
display_result(result)

except Exception as e:
display(Markdown(f"+ Result: {e}\n+ type(result): {type(e)}"))

# most negative
display(Markdown("### Most Negative Value"))
try:
most_negative = -one*((1<<(int_bits-1))-1) -1
display_result(most_negative)
except Exception as e:
display(e, type(e))

# negative overflow
display(Markdown("### Most Negative Value - 1"))
try:
result = most_negative - 1
display_result(result)

except Exception as e:
display(Markdown(f"+ Result: {e}\n+ type(result): {type(e)}"))



## spfpm

from FixedPoint import FXfamily, FXnum

# data type is called "family"
family = FXfamily(n_bits=frac_bits, n_intbits=int_bits)

# construct
value = FXnum(-3, family=family)
testbench(value, type_to_str_f=lambda x: f"{value.family}")



### a

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=-196608)
• float(result): -3.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### a * a

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=589824)
• float(result): 9.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### a / a

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=65535)
• float(result): 0.9999847412109375
• type(result): FXfamily(n_bits=16, n_intbits=16)

### a + a

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=-393216)
• float(result): -6.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### a - a

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=0)
• float(result): 0.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### $$a^{a}$$

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=-2428)
• float(result): -0.03704833984375
• type(result): FXfamily(n_bits=16, n_intbits=16)

### |a|

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=196608)
• float(result): 3.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### Most Positive Value

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=2147418112)
• float(result): 32767.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### Most Positive Value + 1

• Result:
• type(result): <class 'FixedPoint.FXoverflowError'>

### Most Negative Value

• Result: FXnum(family=FXfamily(n_bits=16, n_intbits=16), scaled_value=-2147483648)
• float(result): -32768.0
• type(result): FXfamily(n_bits=16, n_intbits=16)

### Most Negative Value - 1

• Result:
• type(result): <class 'FixedPoint.FXoverflowError'>

## fpbinary

from fpbinary import FpBinary

# construct
value = FpBinary(int_bits=int_bits, frac_bits=frac_bits, signed=True, value=-3)
testbench(value, type_to_str_f=lambda x: f"{x.format}")


### a

• Result: -3.0
• float(result): -3.0
• type(result): (16, 16)

### a * a

• Result: 9.0
• float(result): 9.0
• type(result): (32, 32)

### a / a

• Result: 1.0
• float(result): 1.0
• type(result): (33, 32)

### a + a

• Result: -6.0
• float(result): -6.0
• type(result): (17, 16)

### a - a

• Result: 0.0
• float(result): 0.0
• type(result): (17, 16)

### $$a^{a}$$

• Result: unsupported operand type(s) for ** or pow(): 'fpbinary.FpBinary' and 'fpbinary.FpBinary'
• type(result): <class 'TypeError'>

### |a|

• Result: 3.0
• float(result): 3.0
• type(result): (17, 16)

### Most Positive Value

• Result: 32767.0
• float(result): 32767.0
• type(result): (34, 16)

### Most Positive Value + 1

• Result: 32768.0
• float(result): 32768.0
• type(result): (35, 16)

### Most Negative Value

• Result: -32768.0
• float(result): -32768.0
• type(result): (36, 16)

### Most Negative Value - 1

• Result: -32769.0
• float(result): -32769.0
• type(result): (37, 16)

## fxpmath

from fxpmath import Fxp
# construct
value = Fxp(val=-3, dtype=f'fxp-s{bits}/{frac_bits}', rounding="fix", Shifting="trunc")
testbench(value, type_to_str_f=lambda x: f"{x.dtype}")


### a

• Result: fxp-s32/16(-3.0)
• float(result): -3.0
• type(result): fxp-s32/16

### a * a

• Result: fxp-s64/32(9.0)
• float(result): 9.0
• type(result): fxp-s64/32

### a / a

• Result: fxp-s64/31(1.0)
• float(result): 1.0
• type(result): fxp-s64/31

### a + a

• Result: fxp-s33/16(-6.0)
• float(result): -6.0
• type(result): fxp-s33/16

### a - a

• Result: fxp-s33/16(0.0)
• float(result): 0.0
• type(result): fxp-s33/16

### $$a^{a}$$

• Result: fxp-s53/52(-0.03703703703703698)
• float(result): -0.03703703703703698
• type(result): fxp-s53/52

### |a|

• Result: bad operand type for abs(): 'Fxp'
• type(result): <class 'TypeError'>

### Most Positive Value

• Result: fxp-s33/16(32767.0)
• float(result): 32767.0
• type(result): fxp-s33/16

### Most Positive Value + 1

• Result: fxp-s33/16(32768.0)
• float(result): 32768.0
• type(result): fxp-s33/16

### Most Negative Value

• Result: fxp-s33/16(-32768.0)
• float(result): -32768.0
• type(result): fxp-s33/16

### Most Negative Value - 1

• Result: fxp-s33/16(-32769.0)
• float(result): -32769.0
• type(result): fxp-s33/16

## numfi

from numfi import numfi

# construct
value = numfi(-3, s=True, w=bits, f=frac_bits, rounding='round', overflow='wrap', fixed=True)
testbench(value, type_to_str_f=lambda x: f"{repr(x)}")


### a

• Result: numfi([-3.]) s32/16-r/w
• float(result): -3.0
• type(result): numfi([-3.]) s32/16-r/w

### a * a

• Result: numfi([9.]) s32/16-r/w
• float(result): 9.0
• type(result): numfi([9.]) s32/16-r/w

### a / a

• Result: numfi([1.]) s32/16-r/w
• float(result): 1.0
• type(result): numfi([1.]) s32/16-r/w

### a + a

• Result: numfi([-6.]) s32/16-r/w
• float(result): -6.0
• type(result): numfi([-6.]) s32/16-r/w

### a - a

• Result: numfi([0.]) s32/16-r/w
• float(result): 0.0
• type(result): numfi([0.]) s32/16-r/w

### $$a^{a}$$

• Result: numfi([-0.03703308]) s32/16-r/w
• float(result): -0.037037037037037035
• type(result): numfi([-0.03703308]) s32/16-r/w

### |a|

• Result: numfi([3.]) s32/16-r/w
• float(result): 3.0
• type(result): numfi([3.]) s32/16-r/w

### Most Positive Value

• Result: numfi([32767.]) s32/16-r/w
• float(result): 32767.0
• type(result): numfi([32767.]) s32/16-r/w

### Most Positive Value + 1

• Result: numfi([-32768.]) s32/16-r/w
• float(result): -32768.0
• type(result): numfi([-32768.]) s32/16-r/w

### Most Negative Value

• Result: numfi([-32768.]) s32/16-r/w
• float(result): -32768.0
• type(result): numfi([-32768.]) s32/16-r/w

### Most Negative Value - 1

• Result: numfi([32767.]) s32/16-r/w
• float(result): 32767.0
• type(result): numfi([32767.]) s32/16-r/w

## fixedpoint

from fixedpoint.fixedpoint import FixedPoint
# construct
value = FixedPoint(-3, signed=True, m=int_bits, n=frac_bits, overflow='wrap', rounding='auto')
testbench(value, type_to_str_f=lambda x: f"{repr(x)}")


### a

• Result: FixedPoint('0xfffd0000', signed=1, m=16, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): -3.0
• type(result): FixedPoint('0xfffd0000', signed=1, m=16, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### a * a

• Result: FixedPoint('0x900000000', signed=1, m=32, n=32, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): 9.0
• type(result): FixedPoint('0x900000000', signed=1, m=32, n=32, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### a / a

• Result: unsupported operand type(s) for /: 'FixedPoint' and 'FixedPoint'
• type(result): <class 'TypeError'>

### a + a

• Result: FixedPoint('0x1fffa0000', signed=1, m=17, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): -6.0
• type(result): FixedPoint('0x1fffa0000', signed=1, m=17, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### a - a

• Result: FixedPoint('0x0', signed=1, m=17, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): 0.0
• type(result): FixedPoint('0x0', signed=1, m=17, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### $$a^{a}$$

• Result: Only positive integers are supported for exponentiation.
• type(result): <class 'TypeError'>

### |a|

• Result: FixedPoint('0x30000', signed=1, m=16, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): 3.0
• type(result): FixedPoint('0x30000', signed=1, m=16, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### Most Positive Value

• Result: FixedPoint('0x7fff0000', signed=1, m=33, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): 32767.0
• type(result): FixedPoint('0x7fff0000', signed=1, m=33, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### Most Positive Value + 1

• Result: FixedPoint('0x80000000', signed=1, m=34, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): 32768.0
• type(result): FixedPoint('0x80000000', signed=1, m=34, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### Most Negative Value

• Result: FixedPoint('0x3ffff80000000', signed=1, m=34, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): -32768.0
• type(result): FixedPoint('0x3ffff80000000', signed=1, m=34, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

### Most Negative Value - 1

• Result: FixedPoint('0x7ffff7fff0000', signed=1, m=35, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)
• float(result): -32769.0
• type(result): FixedPoint('0x7ffff7fff0000', signed=1, m=35, n=16, overflow='wrap', rounding='convergent', overflow_alert='error', mismatch_alert='warning', implicit_cast_alert='warning', str_base=16)

## fixedpointtest

• url: https://github.com/sixty-north/fixedpointtest
• Project name: fixedpointtest
• Package name: fixedpoint
• Notes: Variable size. Same package name Schweitzer-Engineering-Laboratories/fixedpoint. I changed the package name in setup.py to fixedpoint2 to have these coexist.
from fixedpoint2 import FixedPoint, QFormat
value = FixedPoint(-3, qformat=QFormat(integer_bits=int_bits, fraction_bits=frac_bits))
testbench(value, type_to_str_f=lambda x: f"{repr(x)}")


### a

• Result: FixedPoint(-3, QFormat(16, 16))
• float(result): -3.0
• type(result): FixedPoint(-3, QFormat(16, 16))

### a * a

• Result: FixedPoint(9, QFormat(33, 32))
• float(result): 9.0
• type(result): FixedPoint(9, QFormat(33, 32))

### a / a

• Result: FixedPoint(1, QFormat(33, 32))
• float(result): 1.0
• type(result): FixedPoint(1, QFormat(33, 32))

### a + a

• Result: FixedPoint(-6, QFormat(17, 16))
• float(result): -6.0
• type(result): FixedPoint(-6, QFormat(17, 16))

### a - a

• Result: FixedPoint(0, QFormat(18, 16))
• float(result): 0.0
• type(result): FixedPoint(0, QFormat(18, 16))

### $$a^{a}$$

• Result: FixedPoint(0.037037037037038089692941866815090179443359375, QFormat(51, 46))
• float(result): -0.03703703703703809
• type(result): FixedPoint(0.037037037037038089692941866815090179443359375, QFormat(51, 46))

### |a|

• Result: FixedPoint(3, QFormat(16, 16))
• float(result): 3.0
• type(result): FixedPoint(3, QFormat(16, 16))

### Most Positive Value

• Result: FixedPoint(32767, QFormat(35, 16))
• float(result): 32767.0
• type(result): FixedPoint(32767, QFormat(35, 16))

### Most Positive Value + 1

• Result: FixedPoint(32768, QFormat(36, 16))
• float(result): 32768.0
• type(result): FixedPoint(32768, QFormat(36, 16))

### Most Negative Value

• Result: FixedPoint(-32768, QFormat(37, 16))
• float(result): -32768.0
• type(result): FixedPoint(-32768, QFormat(37, 16))

### Most Negative Value - 1

• Result: FixedPoint(-32769, QFormat(38, 16))
• float(result): -32769.0
• type(result): FixedPoint(-32769, QFormat(38, 16))

## Other Libraries

• Can you say a bit about why fxpmath was best and what was wrong with the other libraries?
– rhz
Apr 10, 2022 at 15:22
• I've no clue what this is but upvote for effort. Would help if stuff's presented more compactly though. Apr 11, 2022 at 19:23
• @rhz In what sense is fxpmath best? Feb 13 at 10:09
• That was what I was trying to understand from @Simon Hobbs . IIRC, such a claim was made in his answer (but I don't see it now).
– rhz
Feb 13 at 18:23
• For performance, see: apytypes.github.io/apytypes/comparison.html My conclusion is that fxpmath is the most complete library at the moment. (The package which this is taken from, apytypes, currently only installable from source, has some unique features though. Plus performance. But requires more development to be as complete as fxpmath.) Apr 30 at 17:06