5
$\begingroup$

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?

$\endgroup$

1 Answer 1

4
$\begingroup$

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)}"))
    
    # addition
    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

$\endgroup$
5
  • 2
    $\begingroup$ Can you say a bit about why fxpmath was best and what was wrong with the other libraries? $\endgroup$
    – rhz
    Apr 10, 2022 at 15:22
  • $\begingroup$ I've no clue what this is but upvote for effort. Would help if stuff's presented more compactly though. $\endgroup$ Apr 11, 2022 at 19:23
  • $\begingroup$ @rhz In what sense is fxpmath best? $\endgroup$
    – Yair M
    Feb 13 at 10:09
  • $\begingroup$ 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). $\endgroup$
    – rhz
    Feb 13 at 18:23
  • $\begingroup$ 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.) $\endgroup$
    – Oscar
    Apr 30 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.