├── VERSION
├── tests
├── __init__.py
├── porting_v3_funcs.py
├── data
│ ├── pickletest_v3_5_2_p2_linux_64.data
│ ├── pickletest_v3_5_2_p3_linux_64.data
│ ├── pickletest_v3_5_2_p4_linux_64.data
│ ├── pickletest_v3_6_5_p2_linux_64.data
│ ├── pickletest_v3_6_5_p2_win32_32.data
│ ├── pickletest_v3_6_5_p2_win32_64.data
│ ├── pickletest_v3_6_5_p3_linux_64.data
│ ├── pickletest_v3_6_5_p3_win32_32.data
│ ├── pickletest_v3_6_5_p3_win32_64.data
│ ├── pickletest_v3_6_5_p4_linux_64.data
│ ├── pickletest_v3_6_5_p4_win32_32.data
│ ├── pickletest_v3_6_5_p4_win32_64.data
│ ├── pickletest_v3_7_5_p2_win32_64.data
│ ├── pickletest_v3_7_5_p3_win32_64.data
│ ├── pickletest_v3_7_5_p4_win32_64.data
│ ├── pickletest_v3_8_0_p2_win32_64.data
│ ├── pickletest_v3_8_0_p3_win32_64.data
│ ├── pickletest_v3_8_0_p4_win32_64.data
│ ├── pickletest_v3_8_0_p5_win32_64.data
│ ├── pickletest_v3_8_2_p2_linux_64.data
│ ├── pickletest_v3_8_2_p3_linux_64.data
│ ├── pickletest_v3_8_2_p4_linux_64.data
│ ├── pickletest_v3_8_2_p5_linux_64.data
│ ├── pickletest_v2_7_12_p2_linux2_64.data
│ └── readme
├── fpbinary_mem_leak_test.py
├── fpbinary_profiling_test.py
└── test_utils.py
├── release
├── __init__.py
├── lib
│ ├── __init__.py
│ ├── pypi.py
│ ├── github.py
│ ├── common.py
│ └── appveyor.py
├── test_all_local_build.sh
├── download_build.py
├── test_all_pypi.sh
├── run_build.py
├── release.py
└── building_and_releasing.rst
├── doc
├── changelog.rst
├── index.rst
├── objects.rst
├── Makefile
├── make.bat
├── conf.py
└── intro.rst
├── .gitignore
├── MANIFEST.in
├── .readthedocs.yml
├── src
├── fpbinaryenums.h
├── fpbinaryglobaldoc.h
├── fpbinaryarrayfuncs.h
├── fpbinaryversion.h
├── fpbinaryswitchable.h
├── fpbinarycomplexobject.h
├── fpbinaryobject.h
├── fpbinarylarge.h
├── fpbinarysmall.h
├── fpbinaryglobaldoc.c
├── fpbinarymodule.c
├── fpbinaryenums.c
├── fpbinarycommon.h
├── fpbinaryarrayfuncs.c
└── fpbinarycommon.c
├── README.rst
├── demos
├── basic_fir.py
├── basic_FpBinarySwitchable_use.py
└── basic_FpBinary_use.py
├── .clang-format
├── setup.py
├── CHANGELOG.rst
├── appveyor.yml
└── LICENSE
/VERSION:
--------------------------------------------------------------------------------
1 | 1.5.8
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/release/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/release/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/changelog.rst:
--------------------------------------------------------------------------------
1 |
2 | .. include:: ../CHANGELOG.rst
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.so
3 | *.o
4 | *~
5 | .python-version
--------------------------------------------------------------------------------
/tests/porting_v3_funcs.py:
--------------------------------------------------------------------------------
1 |
2 | def long(val):
3 | return int(val)
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include src/*.h
2 | include VERSION
3 | include ALPHA
4 | include README.rst
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_5_2_p2_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_5_2_p2_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_5_2_p3_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_5_2_p3_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_5_2_p4_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_5_2_p4_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p2_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p2_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p2_win32_32.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p2_win32_32.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p2_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p2_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p3_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p3_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p3_win32_32.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p3_win32_32.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p3_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p3_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p4_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p4_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p4_win32_32.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p4_win32_32.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_6_5_p4_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_6_5_p4_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_7_5_p2_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_7_5_p2_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_7_5_p3_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_7_5_p3_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_7_5_p4_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_7_5_p4_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_0_p2_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_0_p2_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_0_p3_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_0_p3_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_0_p4_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_0_p4_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_0_p5_win32_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_0_p5_win32_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_2_p2_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_2_p2_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_2_p3_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_2_p3_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_2_p4_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_2_p4_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v3_8_2_p5_linux_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v3_8_2_p5_linux_64.data
--------------------------------------------------------------------------------
/tests/data/pickletest_v2_7_12_p2_linux2_64.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smlgit/fpbinary/HEAD/tests/data/pickletest_v2_7_12_p2_linux2_64.data
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | fpbinary
2 | ========
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Contents
7 |
8 | intro
9 | objects
10 | changelog
--------------------------------------------------------------------------------
/tests/data/readme:
--------------------------------------------------------------------------------
1 | The win32 labelled files were generated on a Windows 10 Home (version 1703) x64 processor 64-bit OS. Note that python's platform variable labels windows systems as "win32".
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | configuration: doc/conf.py
11 |
12 | # Optionally set the version of Python and requirements required to build your docs
13 | python:
14 | version: 3.8
15 | install:
16 | - method: setuptools
17 | path: .
--------------------------------------------------------------------------------
/src/fpbinaryenums.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYENUMS_H_
6 | #define FPBINARYENUMS_H_
7 |
8 | #include "fpbinarycommon.h"
9 |
10 | extern PyTypeObject OverflowEnumType;
11 | extern PyTypeObject RoundingEnumType;
12 |
13 | void fpbinaryenums_InitModule(void);
14 |
15 | #endif /* FPBINARYENUMS_H_ */
16 |
--------------------------------------------------------------------------------
/src/fpbinaryglobaldoc.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #include "fpbinarycommon.h"
6 |
7 | extern FP_GLOBAL_Doc_VAR(resize_doc);
8 | extern FP_GLOBAL_Doc_VAR(str_ex_doc);
9 | extern FP_GLOBAL_Doc_VAR(bits_to_signed_doc);
10 | extern FP_GLOBAL_Doc_VAR(copy_doc);
11 | extern FP_GLOBAL_Doc_VAR(format_doc);
12 | extern FP_GLOBAL_Doc_VAR(is_signed_doc);
13 |
--------------------------------------------------------------------------------
/release/lib/pypi.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 |
4 | def upload_to_pypi_server(password, files_dir, server='testpypi'):
5 | """
6 | :param server: 'testpypi' or 'pypi'
7 | :return: True if successful
8 | """
9 | result = subprocess.run(['python', '-m', 'twine', 'upload', '--repository',
10 | '{}'.format(server), '-u', '__token__', '-p',
11 | '{}'.format(password), '--verbose',
12 | '{}/*'.format(files_dir.rstrip('/'))],
13 | check=True)
14 |
15 | return result == 0
16 |
17 |
--------------------------------------------------------------------------------
/doc/objects.rst:
--------------------------------------------------------------------------------
1 | fpbinary Objects
2 | ================
3 |
4 | FpBinary
5 | --------
6 | .. autoclass:: fpbinary.FpBinary
7 | :members:
8 |
9 | FpBinaryComplex
10 | ---------------
11 | .. autoclass:: fpbinary.FpBinaryComplex
12 | :members:
13 |
14 | FpBinarySwitchable
15 | ------------------
16 | .. autoclass:: fpbinary.FpBinarySwitchable
17 | :members:
18 |
19 | OverflowEnum
20 | ------------
21 | .. autoclass:: fpbinary.OverflowEnum
22 |
23 | RoundingEnum
24 | ------------
25 | .. autoclass:: fpbinary.RoundingEnum
26 |
27 | FpBinaryOverflowException
28 | -------------------------
29 | .. autoclass:: fpbinary.FpBinaryOverflowException
30 |
31 |
32 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/src/fpbinaryarrayfuncs.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYARRAYFUNCS_H_
6 | #define FPBINARYARRAYFUNCS_H_
7 |
8 | #include "fpbinarycommon.h"
9 |
10 | extern FP_GLOBAL_Doc_VAR(FpBinary_FromArray_doc);
11 | extern FP_GLOBAL_Doc_VAR(FpBinaryComplex_FromArray_doc);
12 | extern FP_GLOBAL_Doc_VAR(FpBinary_ArrayResize_doc);
13 |
14 | PyObject* FpBinary_FromArray(PyObject* self, PyObject* args, PyObject* kwds);
15 | PyObject* FpBinaryComplex_FromArray(PyObject* self, PyObject* args, PyObject* kwds);
16 | PyObject* FpBinary_ArrayResize(PyObject* self, PyObject* args, PyObject* kwds);
17 |
18 | #endif /* FPBINARYARRAYFUNCS_H_ */
19 |
--------------------------------------------------------------------------------
/src/fpbinaryversion.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | /******************************************************************************
6 | *
7 | * This is where the fpbinary package version is defined.
8 | * Don't change the format of the defines - they are assumed by setup.py .
9 | *
10 | *****************************************************************************/
11 |
12 | #ifndef FPBINARYVERSION_H_
13 | #define FPBINARYVERSION_H_
14 |
15 | #include "fpbinarycommon.h"
16 |
17 | /* VERSION_STRING needs to be set when the compiler
18 | * is invoked (normally done by setup.py).
19 | */
20 | #define FPBINARY_VERSION_STR xstr(VERSION_STRING)
21 |
22 | #endif /* FPBINARYVERSION_H_ */
23 |
--------------------------------------------------------------------------------
/release/lib/github.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from . import common
3 |
4 |
5 | def _github_rest_api_build_headers(auth_token, customer_headers={}):
6 | customer_headers['Authorization'] = 'token {}'.format(auth_token)
7 | customer_headers['Content-type'] = 'application/json'
8 | return customer_headers
9 |
10 |
11 | def _github_get_full_url(relative_url):
12 | return common.concat_urls(['https://api.github.com', relative_url])
13 |
14 |
15 | def create_light_tag(auth_token, owner, repository, sha, tagname):
16 | r = requests.post(
17 | _github_get_full_url('/repos/{}/{}/git/refs'.format(owner, repository)),
18 | headers=_github_rest_api_build_headers(auth_token),
19 | json={'ref': 'refs/tags/{}'.format(tagname),'sha': sha})
20 |
21 | # 201 == "created"
22 | if r.status_code != 201:
23 | r.raise_for_status()
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/src/fpbinaryswitchable.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYSWITCHABLE_H
6 | #define FPBINARYSWITCHABLE_H
7 |
8 | #include "fpbinaryobject.h"
9 |
10 | typedef struct
11 | {
12 | PyObject_HEAD bool fp_mode;
13 | PyObject *fp_mode_value;
14 | double dbl_mode_value;
15 | double dbl_mode_min_value;
16 | double dbl_mode_max_value;
17 | } FpBinarySwitchableObject;
18 |
19 | extern PyTypeObject FpBinarySwitchable_Type;
20 |
21 | #define FpBinarySwitchable_CheckExact(op) \
22 | (Py_TYPE(op) == &FpBinarySwitchable_Type)
23 | #define FpBinarySwitchable_Check(op) \
24 | PyObject_TypeCheck(op, &FpBinarySwitchable_Type)
25 |
26 | void FpBinarySwitchable_InitModule(void);
27 |
28 | #endif
29 |
--------------------------------------------------------------------------------
/src/fpbinarycomplexobject.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYCOMPLEXOBJECT_H
6 | #define FPBINARYCOMPLEXOBJECT_H
7 |
8 | #include "fpbinarycommon.h"
9 |
10 | typedef struct
11 | {
12 | PyObject_HEAD
13 | PyObject *real;
14 | PyObject *imag;
15 | } FpBinaryComplexObject;
16 |
17 | extern PyTypeObject FpBinaryComplex_Type;
18 |
19 | #define PYOBJ_TO_REAL_FP(ob) (((FpBinaryComplexObject *)ob)->real)
20 | #define PYOBJ_TO_IMAG_FP(ob) (((FpBinaryComplexObject *)ob)->imag)
21 | #define PYOBJ_TO_REAL_FP_PYOBJ(ob) ((PyObject *) PYOBJ_TO_REAL_FP(ob))
22 | #define PYOBJ_TO_IMAG_FP_PYOBJ(ob) ((PyObject *) PYOBJ_TO_IMAG_FP(ob))
23 |
24 | #define FpBinaryComplex_CheckExact(op) (Py_TYPE(op) == &FpBinaryComplex_Type)
25 | #define FpBinaryComplex_Check(op) PyObject_TypeCheck(op, &FpBinaryComplex_Type)
26 |
27 |
28 | #endif
29 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://img.shields.io/pypi/v/fpbinary.svg
2 | :target: https://pypi.org/project/fpbinary/
3 |
4 | .. image:: https://readthedocs.org/projects/fpbinary/badge/?version=latest
5 | :target: https://fpbinary.readthedocs.io/en/latest/
6 |
7 | Introduction
8 | ================
9 |
10 | fpbinary is a binary fixed point package for Python. It is written as an extension module for the CPython implementation of Python.
11 |
12 | fpbinary was created with **fast** simulation of math-intensive systems destined for digital hardware (e.g. signal processing) in mind. While Python has great support for signal processing functions, there is no offical fixed point package. Implementaions written completely in Python tend to be frustratingly slow, so fpbinary is an attempt to make fixed point simulation of large, complex hardware systems in Python viable.
13 |
14 |
15 | Documentation
16 | =============
17 |
18 | For installation instructions, feature list and other documentation: `Read the Docs `_
19 |
--------------------------------------------------------------------------------
/release/test_all_local_build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # ==================================================================
4 | #
5 | # Will run the library tests on all pyenv versions by doing local
6 | # builds.
7 | #
8 | # The following need to be installed for this script to run:
9 | # - pyenv
10 | # - for each pyenv python version installed, virtualenv
11 |
12 |
13 | # Exit on error
14 | set -e
15 |
16 | # Read pyenv versions installed
17 | declare -a pyenv_versions
18 | readarray -t pyenv_versions < <(pyenv versions --bare --skip-aliases)
19 |
20 | # Remove build and dist dirs to force a clear install if compiling from source
21 | rm -rf build dist
22 |
23 |
24 | # For each pyenv version, create a virtualenv and run the install
25 | for v in ${pyenv_versions[@]}; do
26 |
27 | echo "======================================================"
28 | echo $v
29 | echo "======================================================"
30 |
31 | eval "$(pyenv init -)"
32 | pyenv shell $v
33 |
34 | virtualenv --clear venv$v
35 | source venv$v/bin/activate
36 |
37 | # Need numpy for tests
38 | pip install numpy
39 | pip install scipy
40 |
41 | python setup.py install
42 | python -m unittest discover -s tests -p testFpBinary*
43 |
44 | # Deactivate virtualenv
45 | deactivate
46 | rm -rf venv$v
47 | done
48 |
49 |
50 |
--------------------------------------------------------------------------------
/demos/basic_fir.py:
--------------------------------------------------------------------------------
1 | import math
2 | from collections import deque
3 | from fpbinary import FpBinary, OverflowEnum, RoundingEnum
4 |
5 | # ======================================
6 | # Basic FIR Demo
7 | data_path_len = 32
8 |
9 | # Just a delay filter
10 | filter_len = 11
11 | filter_coeffs = int(filter_len / 2) * [FpBinary(2, 30, value=0.0)] + \
12 | [FpBinary(2, 30, value=1.0)] + \
13 | int(filter_len / 2) * [FpBinary(2, 30, value=0.0)]
14 |
15 | # 32 bit data
16 | delay_line = deque(filter_len * [FpBinary(2, 30, value=0.0)], filter_len)
17 |
18 | def fir_next_sample(sample):
19 | # Allow for hardware specs
20 | guard_bits = int(math.log(filter_len, 2)) + 1
21 | adder_bits = 48
22 | adder_in_format = (2 + guard_bits, adder_bits - 2 - guard_bits)
23 | output_format = (2 + guard_bits, 32 - 2 - guard_bits)
24 |
25 | # Ensure data is correct format
26 | delay_line.appendleft(sample.resize(format=delay_line[0], overflow_mode=OverflowEnum.wrap))
27 |
28 | accum = 0.0
29 | for tap, coeff in zip(delay_line, filter_coeffs):
30 | accum += (tap * coeff).resize(adder_in_format, round_mode=RoundingEnum.direct_neg_inf)
31 |
32 | return accum.resize(output_format)
33 |
34 |
35 | if __name__ == '__main__':
36 | for i in range(-10, 10):
37 | next_sample = fir_next_sample(FpBinary(int_bits=2, frac_bits=30, value=i / 10.0))
38 | print(next_sample, next_sample.format)
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/fpbinaryobject.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYOBJECT_H
6 | #define FPBINARYOBJECT_H
7 |
8 | #include "fpbinarycommon.h"
9 |
10 | typedef struct
11 | {
12 | PyObject_HEAD fpbinary_base_t *base_obj;
13 | } FpBinaryObject;
14 |
15 | #define PYOBJ_TO_BASE_FP(ob) (((FpBinaryObject *)ob)->base_obj)
16 | #define PYOBJ_TO_BASE_FP_PYOBJ(ob) ((PyObject *)PYOBJ_TO_BASE_FP(ob))
17 |
18 | extern PyTypeObject FpBinary_Type;
19 |
20 | #define FpBinary_CheckExact(op) (Py_TYPE(op) == &FpBinary_Type)
21 | #define FpBinary_Check(op) PyObject_TypeCheck(op, &FpBinary_Type)
22 |
23 | FpBinaryObject *FpBinary_FromParams(long int_bits, long frac_bits,
24 | bool is_signed, double value,
25 | PyObject *bit_field,
26 | PyObject *format_instance);
27 | FpBinaryObject *FpBinary_FromValue(PyObject *value);
28 | void FpBinary_SetTwoInstToSameFormat(PyObject **op1, PyObject **op2);
29 |
30 | /*
31 | * Functions for client objects to easily call FpBinary user-specified methods.
32 | * These tend to use the Python-like call interfaces rather than using an insider's
33 | * knowledge of the underlying structures of FpBinary, so should be safe to use
34 | * on objects that quack like an FpBinary...
35 | */
36 | PyObject *FpBinary_ResizeWithCInts(PyObject *value, long int_bits, long frac_bits,
37 | long round_mode, long overflow_mode);
38 | PyObject *FpBinary_ResizeWithFormatInstance(PyObject *value, PyObject *format_instance,
39 | long round_mode, long overflow_mode);
40 |
41 | #endif
42 |
--------------------------------------------------------------------------------
/src/fpbinarylarge.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYLARGE_H
6 | #define FPBINARYLARGE_H
7 |
8 | #include "fpbinarycommon.h"
9 |
10 | typedef struct
11 | {
12 | fpbinary_base_t fpbinary_base;
13 | PyObject *int_bits;
14 | PyObject *frac_bits;
15 | PyObject *scaled_value;
16 | bool is_signed;
17 | } FpBinaryLargeObject;
18 |
19 | extern PyTypeObject FpBinary_LargeType;
20 | extern fpbinary_private_iface_t FpBinary_LargePrvIface;
21 |
22 | #define FpBinaryLarge_CheckExact(op) (Py_TYPE(op) == &FpBinary_LargeType)
23 | /* TODO: */
24 | #define FpBinaryLarge_Check(op) FpBinaryLarge_CheckExact(op)
25 |
26 | void FpBinaryLarge_InitModule(void);
27 |
28 | /* Helper functions for use of top client object. */
29 |
30 | void FpBinaryLarge_FormatAsInts(PyObject *self, FP_INT_TYPE *out_int_bits,
31 | FP_INT_TYPE *out_frac_bits);
32 | PyObject *FpBinaryLarge_BitsAsPylong(PyObject *obj);
33 | bool FpBinaryLarge_IsSigned(PyObject *obj);
34 |
35 | PyObject *FpBinaryLarge_FromDouble(double value, FP_INT_TYPE int_bits,
36 | FP_INT_TYPE frac_bits, bool is_signed,
37 | fp_overflow_mode_t overflow_mode,
38 | fp_round_mode_t round_mode);
39 | PyObject *FpBinaryLarge_FromBitsPylong(PyObject *scaled_value,
40 | FP_INT_TYPE int_bits,
41 | FP_INT_TYPE frac_bits, bool is_signed);
42 |
43 | PyObject *FpBinaryLarge_FromPickleDict(PyObject *dict);
44 | bool FpBinaryLarge_UpdatePickleDict(PyObject *self, PyObject *dict);
45 |
46 | #endif
47 |
--------------------------------------------------------------------------------
/src/fpbinarysmall.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYSMALL_H
6 | #define FPBINARYSMALL_H
7 |
8 | #include "fpbinarycommon.h"
9 |
10 | typedef struct
11 | {
12 | fpbinary_base_t fpbinary_base;
13 | FP_INT_TYPE int_bits;
14 | FP_INT_TYPE frac_bits;
15 | FP_UINT_TYPE scaled_value;
16 | bool is_signed;
17 | } FpBinarySmallObject;
18 |
19 | extern PyTypeObject FpBinary_SmallType;
20 | extern fpbinary_private_iface_t FpBinary_SmallPrvIface;
21 |
22 | #define FpBinarySmall_CheckExact(op) (Py_TYPE(op) == &FpBinary_SmallType)
23 | /* TODO: */
24 | #define FpBinarySmall_Check(op) FpBinarySmall_CheckExact(op)
25 |
26 | #define FP_SMALL_MAX_BITS FP_UINT_NUM_BITS
27 |
28 | /* Helper functions for use of top client object. */
29 |
30 | void FpBinarySmall_FormatAsInts(PyObject *self, FP_INT_TYPE *out_int_bits,
31 | FP_INT_TYPE *out_frac_bits);
32 | PyObject *FpBinarySmall_BitsAsPylong(PyObject *obj);
33 | PyObject *FpBinarySmall_FromDouble(double value, FP_INT_TYPE int_bits,
34 | FP_INT_TYPE frac_bits, bool is_signed,
35 | fp_overflow_mode_t overflow_mode,
36 | fp_round_mode_t round_mode);
37 | PyObject *FpBinarySmall_FromBitsPylong(PyObject *scaled_value,
38 | FP_INT_TYPE int_bits,
39 | FP_INT_TYPE frac_bits, bool is_signed);
40 |
41 | PyObject *FpBinarySmall_FromPickleDict(PyObject *dict);
42 | bool FpBinarySmall_UpdatePickleDict(PyObject *self, PyObject *dict);
43 |
44 | bool FpBinarySmall_IsNegative(PyObject *obj);
45 | FP_UINT_TYPE fpbinarysmall_can_divide_ops(FP_UINT_TYPE op1_total_bits,
46 | FP_UINT_TYPE op2_total_bits);
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('.'))
16 |
17 |
18 | # -- Project information -----------------------------------------------------
19 |
20 | project = 'fpbinary'
21 | copyright = '2022, smlgit'
22 | author = 'smlgit'
23 |
24 | master_doc = 'index'
25 |
26 |
27 | # -- General configuration ---------------------------------------------------
28 |
29 | # Add any Sphinx extension module names here, as strings. They can be
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 | # ones.
32 | extensions = [
33 | 'sphinx.ext.autodoc',
34 | 'sphinx.ext.napoleon'
35 | ]
36 |
37 | # Add any paths that contain templates here, relative to this directory.
38 | templates_path = ['_templates']
39 |
40 | # List of patterns, relative to source directory, that match files and
41 | # directories to ignore when looking for source files.
42 | # This pattern also affects html_static_path and html_extra_path.
43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
44 |
45 |
46 | # -- Options for HTML output -------------------------------------------------
47 |
48 | # The theme to use for HTML and HTML Help pages. See the documentation for
49 | # a list of builtin themes.
50 | #
51 | html_theme = 'sphinx_rtd_theme'
52 |
53 | # Add any paths that contain custom static files (such as style sheets) here,
54 | # relative to this directory. They are copied after the builtin static files,
55 | # so a file named "default.css" will overwrite the builtin "default.css".
56 | html_static_path = ['_static']
57 |
58 |
59 | # -- Extension configuration -------------------------------------------------
60 |
--------------------------------------------------------------------------------
/demos/basic_FpBinarySwitchable_use.py:
--------------------------------------------------------------------------------
1 | import random
2 | from fpbinary import FpBinary, FpBinarySwitchable
3 |
4 | fp_mode = True
5 |
6 |
7 | # An instance can be created with fixed point and float values
8 | FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7)
9 |
10 |
11 | # FpBinarySwitchable looks like FpBinary
12 | num1 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7)
13 | num2 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=7.3), float_value=6.7)
14 | # {} + {} == {} {} * {} = {}'.format(num1, num2, num1 + num2, num1, num2, num1 * num2))
15 |
16 |
17 | # FpBinarySwitchable plays well with FpBinary
18 | num1 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7)
19 | num2 = FpBinary(8, 8, value=7.3)
20 | num1, num2, num1 + num2, num1, num2, num1 * num2, type(num1 * num2)
21 |
22 |
23 | # FpBinarySwitchable is so called because you can switch operation between fixed and floating point
24 | # simply by flicking a constructor variable. Operations that aren\'t supported by normal numerical
25 | # objects (like resize) can still be called in floating point mode without an exception.
26 |
27 | for i in range(0, 2):
28 | num1 = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7)
29 | # fp_mode: {} Resized: {}'.format(fp_mode, num1.resize((2, 7))))
30 | fp_mode = not fp_mode
31 |
32 |
33 | # FpBinarySwitchable can also be used to track min and max values while in floating point mode by
34 | # using the value property
35 |
36 | num1 = FpBinarySwitchable(fp_mode=False, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0)
37 | for i in range(0, 5):
38 | num1.value = FpBinary(8, 8, value=random.uniform(-1.0, 1.0))
39 | num1
40 |
41 | num1.min_value, num1.max_value
42 |
43 |
44 | # The value property can be set to other FpBinarySwitchable instances too
45 | num1 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0)
46 | num2 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=5.0), float_value=5.0)
47 | num3 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0)
48 | num3.value = num1 + num2
49 | type(num1), type(num2), type(num3)
50 |
--------------------------------------------------------------------------------
/release/lib/common.py:
--------------------------------------------------------------------------------
1 | import os, json, re
2 |
3 |
4 | def get_version_from_appveyor_build_name(build_name):
5 | # For release builds, build name is:
6 | # branch_name-rc
7 | # and need to return:
8 | #
9 | #
10 | # For non-release builds, build name is:
11 | # branch_name-a
12 | # and need to return:
13 | # a
14 |
15 | if re.match('^.+-[0-9].[0-9].[0-9]rc[0-9]+$', build_name):
16 | # Release build
17 | return re.sub('rc[0-9]+$', '', re.sub('^.+-', '', build_name))
18 | elif re.match('^.+-[0-9].[0-9].[0-9]a[0-9]+$', build_name):
19 | # Non-release build
20 | return re.sub('^.+-', '', build_name)
21 |
22 | return None
23 |
24 |
25 | def get_security_config_file_path():
26 | if (os.path.exists('security.json')):
27 | return os.path.abspath('security.json')
28 |
29 | if (os.path.exists('release/security.json')):
30 | return os.path.abspath('release/security.json')
31 |
32 | return None
33 |
34 |
35 | def get_appveyor_security():
36 | security_file = get_security_config_file_path()
37 |
38 | if security_file is not None:
39 | with open(security_file, 'r') as f:
40 | config = json.load(f)
41 |
42 | return config['APPVEYOR']
43 |
44 | return None
45 |
46 |
47 | def get_testpypi_security():
48 | security_file = get_security_config_file_path()
49 |
50 | if security_file is not None:
51 | with open(security_file, 'r') as f:
52 | config = json.load(f)
53 |
54 | return config['TESTPYPI']
55 |
56 | return None
57 |
58 |
59 | def get_pypi_security():
60 | security_file = get_security_config_file_path()
61 |
62 | if security_file is not None:
63 | with open(security_file, 'r') as f:
64 | config = json.load(f)
65 |
66 | return config['PYPI']
67 |
68 | return None
69 |
70 |
71 | def get_github_security():
72 | security_file = get_security_config_file_path()
73 |
74 | if security_file is not None:
75 | with open(security_file, 'r') as f:
76 | config = json.load(f)
77 |
78 | return config['GITHUB']
79 |
80 | return None
81 |
82 |
83 | def concat_urls(urls):
84 |
85 | result = ''
86 |
87 | for url in urls:
88 | result += url.strip('/') + '/'
89 |
90 | return result.strip('/')
91 |
--------------------------------------------------------------------------------
/release/download_build.py:
--------------------------------------------------------------------------------
1 | import argparse, logging, os
2 | from lib.appveyor import get_build_from_name, get_last_build, download_build_artifacts
3 | from lib.pypi import upload_to_pypi_server
4 | from lib.common import get_appveyor_security, get_testpypi_security
5 |
6 |
7 | def main():
8 | logging.basicConfig()
9 | logging.root.setLevel(logging.INFO)
10 |
11 | parser = argparse.ArgumentParser(description='Downloads the artifacts of a build on Appveyor.')
12 | parser.add_argument('buildname', type=str,
13 | help='The build name (or \'version\' as Appveyor calls it) to download. '
14 | 'Enter a branch name if you want to download the latest build from that branch.')
15 | parser.add_argument('outputdir', type=str,
16 | help='The artifacts of the build will be downloaded to this directory.')
17 | parser.add_argument('--upload-dest', type=str, default=None, choices=['pypi', 'testpypi'],
18 | help='Specify to upload the artifacts to the respective package server.')
19 | args = parser.parse_args()
20 |
21 | security_dict = get_appveyor_security()
22 |
23 | build_name = None
24 |
25 | # First see if the build name exists as a build
26 | try:
27 | logging.info('Looking for build {}'.format(args.buildname))
28 | get_build_from_name(security_dict['token'], security_dict['account'],
29 | 'fpbinary', args.buildname)
30 | build_name = args.buildname
31 | except:
32 | # Then try a branch
33 | try:
34 | logging.info(('Looking for branch {}'.format(args.buildname)))
35 | build = get_last_build(security_dict['token'], security_dict['account'],
36 | 'fpbinary', branch=args.buildname)
37 | build_name = build['version']
38 | logging.info('Found build {} on branch {}'.format(build['version'], args.buildname))
39 | except:
40 | pass
41 |
42 |
43 | if build_name is None:
44 | logging.error('Failed to find a build or branch called {}'.format(args.buildname))
45 | exit(1)
46 |
47 | logging.info('Downloading build {} ...'.format(build_name))
48 | download_build_artifacts(security_dict['token'], security_dict['account'],
49 | 'fpbinary', os.path.abspath(args.outputdir), build_name=build_name)
50 |
51 | if args.upload_dest is not None:
52 | upload_to_pypi_server(get_testpypi_security()['token'], args.outputdir, server=args.upload_dest)
53 |
54 |
55 | if __name__ == '__main__':
56 | main()
--------------------------------------------------------------------------------
/release/test_all_pypi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # ==================================================================
4 | # The following need to be installed for this script to run:
5 | # - pyenv
6 | # - for each pyenv python version installed, virtualenv
7 | # ==================================================================
8 | #
9 | # Will run the library tests on all pyenv versions by installing
10 | # from either test pypi or pypi proper.
11 | #
12 | # Command syntax:
13 | # ./test_all_pypi.sh server_name [version]
14 | # where server_name is either test or pypi and
15 | # version is the version as shown on the server. If
16 | # not specified, will install the latest.
17 | #
18 | # The following need to be installed for this script to run:
19 | # - pyenv
20 | # - for each pyenv python version installed, virtualenv
21 |
22 |
23 | # Exit on error
24 | set -e
25 |
26 | # Commandline args
27 | args=("$@")
28 |
29 | if [ ${#args[@]} -lt 1 ]; then
30 | echo "You must specify either 'pypi' or 'test' as the pypi install location."
31 | (exit 1);
32 | fi
33 |
34 | location=${args[0]}
35 | if [ "$location" != "pypi" ] && [ "$location" != "test" ]; then
36 | echo "You must specify either 'pypi' or 'test' as the pypi install location."
37 | (exit 1);
38 | fi
39 |
40 | version=""
41 | if [ ${#args[@]} -gt 1 ]; then
42 | version=${args[1]}
43 | fi
44 |
45 | if [ "$version" == "" ]; then
46 | package="fpbinary"
47 | else
48 | package="fpbinary==$version"
49 | fi
50 |
51 |
52 | # Read pyenv versions installed
53 | declare -a pyenv_versions
54 | readarray -t pyenv_versions < <(pyenv versions --bare --skip-aliases)
55 |
56 | # Remove build and dist dirs to force a clear install if compiling from source
57 | rm -rf build dist
58 |
59 | # For each pyenv version, create a virtualenv and run the install
60 | for v in ${pyenv_versions[@]}; do
61 |
62 | echo "======================================================"
63 | echo $v
64 | echo "======================================================"
65 |
66 | eval "$(pyenv init -)"
67 | pyenv shell $v
68 |
69 | virtualenv --clear venv$v
70 | source venv$v/bin/activate
71 |
72 | # Need numpy for tests
73 | pip install numpy
74 | pip install scipy
75 |
76 | echo "======================================================"
77 | echo "Installing $package via pypi site $location"
78 | echo "======================================================"
79 |
80 | if [ "$location" == "test" ]; then
81 | pip install -I --pre --no-cache-dir --index-url https://test.pypi.org/simple/ --no-deps $package
82 | else
83 | pip install -I --pre --no-cache-dir --no-deps $package
84 | fi
85 |
86 | # Run fpbinary tests
87 | python -m unittest discover -s tests -p testFpBinary*
88 |
89 | # Deactivate virtualenv
90 | deactivate
91 | rm -rf venv$v
92 | done
93 |
94 |
95 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | # BasedOnStyle: LLVM
4 | AccessModifierOffset: -2
5 | AlignAfterOpenBracket: Align
6 | AlignConsecutiveAssignments: false
7 | AlignConsecutiveDeclarations: false
8 | AlignEscapedNewlinesLeft: false
9 | AlignOperands: true
10 | AlignTrailingComments: true
11 | AllowAllParametersOfDeclarationOnNextLine: true
12 | AllowShortBlocksOnASingleLine: false
13 | AllowShortCaseLabelsOnASingleLine: true
14 | AllowShortFunctionsOnASingleLine: All
15 | AllowShortIfStatementsOnASingleLine: false
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterDefinitionReturnType: true
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: false
21 | BinPackArguments: true
22 | BinPackParameters: true
23 | BraceWrapping:
24 | AfterClass: true
25 | AfterControlStatement: true
26 | AfterEnum: true
27 | AfterFunction: true
28 | AfterNamespace: true
29 | AfterObjCDeclaration: true
30 | AfterStruct: true
31 | AfterUnion: true
32 | BeforeCatch: true
33 | BeforeElse: true
34 | IndentBraces: false
35 | BreakBeforeBinaryOperators: None
36 | BreakBeforeBraces: Custom
37 | BreakBeforeTernaryOperators: true
38 | BreakConstructorInitializersBeforeComma: false
39 | ColumnLimit: 80
40 | CommentPragmas: '^ IWYU pragma:'
41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
42 | ConstructorInitializerIndentWidth: 4
43 | ContinuationIndentWidth: 4
44 | Cpp11BracedListStyle: true
45 | DerivePointerAlignment: false
46 | DisableFormat: false
47 | ExperimentalAutoDetectBinPacking: false
48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
49 | IncludeCategories:
50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
51 | Priority: 2
52 | - Regex: '^(<|"(gtest|isl|json)/)'
53 | Priority: 3
54 | - Regex: '.*'
55 | Priority: 1
56 | IndentCaseLabels: true
57 | IndentWidth: 4
58 | IndentWrappedFunctionNames: false
59 | KeepEmptyLinesAtTheStartOfBlocks: true
60 | MacroBlockBegin: ''
61 | MacroBlockEnd: ''
62 | MaxEmptyLinesToKeep: 1
63 | NamespaceIndentation: None
64 | ObjCBlockIndentWidth: 2
65 | ObjCSpaceAfterProperty: false
66 | ObjCSpaceBeforeProtocolList: true
67 | PenaltyBreakBeforeFirstCallParameter: 19
68 | PenaltyBreakComment: 300
69 | PenaltyBreakFirstLessLess: 120
70 | PenaltyBreakString: 1000
71 | PenaltyExcessCharacter: 1000000
72 | PenaltyReturnTypeOnItsOwnLine: 60
73 | PointerAlignment: Right
74 | ReflowComments: true
75 | SortIncludes: true
76 | SpaceAfterCStyleCast: false
77 | SpaceBeforeAssignmentOperators: true
78 | SpaceBeforeParens: ControlStatements
79 | SpaceInEmptyParentheses: false
80 | SpacesBeforeTrailingComments: 1
81 | SpacesInAngles: false
82 | SpacesInContainerLiterals: true
83 | SpacesInCStyleCastParentheses: false
84 | SpacesInParentheses: false
85 | SpacesInSquareBrackets: false
86 | Standard: Cpp11
87 | TabWidth: 8
88 | UseTab: Never
89 | ...
90 |
91 |
--------------------------------------------------------------------------------
/release/run_build.py:
--------------------------------------------------------------------------------
1 | import argparse, logging, os
2 | from lib.appveyor import start_build, download_build_artifacts
3 | from lib.pypi import upload_to_pypi_server
4 | from lib.common import get_appveyor_security, get_testpypi_security
5 |
6 |
7 | default_output_dir = os.path.abspath('download_dir')
8 |
9 |
10 | def main():
11 | logging.basicConfig()
12 | logging.root.setLevel(logging.INFO)
13 |
14 | parser = argparse.ArgumentParser(description='Run an fpbinary build on Appveyor.')
15 | parser.add_argument('branch', type=str,
16 | help='The fpbinary Github branch to run a build on.')
17 | parser.add_argument('--output-dir', type=str, default=None,
18 | help='If specified, the artifacts of the build will be downloaded to this directory.')
19 | parser.add_argument('--upload-dest', type=str, default=None, choices=['pypi', 'testpypi'],
20 | help='Specify to upload the artifacts to the respective package server.')
21 | parser.add_argument('--wait-complete', action='store_true',
22 | help='If specified, will wait until the build is finished. Automatically set if'
23 | '--upload-dest or --output-dir are set.')
24 | parser.add_argument('--release', action='store_true',
25 | help='If specified, do the build without the \'a\' pre release specifier in the output files.')
26 | parser.add_argument('--install-from-testpypi', action='store_true',
27 | help='If specified, the build will upload to test pypi and install from it.')
28 |
29 | args = parser.parse_args()
30 |
31 | if args.upload_dest is not None and args.output_dir is None:
32 | args.output_dir = default_output_dir
33 |
34 | if args.output_dir is not None:
35 | args.wait_complete = True
36 |
37 | appveyor_security_dict = get_appveyor_security()
38 | testpypi_security_dict = get_testpypi_security()
39 |
40 | result_tuple = start_build(appveyor_security_dict['token'], appveyor_security_dict['account'],
41 | 'fpbinary', args.branch,
42 | install_from_testpypi=args.install_from_testpypi,
43 | is_release_build=args.release, wait_for_finish=args.wait_complete)
44 |
45 | if not args.wait_complete:
46 | return
47 |
48 | if result_tuple is None:
49 | logging.error('Build didn\'t start')
50 | exit(1)
51 |
52 | build_success = result_tuple[0]
53 | build_id = result_tuple[1]
54 |
55 | if build_success is True and args.output_dir is not None:
56 | download_build_artifacts(appveyor_security_dict['token'], appveyor_security_dict['account'],
57 | 'fpbinary', os.path.abspath(args.output_dir), build_id=build_id)
58 |
59 | if args.upload_dest is not None:
60 | upload_to_pypi_server(testpypi_security_dict['token'], args.output_dir, server='testpypi')
61 |
62 |
63 |
64 | if __name__ == '__main__':
65 | main()
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os, sys
2 | from setuptools import setup, Extension
3 |
4 |
5 | # Version information
6 | def get_version_number():
7 | with open('VERSION', 'r') as f:
8 | for line in f:
9 | return line.split('.')
10 |
11 | return ()
12 |
13 | # Alpha build information
14 | # Builder must create a file in the fpbinary root directory called "ALPHA"
15 | # with the alpha build string in it if they want the build to have a version
16 | # with extra information appended to it.
17 | def get_alpha_str():
18 | if os.path.exists('ALPHA'):
19 | with open('ALPHA', 'r') as f:
20 | for line in f:
21 | return line.strip()
22 |
23 | return None
24 |
25 | version_tuple = get_version_number()
26 |
27 | if len(version_tuple) < 3:
28 | raise SystemError("Couldn't find the module version number!")
29 |
30 | version = '{}.{}.{}'.format(version_tuple[0], version_tuple[1], version_tuple[2])
31 | alpha_str = get_alpha_str()
32 |
33 | if alpha_str is not None:
34 | version += alpha_str
35 |
36 | with open("README.rst", "r") as fh:
37 | long_description = fh.read()
38 |
39 | extra_compile_args = []
40 |
41 | fpbinary_module = Extension('fpbinary',
42 | define_macros=[('MAJOR_VERSION', version_tuple[0]),
43 | ('MINOR_VERSION', version_tuple[1]),
44 | ('MICRO_VERSION', version_tuple[2]),
45 | ('VERSION_STRING', version)],
46 | sources=['src/fpbinarymodule.c',
47 | 'src/fpbinaryglobaldoc.c',
48 | 'src/fpbinarycommon.c',
49 | 'src/fpbinarysmall.c',
50 | 'src/fpbinarylarge.c',
51 | 'src/fpbinaryobject.c',
52 | 'src/fpbinarycomplexobject.c',
53 | 'src/fpbinaryswitchable.c',
54 | 'src/fpbinaryarrayfuncs.c',
55 | 'src/fpbinaryenums.c'],
56 | extra_compile_args=extra_compile_args)
57 |
58 |
59 | setup(name='fpbinary',
60 | version=version,
61 | description='Provides binary fixed point functionality.',
62 | long_description=long_description,
63 | python_requires='>=2.7',
64 |
65 | classifiers=[
66 | 'Operating System :: Microsoft :: Windows',
67 | 'Operating System :: POSIX',
68 | 'Operating System :: MacOS :: MacOS X',
69 | 'Programming Language :: Python :: 2.7',
70 | 'Programming Language :: Python :: 3'],
71 |
72 | url="https://github.com/smlgit/fpbinary",
73 | author_email='smlgit@protonmail.com',
74 | keywords='fixed-point, binary, bit-accurate, dsp, fpga',
75 | license='GPL-2.0 License',
76 |
77 | project_urls={
78 | 'Source': 'https://github.com/smlgit/fpbinary',
79 | 'Documentation': 'https://fpbinary.readthedocs.io/en/latest/',
80 | },
81 |
82 | ext_modules=[fpbinary_module])
83 |
--------------------------------------------------------------------------------
/release/release.py:
--------------------------------------------------------------------------------
1 | import argparse, logging, os, time, subprocess
2 | from lib.appveyor import download_build_artifacts, get_build_from_name, build_is_successful, get_build_summary
3 | from lib.pypi import upload_to_pypi_server
4 | from lib.github import create_light_tag
5 | from lib.common import get_appveyor_security, get_pypi_security, get_github_security, get_version_from_appveyor_build_name
6 |
7 |
8 | default_output_dir = os.path.abspath('download_dir')
9 | pypi_upload_delay_minutes = 15
10 |
11 |
12 | def main():
13 | logging.basicConfig()
14 | logging.root.setLevel(logging.INFO)
15 |
16 | parser = argparse.ArgumentParser(description='Will download build artifacts from Appveyor, '
17 | 'upload to PyPi, run install tests and add tag to GitHub.')
18 | parser.add_argument('buildname', type=str,
19 | help='The build name/version of the Appveyor build to release.')
20 |
21 | args = parser.parse_args()
22 |
23 | version = get_version_from_appveyor_build_name(args.buildname)
24 | if version is None:
25 | raise ValueError('Couldn\'t parse build name {} to a valid version.'.format(args.buildname))
26 |
27 | appveyor_security_dict = get_appveyor_security()
28 | pypi_security_dict = get_pypi_security()
29 | github_security_dict = get_github_security()
30 |
31 | build_dict = get_build_from_name(appveyor_security_dict['token'], appveyor_security_dict['account'],
32 | 'fpbinary', args.buildname)
33 |
34 | if build_is_successful(build_dict) is False:
35 | raise ValueError('The build for version {} was not successful. Let\'s not release it...'.format(
36 | args.buildname
37 | ))
38 |
39 | logging.info('Releasing build:')
40 | logging.info(get_build_summary(build_dict))
41 | logging.info('Version: {}'.format(version))
42 | ans = input('Are you sure you want to continue? (y/n)')
43 |
44 | if ans != 'y':
45 | return
46 |
47 |
48 | # ====================================================
49 | # Download from Appveyor
50 | # ====================================================
51 |
52 | download_dir_abs = os.path.abspath(default_output_dir)
53 | download_build_artifacts(appveyor_security_dict['token'], appveyor_security_dict['account'],
54 | 'fpbinary', download_dir_abs, build_name=args.buildname)
55 |
56 |
57 | # ====================================================
58 | # Upload to PyPi
59 | # ====================================================
60 |
61 | logging.info('Uploading to PyPi...')
62 | upload_to_pypi_server(pypi_security_dict['token'], download_dir_abs, server='pypi')
63 |
64 | logging.info('Sleeping to give PyPi time to get sorted...')
65 | time.sleep(pypi_upload_delay_minutes * 60)
66 |
67 |
68 | # ====================================================
69 | # Run test scripts
70 | # ====================================================
71 |
72 | test_script_abs = os.path.abspath('release/test_all_pypi.sh')
73 | subprocess.run([str(test_script_abs), 'pypi', version], check=True)
74 |
75 |
76 | # ====================================================
77 | # Create tag at Github
78 | # ====================================================
79 |
80 | tag_str = 'v' + version
81 | logging.info('Creating tag {} on commit {}...'.format(tag_str, build_dict['commitId']))
82 | create_light_tag(github_security_dict['token'], 'smlgit', 'fpbinary',
83 | build_dict['commitId'], tag_str)
84 |
85 |
86 | logging.info('Release of version {} is complete.'.format(version))
87 |
88 |
89 | if __name__ == '__main__':
90 | main()
--------------------------------------------------------------------------------
/src/fpbinaryglobaldoc.c:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | /******************************************************************************
6 | *
7 | * Doc strings that can be used by more than one object are defined here.
8 | *
9 | *****************************************************************************/
10 |
11 | #include "fpbinaryglobaldoc.h"
12 |
13 | FP_GLOBAL_Doc_STRVAR(
14 | resize_doc,
15 | "resize(format, overflow_mode=fpbinary.OverflowEnum.wrap, "
16 | "round_mode=fpbinary.RoundingEnum.direct_neg_inf)\n"
17 | "--\n"
18 | "\n"
19 | "Resizes the fixed point object IN PLACE to the format described by "
20 | "format.\n"
21 | "\n"
22 | "Parameters\n"
23 | "----------\n"
24 | "format : length 2 tuple or instance of this object.\n"
25 | " If tuple, will be resized to format[0] int bits and format[1] frac "
26 | "bits.\n"
27 | " If instance of this type, the format will be taken from format.\n"
28 | "\n"
29 | "overflow_mode : fpbinary.OverflowEnum, optional\n"
30 | " Specifies how to deal with overflows when int bits are reduced.\n"
31 | " Default is wrap.\n"
32 | "\n"
33 | "round_mode : fpbinary.RoundingEnum, optional\n"
34 | " Specifies how to deal with rounding when frac bits are reduced.\n"
35 | " Default is direct_neg_inf.\n"
36 | "\n"
37 | "Returns\n"
38 | "----------\n"
39 | "FpBinary/FpBinaryComplex\n"
40 | " `self`\n");
41 |
42 | FP_GLOBAL_Doc_STRVAR(
43 | str_ex_doc,
44 | "str_ex()\n"
45 | "--\n"
46 | "\n"
47 | "Returns a str displaying the full precision value of the fixed point "
48 | "object.\n"
49 | "This is useful when the fixed point value has more bits than native "
50 | "floating \n"
51 | "point types can handle. Note that scientific notation is not used.\n"
52 | "\n"
53 | "Parameters\n"
54 | "----------\n"
55 | "None\n"
56 | "\n"
57 | "Returns\n"
58 | "----------\n"
59 | "str or unicode\n"
60 | " Non-scientific notation string representation of the instance "
61 | "value.\n");
62 |
63 | FP_GLOBAL_Doc_STRVAR(
64 | bits_to_signed_doc,
65 | "bits_to_signed()\n"
66 | "--\n"
67 | "\n"
68 | "Interprets the bits of the fixed point binary object as a 2's complement "
69 | "signed\n"
70 | "integer and returns an int. If `self` is an unsigned object, the MSB, as "
71 | "defined by\n"
72 | "the `int_bits` and `frac_bits` values, will be considered a sign bit.\n"
73 | "\n"
74 | "Parameters\n"
75 | "----------\n"
76 | "None\n"
77 | "\n"
78 | "Returns\n"
79 | "----------\n"
80 | "int\n"
81 | " The object bits interpreted as a 2's complement signed integer.\n");
82 |
83 | FP_GLOBAL_Doc_STRVAR(copy_doc,
84 | "__copy__(self)\n"
85 | "--\n"
86 | "\n"
87 | "Performs a copy of self and returns a new object. If "
88 | "self is a wrapper object, the\n"
89 | "embedded fixed point object will also be copied.\n"
90 | "Returns\n"
91 | "----------\n"
92 | "result : type(self)\n");
93 |
94 | FP_GLOBAL_Doc_STRVAR(format_doc, "tuple : Read-only (int_bits, frac_bits) "
95 | "tuple representing the fixed point "
96 | "format.\n");
97 |
98 | FP_GLOBAL_Doc_STRVAR(
99 | is_signed_doc,
100 | "bool : Read-only. True if the fixed point object is signed.\n");
101 |
--------------------------------------------------------------------------------
/demos/basic_FpBinary_use.py:
--------------------------------------------------------------------------------
1 | from fpbinary import FpBinary, OverflowEnum, RoundingEnum
2 |
3 |
4 | # New fixed point number from float value
5 | FpBinary(int_bits=4, frac_bits=4, signed=True, value=2.5)
6 |
7 |
8 | # New fixed point number from float value, format set by another instance
9 | FpBinary(format_inst=FpBinary(int_bits=4, frac_bits=4, signed=True, value=2.5),
10 | signed=True, value=2.5)
11 |
12 |
13 | # The (int_bits, frac_bits) tuple can be accessed via the format property
14 | FpBinary(4, 6).format
15 |
16 |
17 | # If you are dealing with massive numbers such that the float value doesn't have
18 | # enough precision, you can define your value as a bit field (type int)
19 | fp_massive = FpBinary(int_bits=128, frac_bits=128, signed=True, bit_field=(1 << 255) + 1)
20 |
21 | # The default string rep uses floats, but the str_ex() method outputs a string that
22 | # preserves precision
23 | fp_massive
24 | fp_massive.str_ex()
25 |
26 | # The format can have negative int_bits or negative frac_bits so long as the total
27 | # bits is greater than 0. The meaning of a negative format value is that number of
28 | # bits is removed from the other format part, but the extreme bit position remains
29 | # the same. E.g.:
30 | #
31 | # a format of (-2, 10) gives 8 fractional bits, 0 integer bits and the fractional
32 | # bit positions are 3 to 10 (inclusive).
33 | FpBinary(int_bits=-2, frac_bits=10, signed=False, value=2.0**-10)
34 |
35 | # This should saturate to the maximum representable value for an 8 bit unsigned
36 | # with fractional bit positions 3 to 10:
37 | FpBinary(int_bits=-2, frac_bits=10, signed=False, value=2.0**-1)
38 |
39 | # Similarly, negative frac_bits produces an instance with only integer bit positions:
40 | FpBinary(int_bits=10, frac_bits=-2, signed=False, value=2.0**9)
41 |
42 | # Basic math ops are supported, and overflow is guaranteed to NOT happen
43 | FpBinary(4, 4, value=2.5) + FpBinary(4, 4, value=2.5)
44 | FpBinary(4, 4, value=2.5) - FpBinary(4, 4, value=2.5)
45 | FpBinary(4, 4, value=2.5) * FpBinary(4, 4, value=2.5)
46 | FpBinary(4, 4, value=2.5) / FpBinary(4, 4, value=2.5)
47 |
48 | # Negative int/frac bits instances use the same rules of arithmetic, overflow, rounding
49 | # and resultant format as ordinary formats.
50 | add_res = FpBinary(-3, 8, value=0.03125) + FpBinary(9, -2, value=12.0)
51 | add_res
52 | add_res.format
53 |
54 | mul_res = FpBinary(-3, 8, value=0.03125) * FpBinary(9, -2, value=12.0)
55 | mul_res
56 | mul_res.format
57 |
58 |
59 | # Resizing numbers after operations can be done either by format tuple
60 | # or taking the format from another FpBinary instance
61 | mul_res = FpBinary(4, 4, value=2.5) * FpBinary(4, 4, value=2.5)
62 | mul_res.resize((7, 8), overflow_mode=OverflowEnum.sat, round_mode=RoundingEnum.near_pos_inf)
63 | mul_res.resize(format=FpBinary(2, 4), overflow_mode=OverflowEnum.sat, round_mode=RoundingEnum.near_pos_inf)
64 |
65 |
66 | # FpBinary objects play well with other types of numbers - formats will be inferred from the number's value
67 | FpBinary(4, 4, value=2.5) * 3.5
68 | FpBinary(4, 4, value=2.5) * 3
69 |
70 |
71 | # Unsigned data is also supported
72 | num1 = FpBinary(4, 2, signed=False, value=3.75)
73 | num2 = FpBinary(4, 2, signed=False, value=4.0)
74 | num1 - num2
75 |
76 |
77 | # FpBinary instances can be sliced and indexed (useful for things like NCOs.')
78 | # The result of slicing is another FpBinary number whose value is the bits interpreted as an int.
79 | # bin() is also useful.
80 | num1 = FpBinary(4, 4, value=5.5)
81 | num1, bin(num1), num1, num1[3:1], num1, bin(num1[3:1]), num1, bin(num1[:]), num1, num1[3]
82 |
83 |
84 | # The bits_to_signed() method takes all the bits in a FpBinary and interprets them as a signed integer
85 | num1 = FpBinary(4, 4, value=-3.5)
86 | num1, bin(num1), num1.bits_to_signed()
87 |
88 |
89 | # The __index__ method takes all the bits in a FpBinary and interprets them as an unsigned integer
90 | num1, bin(num1), num1.__index__()
91 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 |
2 | Changelog
3 | =========
4 |
5 | `v1.5.8 /github.com/smlgit/fpbinary/releases/tag/v1.5.8>`_
6 | ----------------------------------------------------------------
7 |
8 | **Bug fixes:**
9 |
10 | * numpy float64 type causes kernel crash in fpBinarySwitchable. `#25 /github.com/smlgit/fpbinary/issues/25>`_
11 | * Error in doc for RoundingEnum? `#26 /github.com/smlgit/fpbinary/issues/26>`_
12 | * Dead kernel when using resize method incorrectly. `#27 /github.com/smlgit/fpbinary/issues/27>`_
13 |
14 | `v1.5.7 /github.com/smlgit/fpbinary/releases/tag/v1.5.7>`_
15 | ----------------------------------------------------------------
16 |
17 | **Bug fixes:**
18 |
19 | * Memory leak in FpBinaryComplex functions. `#23 /github.com/smlgit/fpbinary/issues/23>`_
20 |
21 | `v1.5.6 /github.com/smlgit/fpbinary/releases/tag/v1.5.6>`_
22 | ----------------------------------------------------------------
23 |
24 | **Enhancements:**
25 |
26 | * Add support for create and resize of fixed point objects in lists and arrays. `#22 /github.com/smlgit/fpbinary/issues/22>`_
27 |
28 |
29 | `v1.5.5 /github.com/smlgit/fpbinary/releases/tag/v1.5.5>`_
30 | ----------------------------------------------------------------
31 |
32 | **Enhancements:**
33 |
34 | * Add support for squaring fixed point numbers. `#21 /github.com/smlgit/fpbinary/issues/21>`_
35 | * Add support casting FpBinaryComplex to native complex type. `#20 /github.com/smlgit/fpbinary/issues/20>`_
36 |
37 |
38 | `v1.5.4 /github.com/smlgit/fpbinary/releases/tag/v1.5.4>`_
39 | ----------------------------------------------------------------
40 |
41 | **Enhancements:**
42 |
43 | * Build macOS and Windows binaries for Python 3.10 and 3.11.
44 |
45 | * Note that Win32 binaries for 3.10 and later will not be supported.
46 |
47 | **Bug fixes:**
48 |
49 | * Add support for Numpy. `#9 /github.com/smlgit/fpbinary/issues/9>`_
50 |
51 |
52 | `v1.5.3 /github.com/smlgit/fpbinary/releases/tag/v1.5.3>`_
53 | ----------------------------------------------------------------
54 |
55 | **Enhancements:**
56 |
57 | * Build macOS and Windows binaries for Python 3.9.0.
58 |
59 |
60 | `v1.5.2 /github.com/smlgit/fpbinary/releases/tag/v1.5.2>`_
61 | ----------------------------------------------------------------
62 |
63 | **Enhancements:**
64 |
65 | * Add support for macOS installation binaries. `#19 /github.com/smlgit/fpbinary/issues/19>`_
66 | * Modify the Overflow and Rounding enums to be static classes. `#18 /github.com/smlgit/fpbinary/issues/18>`_
67 |
68 |
69 | `v1.5.1 /github.com/smlgit/fpbinary/releases/tag/v1.5.1>`_
70 | ----------------------------------------------------------------
71 |
72 | **Enhancements:**
73 |
74 | * Add PyPi and readthedocs support. `#16 /github.com/smlgit/fpbinary/issues/16>`_
75 |
76 | **Bug fixes:**
77 |
78 |
79 | * Unpickling an FpBinary instance that was created on a larger word length machine could theoretically produce the wrong value `#15 /github.com/smlgit/fpbinary/issues/15>`_
80 | * FpBinary int() method may return incorrect value when running 32 bit python on a 64 bit machine `#14 /github.com/smlgit/fpbinary/issues/14>`_
81 | * Division on FpBinary objects may cause a crash `#13 /github.com/smlgit/fpbinary/issues/13>`_
82 |
83 | `v1.4 /github.com/smlgit/fpbinary/releases/tag/v1.4>`_
84 | ------------------------------------------------------------
85 |
86 | **Bug fixes:**
87 |
88 |
89 | * Installation on Windows 10. `#12 /github.com/smlgit/fpbinary/issues/12>`_
90 |
91 | `v1.3 /github.com/smlgit/fpbinary/releases/tag/v1.3>`_
92 | ------------------------------------------------------------
93 |
94 | **Enhancements:**
95 |
96 |
97 | * Added support for pickling `#10 /github.com/smlgit/fpbinary/issues/10>`_
98 |
99 | **Bug fixes:**
100 |
101 |
102 | * Copying FpBinarySwitchable objects may result in the wrong min_value or max_value. `#11 /github.com/smlgit/fpbinary/issues/11>`_
103 |
--------------------------------------------------------------------------------
/src/fpbinarymodule.c:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | /******************************************************************************
6 | *
7 | * Packaging up of publicly accessible objects into the fpbinary Python module.
8 | *
9 | *****************************************************************************/
10 |
11 | #include "fpbinaryarrayfuncs.h"
12 | #include "fpbinarycomplexobject.h"
13 | #include "fpbinaryenums.h"
14 | #include "fpbinarylarge.h"
15 | #include "fpbinaryobject.h"
16 | #include "fpbinarysmall.h"
17 | #include "fpbinaryswitchable.h"
18 | #include "fpbinaryversion.h"
19 |
20 | #define FPBINARY_MOD_NAME "fpbinary"
21 | #define FPBINARY_MOD_DOC "Fixed point binary module."
22 |
23 | PyObject *FpBinaryOverflowException;
24 | static PyObject *FpBinaryVersionString;
25 |
26 | static PyMethodDef fpbinarymod_methods[] = {
27 | {
28 | .ml_name = "fpbinary_list_from_array",
29 | .ml_meth = (PyCFunction)FpBinary_FromArray,
30 | .ml_flags = METH_VARARGS | METH_KEYWORDS,
31 | .ml_doc = FpBinary_FromArray_doc,
32 | },
33 |
34 | {
35 | .ml_name = "fpbinarycomplex_list_from_array",
36 | .ml_meth = (PyCFunction)FpBinaryComplex_FromArray,
37 | .ml_flags = METH_VARARGS | METH_KEYWORDS,
38 | .ml_doc = FpBinaryComplex_FromArray_doc,
39 | },
40 |
41 | {
42 | .ml_name = "array_resize",
43 | .ml_meth = (PyCFunction)FpBinary_ArrayResize,
44 | .ml_flags = METH_VARARGS | METH_KEYWORDS,
45 | .ml_doc = FpBinary_ArrayResize_doc,
46 | },
47 |
48 | {NULL}, /* Sentinel */
49 | };
50 |
51 | #if PY_MAJOR_VERSION >= 3
52 | static PyModuleDef fpbinarymoduledef = {
53 | PyModuleDef_HEAD_INIT,
54 | .m_name = FPBINARY_MOD_NAME,
55 | .m_doc = FPBINARY_MOD_DOC,
56 | .m_methods = fpbinarymod_methods,
57 | .m_size = -1,
58 | };
59 |
60 | #define INITERROR return NULL
61 | PyMODINIT_FUNC
62 | PyInit_fpbinary(void)
63 | #else
64 |
65 | #define INITERROR return
66 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
67 | #define PyMODINIT_FUNC void
68 | #endif
69 | PyMODINIT_FUNC
70 | initfpbinary(void)
71 | #endif
72 | {
73 | PyObject *m;
74 |
75 | if (PyType_Ready(&FpBinary_SmallType) < 0)
76 | INITERROR;
77 |
78 | if (PyType_Ready(&FpBinary_LargeType) < 0)
79 | INITERROR;
80 |
81 | if (PyType_Ready(&FpBinary_Type) < 0)
82 | INITERROR;
83 |
84 | if (PyType_Ready(&FpBinaryComplex_Type) < 0)
85 | INITERROR;
86 |
87 | if (PyType_Ready(&FpBinarySwitchable_Type) < 0)
88 | INITERROR;
89 |
90 | if (PyType_Ready(&OverflowEnumType) < 0)
91 | {
92 | INITERROR;
93 | }
94 |
95 | if (PyType_Ready(&RoundingEnumType) < 0)
96 | {
97 | INITERROR;
98 | }
99 |
100 | FpBinaryCommon_InitModule();
101 |
102 | #if PY_MAJOR_VERSION >= 3
103 | m = PyModule_Create(&fpbinarymoduledef);
104 | #else
105 | m = Py_InitModule3("fpbinary", fpbinarymod_methods,
106 | "Fixed point binary module.");
107 | #endif
108 |
109 | Py_INCREF(&FpBinary_SmallType);
110 | PyModule_AddObject(m, "_FpBinarySmall", (PyObject *)&FpBinary_SmallType);
111 |
112 | Py_INCREF(&FpBinary_LargeType);
113 | PyModule_AddObject(m, "_FpBinaryLarge", (PyObject *)&FpBinary_LargeType);
114 | FpBinaryLarge_InitModule();
115 |
116 | Py_INCREF(&FpBinary_Type);
117 | PyModule_AddObject(m, "FpBinary", (PyObject *)&FpBinary_Type);
118 |
119 | Py_INCREF(&FpBinarySwitchable_Type);
120 | PyModule_AddObject(m, "FpBinarySwitchable",
121 | (PyObject *)&FpBinarySwitchable_Type);
122 |
123 | Py_INCREF(&FpBinaryComplex_Type);
124 | PyModule_AddObject(m, "FpBinaryComplex", (PyObject *)&FpBinaryComplex_Type);
125 |
126 | /* Create enum instances */
127 | Py_INCREF(&OverflowEnumType);
128 | PyModule_AddObject(m, "OverflowEnum", (PyObject *)&OverflowEnumType);
129 |
130 | Py_INCREF(&RoundingEnumType);
131 | PyModule_AddObject(m, "RoundingEnum", (PyObject *)&RoundingEnumType);
132 |
133 | fpbinaryenums_InitModule();
134 |
135 | FpBinaryOverflowException =
136 | PyErr_NewException("fpbinary.FpBinaryOverflowException", NULL, NULL);
137 | PyModule_AddObject(m, "FpBinaryOverflowException",
138 | FpBinaryOverflowException);
139 |
140 | /* Doing this to ensure the version string is the default type on
141 | * each version of python.
142 | */
143 | #if PY_MAJOR_VERSION >= 3
144 | FpBinaryVersionString = PyUnicode_FromString(FPBINARY_VERSION_STR);
145 | #else
146 | FpBinaryVersionString = PyString_FromString(FPBINARY_VERSION_STR);
147 | #endif
148 | PyModule_AddObject(m, "__version__", FpBinaryVersionString);
149 |
150 | #if PY_MAJOR_VERSION >= 3
151 | return m;
152 | #endif
153 | }
154 |
--------------------------------------------------------------------------------
/tests/fpbinary_mem_leak_test.py:
--------------------------------------------------------------------------------
1 | import psutil
2 | import time
3 | import gc
4 | import fpbinary
5 | import numpy as np
6 |
7 |
8 | def create_float_obj(quantity=1000000):
9 | l = [0.001 * j for j in range(quantity)]
10 |
11 |
12 | def create_fpbinarycomplex_value_param_obj(quantity=1000000):
13 | l = [fpbinary.FpBinaryComplex(8, 8, value=1.05 + 0.25j) for j in range(quantity)]
14 |
15 |
16 | def create_fpbinarycomplex_obj(quantity=1000000):
17 | l = [fpbinary.FpBinaryComplex(8, 8) for j in range(quantity)]
18 |
19 |
20 | def create_fpbinarycomplex_numpy_obj(quantity=1000000):
21 | ar = np.array([fpbinary.FpBinaryComplex(8, 8) for j in range(quantity)])
22 |
23 |
24 | def create_fpbinaryswitchable_obj_float_mode(quantity=1000000):
25 | l = [fpbinary.FpBinarySwitchable(False, float_value=quantity / 0.3) for j in range(quantity)]
26 |
27 |
28 | def create_fpbinaryswitchable_obj_float_mode_npfloat(quantity=1000000):
29 | l = [fpbinary.FpBinarySwitchable(False, float_value=np.float64(quantity / 0.3)) for j in range(quantity)]
30 |
31 |
32 | def create_fpbinaryswitchable_obj_fp_mode(quantity=1000000):
33 | l = [fpbinary.FpBinarySwitchable(False,
34 | fp_value=fpbinary.FpBinary(8, 8, True, quantity / 0.3),
35 | float_value=quantity / 0.3)
36 | for j in range(quantity)]
37 |
38 |
39 | def resize_fpbinary_obj(quantity=1000000):
40 | l = [fpbinary.FpBinary(8, 8) for j in range(quantity)]
41 | for obj in l:
42 | obj.resize((4, 4))
43 |
44 |
45 | def resize_fpbinarycomplex_obj(quantity=1000000):
46 | l = [fpbinary.FpBinaryComplex(8, 8) for j in range(quantity)]
47 | for obj in l:
48 | obj.resize((4, 4))
49 |
50 |
51 | def basic_arith_fpbinary(quantity):
52 | ops_1 = [fpbinary.FpBinary(8, 8, value=0.1*j) for j in range(1, quantity + 1)]
53 | ops_2 = [fpbinary.FpBinary(8, 8, value=-0.5*j) for j in range(1, quantity + 1)]
54 |
55 | for i in range(len(ops_1)):
56 | add = ops_1[i] + ops_2[i]
57 | sub = ops_1[i] - ops_2[i]
58 | mult = ops_1[i] * ops_2[i]
59 | divide = ops_1[i] / ops_2[i]
60 | neg = -ops_1[i]
61 | ab = abs(ops_1[i])
62 | po = ops_1[i] ** 2
63 | ls = ops_1[i] << 3
64 | rs = ops_1[i] >> 3
65 |
66 |
67 | def basic_arith_fpbinarycomplex(quantity):
68 | ops_1 = [fpbinary.FpBinaryComplex(8, 8, value=0.1*j - 0.1*j*1.0j) for j in range(1, quantity + 1)]
69 | ops_2 = [fpbinary.FpBinaryComplex(8, 8, value=-0.5*j + 0.2*j*1.0j) for j in range(1, quantity + 1)]
70 |
71 | for i in range(len(ops_1)):
72 | add = ops_1[i] + ops_2[i]
73 | sub = ops_1[i] - ops_2[i]
74 | mult = ops_1[i] * ops_2[i]
75 | divide = ops_1[i] / ops_2[i]
76 | neg = -ops_1[i]
77 | ab = abs(ops_1[i])
78 | po = ops_1[i]**2
79 | ls = ops_1[i] << 3
80 | rs = ops_1[i] >> 3
81 |
82 |
83 | def fpbinarycomplex_conjugate(quantity):
84 | ops_1 = [fpbinary.FpBinaryComplex(8, 8, value=0.1*j - 0.1*j*1.0j) for j in range(1, quantity + 1)]
85 |
86 | for i in range(len(ops_1)):
87 | conj = ops_1[i].conjugate()
88 |
89 |
90 | def fpbinary_array_ops(quantity):
91 | float_l = [0.001 * j for j in range(quantity)]
92 |
93 | fpbinary_l = fpbinary.fpbinary_list_from_array(float_l, 8, 8)
94 | fpbinary.array_resize(fpbinary_l, (4, 6))
95 |
96 |
97 | def fpbinarycomplex_array_ops(quantity):
98 | complex_l = [0.001 * j - 0.02j * j for j in range(quantity)]
99 |
100 | fpbinarycomplex_l = fpbinary.fpbinarycomplex_list_from_array(complex_l, 8, 8)
101 | fpbinary.array_resize(fpbinarycomplex_l, (4, 6))
102 |
103 |
104 | funcs = {
105 | 'fpbinarycomplex': create_fpbinarycomplex_obj,
106 | 'fpbinarycomplex_float_param': create_fpbinarycomplex_value_param_obj,
107 | 'fpbinarycomplex_numpy': create_fpbinarycomplex_value_param_obj,
108 | 'fpbinaryswitchable_float_mode': create_fpbinaryswitchable_obj_float_mode,
109 | 'fpbinaryswitchable_float_mode_npfloat': create_fpbinaryswitchable_obj_float_mode_npfloat,
110 | 'fpbinaryswitchable_fp_mode': create_fpbinaryswitchable_obj_fp_mode,
111 | 'fpbinary_resize': resize_fpbinary_obj,
112 | 'fpbinarycomplex_resize': resize_fpbinarycomplex_obj,
113 | 'fpbinary_basic_arith': basic_arith_fpbinary,
114 | 'fpbinarycomplex_basic_arith': basic_arith_fpbinarycomplex,
115 | 'fpbinarycomplex_conjugate': fpbinarycomplex_conjugate,
116 | 'fpbinary_array_ops': fpbinary_array_ops,
117 | 'fpbinarycomplex_array_ops': fpbinarycomplex_array_ops,
118 | }
119 |
120 |
121 | def fpbinary_func_test(create_func_name, iterations=16, objects_per_iteration=1000000):
122 |
123 | proc = psutil.Process()
124 |
125 | start_mem = None
126 |
127 | for i in range(iterations):
128 |
129 | funcs[create_func_name](objects_per_iteration)
130 |
131 | if start_mem is None:
132 | start_mem = proc.memory_info().rss
133 |
134 | gc.collect()
135 | time.sleep(1)
136 | mem_increase = proc.memory_info().rss - start_mem
137 |
138 | if mem_increase > 0.5 * start_mem:
139 | raise MemoryError('Memory increased by {} \%'.format(mem_increase / start_mem * 100))
140 |
141 |
142 | def run_all_leak_funcs(iterations=16, objects_per_iteration=100000):
143 | for func_name in funcs.keys():
144 | print(func_name + '...')
145 | fpbinary_func_test(func_name, iterations, objects_per_iteration)
146 |
147 | print('No memory leaks detected.')
148 |
149 |
150 | def main():
151 | run_all_leak_funcs()
152 |
153 |
154 | if __name__ == '__main__':
155 | main()
156 |
--------------------------------------------------------------------------------
/tests/fpbinary_profiling_test.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import glob
3 | import cProfile
4 | import pstats
5 | from argparse import ArgumentParser
6 | from fpbinary import FpBinary, FpBinarySwitchable, _FpBinarySmall, _FpBinaryLarge, OverflowEnum, RoundingEnum
7 |
8 | NUM_OBJECT_CREATIONS = 100000
9 | NUM_MULTIPLIES = 100000
10 | NUM_ADDS = 100000
11 | NUM_RESIZES = 100000
12 |
13 |
14 | def build_create_params_float(int_bits, frac_bits, val):
15 | return (val,)
16 |
17 |
18 | def build_create_params_fpbinary(int_bits, frac_bits, val):
19 | return (int_bits, frac_bits, True, val)
20 |
21 |
22 | def build_create_params_fpswitchable_fpmode(int_bits, frac_bits, val):
23 | return (True,
24 | FpBinary(int_bits=int_bits, frac_bits=frac_bits, signed=True, value=val),
25 | 0.0)
26 |
27 |
28 | def build_create_params_fpswitchable_non_fpmode(int_bits, frac_bits, val):
29 | return (False,
30 | FpBinary(int_bits=int_bits, frac_bits=frac_bits, signed=True, value=val),
31 | val)
32 |
33 |
34 | def test_create(num_of_creates, class_type, build_param_func, int_bits, frac_bits, value):
35 | params = build_param_func(int_bits, frac_bits, value)
36 | for i in range(0, num_of_creates):
37 | res = class_type(*params)
38 |
39 |
40 | def test_multiply(num_of_multiplies, class_type, build_param_func,
41 | int_bits, frac_bits, val1, val2):
42 | num1 = class_type(*build_param_func(int_bits, frac_bits, val1))
43 | num2 = class_type(*build_param_func(int_bits, frac_bits, val2))
44 |
45 | for i in range(0, num_of_multiplies):
46 | res = num1 * num2
47 |
48 |
49 | def test_add(num_of_adds, class_type, build_param_func,
50 | int_bits, frac_bits, val1, val2):
51 | num1 = class_type(*build_param_func(int_bits, frac_bits, val1))
52 | num2 = class_type(*build_param_func(int_bits, frac_bits, val2))
53 |
54 | for i in range(0, num_of_adds):
55 | res = num1 + num2
56 |
57 |
58 | def test_resize(num_of_resizes, class_type, build_param_func,
59 | start_int_bits, start_frac_bits, resize_int_bits,
60 | resize_frac_bits, start_val, overflow_mode, round_mode):
61 | num1 = class_type(*build_param_func(start_int_bits, start_frac_bits, start_val))
62 |
63 | if hasattr(num1, 'resize') == False:
64 | return
65 |
66 | resize_tup = (resize_int_bits, resize_frac_bits)
67 |
68 | for i in range(0, num_of_resizes):
69 | num1.resize(resize_tup, overflow_mode=overflow_mode, round_mode=round_mode)
70 |
71 |
72 | types_table = {
73 | 'fpbinary': {'class': FpBinary, 'build_params_func': build_create_params_fpbinary},
74 | 'fpbinaryswitchable_fpmode': {'class': FpBinarySwitchable,
75 | 'build_params_func': build_create_params_fpswitchable_fpmode},
76 | 'fpbinaryswitchable_non_fpmode': {'class': FpBinarySwitchable,
77 | 'build_params_func': build_create_params_fpswitchable_non_fpmode},
78 | 'fpbinarysmall': {'class': _FpBinarySmall, 'build_params_func': build_create_params_fpbinary},
79 | 'fpbinarylarge': {'class': _FpBinaryLarge, 'build_params_func': build_create_params_fpbinary},
80 | 'float': {'class': float, 'build_params_func': build_create_params_float},
81 | }
82 |
83 |
84 | def run_type_test(type_name):
85 |
86 | # Create test
87 | test_create(NUM_OBJECT_CREATIONS, types_table[type_name]['class'],
88 | types_table[type_name]['build_params_func'],
89 | int_bits=8, frac_bits=8, value=66.666)
90 |
91 | # Multiply test
92 | test_multiply(NUM_MULTIPLIES, types_table[type_name]['class'],
93 | types_table[type_name]['build_params_func'],
94 | int_bits = 3, frac_bits = 6, val1 = 1.23, val2 = 45.7)
95 |
96 | # Add test
97 | test_add(NUM_ADDS, types_table[type_name]['class'],
98 | types_table[type_name]['build_params_func'],
99 | int_bits=3, frac_bits=6, val1=-3.3445322, val2=0.035)
100 |
101 | # Resize test
102 | test_resize(NUM_RESIZES, types_table[type_name]['class'],
103 | types_table[type_name]['build_params_func'],
104 | start_int_bits=16, start_frac_bits=16, resize_int_bits=8,
105 | resize_frac_bits=8, start_val=-23343.3445322, overflow_mode=OverflowEnum.wrap,
106 | round_mode=RoundingEnum.near_pos_inf)
107 |
108 |
109 | def main(types_list, base_filename=''):
110 | for type_name in types_list:
111 | filename = '{}_{}'.format(base_filename, type_name) if base_filename else None
112 |
113 | cProfile.run('run_type_test(\'{}\')'.format(type_name), filename=filename, sort='tottime')
114 |
115 | if filename:
116 | p = pstats.Stats(filename)
117 | p.strip_dirs().sort_stats('tottime').print_stats()
118 |
119 |
120 | if __name__ == '__main__':
121 | parser = ArgumentParser()
122 | parser.add_argument('-t', '--type', type=str, help='One of {}, or \'all\'.'.format(types_table.keys()))
123 | parser.add_argument('--base-fname', type=str, default=None)
124 | parser.add_argument('--print-data', action='store_true')
125 |
126 | args = parser.parse_args()
127 |
128 | if args.print_data and args.base_fname:
129 | for filename in glob.glob('{}*'.format(args.base_fname)):
130 | p = pstats.Stats(filename)
131 | p.strip_dirs().sort_stats('tottime').print_stats()
132 | elif args.type != '':
133 | if args.type == 'all':
134 | types_list = types_table.keys()
135 | else:
136 | types_list = [args.type]
137 |
138 | main(types_list=types_list, base_filename=args.base_fname)
139 |
--------------------------------------------------------------------------------
/src/fpbinaryenums.c:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | /******************************************************************************
6 | *
7 | * Basic objects to make the use of enumerated values easier pre python3.
8 | * For each enum type, a PyTypeObject is defined which has an int field for
9 | * each possible value. The fields are exposed to python users as get
10 | * properties.
11 | *
12 | *****************************************************************************/
13 |
14 | #include "fpbinaryenums.h"
15 | #include "structmember.h"
16 |
17 | /*
18 | *
19 | * Overflow enumeration class.
20 | *
21 | */
22 |
23 | PyDoc_STRVAR(
24 | fpbinaryoverflow_enum_doc,
25 | "Provides static fields for overflow modes.\n"
26 | "\n"
27 | "Attributes\n"
28 | "----------\n"
29 | "wrap : int\n"
30 | " This is essentially the truncation of any int bits that are being "
31 | "removed (usually via a resize() call). \n"
32 | " For signed types, this mayresult in a positive number becoming "
33 | "negative and vice versa.\n"
34 | "\n"
35 | "sat : int\n"
36 | " If an overflow occurs, the value is railed to the min or max value of "
37 | "the new bit format.\n"
38 | "\n"
39 | "excep : int\n"
40 | " If an overflow occurs, an FpBinaryOverflowException is raised.\n");
41 | PyTypeObject OverflowEnumType = {
42 | PyVarObject_HEAD_INIT(NULL, 0).tp_name = "fpbinary.OverflowEnum",
43 | .tp_doc = fpbinaryoverflow_enum_doc, .tp_itemsize = 0,
44 | .tp_flags = Py_TPFLAGS_DEFAULT,
45 | };
46 |
47 | /*
48 | *
49 | * Rounding enumeration class.
50 | *
51 | */
52 |
53 | PyDoc_STRVAR(
54 | fpbinaryrounding_enum_doc,
55 | "Provides static fields for rounding modes.\n"
56 | "The enums will generally be of the 'direct' or 'near' types. \n"
57 | "'near' implies that a rule is applied if the value is exactly "
58 | "halfway between the representable value. \n"
59 | "'direct' implies that no consideration is given to these halfway "
60 | "situations. \n"
61 | "\n"
62 | "Attributes\n"
63 | "----------\n"
64 | "near_pos_inf : int\n"
65 | " The value is rounded towards the nearest value representable by the "
66 | "new format. \n"
67 | " Ties (i.e. X.5) are rounded towards positive infinity.\n"
68 | " The IEEE 754 standard does not have an equivalent, but this is common "
69 | "in "
70 | "general arithmetic that many call 'rounding up'.\n"
71 | " Examples: *5.5 and 5.6 both go to 6.0 (assuming resizing to zero "
72 | "fract_bits). \n"
73 | " -5.25 goes to -5.0, -5.375 goes to -5.5 (assuming resizing to one "
74 | "fract_bit).* \n"
75 | "\n"
76 | "direct_neg_inf : int\n"
77 | " The value is rounded in the negative direction to the nearest "
78 | " value representable by the new format. \n"
79 | " This is a clean truncate of bits without any other processing. It is "
80 | " often called 'flooring'. \n"
81 | " This is the mode the VHDL fixed point library applies when using the "
82 | "'truncate' mode. \n"
83 | " The IEEE 754 standard calls this 'Round toward -infinity'. \n"
84 | " Examples: *5.5 and 5.6 both go to 5.0 (assuming resizing to zero "
85 | "fract_bits). \n"
86 | " -5.25 and -5.375 both go to -5.5 (assuming resizing to one "
87 | "fract_bit).* \n"
88 | "\n"
89 | "near_zero : int\n"
90 | " The value is rounded towards the nearest value representable by the "
91 | "new format. \n"
92 | " Ties (i.e. X.5) are rounded towards zero. \n"
93 | " The IEEE 754 standard does not have an equivalent.\n"
94 | " Examples: *5.5 goes to 5.0, 5.6 goes to 6.0 (assuming resizing to "
95 | "zero fract_bits).\n"
96 | " -5.25 goes to -5.0, -5.375 goes to -5.5 (assuming resizing to one "
97 | "fract_bit).* \n"
98 | "\n"
99 | "direct_zero : int\n"
100 | " The value is rounded in the direction towards zero to the nearest "
101 | "value representable by the new format. \n"
102 | " The IEEE 754 standard calls this 'Round toward 0' or 'truncation'. \n"
103 | " Examples: *5.5 and 5.6 both go to 5.0 (assuming resizing to zero "
104 | "fract_bits). \n"
105 | " -5.25 and -5.375 both go to -5.0 (assuming resizing to one "
106 | "fract_bit).* \n"
107 | "\n"
108 | "near_even : int\n"
109 | " The value is rounded towards the nearest value representable by the "
110 | "new format. \n"
111 | " Ties (i.e. X.5) are rounded towards the 'even' representation. This "
112 | "means that, after rounding a tie, the lsb is zero. \n"
113 | " The IEEE 754 standard calls this 'Round to nearest, ties to even'. \n"
114 | " This is also the mode the Python 3 round() function applies. \n"
115 | " This is also the mode the VHDL fixed point library applies when using "
116 | "the 'round' mode. \n"
117 | " Examples: *5.5 and 6.5 both go to 6.0 (assuming resizing to zero "
118 | "fract_bits). \n"
119 | " -5.5 and -6.5 both go to -6.0 (assuming resizing to zero fract_bits). "
120 | "\n"
121 | " 5.75 goes to 6.0, 5.25 goes to 5.0 (assuming resizing to one "
122 | "fract_bit).* \n");
123 | PyTypeObject RoundingEnumType = {
124 | PyVarObject_HEAD_INIT(NULL, 0).tp_name = "fpbinary.RoundingEnum",
125 | .tp_doc = fpbinaryrounding_enum_doc, .tp_itemsize = 0,
126 | .tp_flags = Py_TPFLAGS_DEFAULT,
127 | };
128 |
129 | void
130 | fpbinaryenums_InitModule(void)
131 | {
132 | /* Set type object attribute dicts with our fields for both enums */
133 |
134 | PyDict_SetItemString(OverflowEnumType.tp_dict, "wrap",
135 | PyLong_FromLong((long)OVERFLOW_WRAP));
136 | PyDict_SetItemString(OverflowEnumType.tp_dict, "sat",
137 | PyLong_FromLong((long)OVERFLOW_SAT));
138 | PyDict_SetItemString(OverflowEnumType.tp_dict, "excep",
139 | PyLong_FromLong((long)OVERFLOW_EXCEP));
140 |
141 | PyDict_SetItemString(RoundingEnumType.tp_dict, "near_pos_inf",
142 | PyLong_FromLong((long)ROUNDING_NEAR_POS_INF));
143 | PyDict_SetItemString(RoundingEnumType.tp_dict, "direct_neg_inf",
144 | PyLong_FromLong((long)ROUNDING_DIRECT_NEG_INF));
145 | PyDict_SetItemString(RoundingEnumType.tp_dict, "near_zero",
146 | PyLong_FromLong((long)ROUNDING_NEAR_ZERO));
147 | PyDict_SetItemString(RoundingEnumType.tp_dict, "direct_zero",
148 | PyLong_FromLong((long)ROUNDING_DIRECT_ZERO));
149 | PyDict_SetItemString(RoundingEnumType.tp_dict, "near_even",
150 | PyLong_FromLong((long)ROUNDING_NEAR_EVEN));
151 | }
152 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import sys, math, pickle, os, struct
2 | from fpbinary import FpBinary, FpBinarySwitchable, FpBinaryComplex
3 |
4 |
5 | if sys.version_info[0] >= 3:
6 | from tests.porting_v3_funcs import *
7 |
8 |
9 | def get_small_type_size():
10 | """ Returns the number of bits the FpBinarySmall object should be able to support.
11 | This is based on the assumption that FpBinary uses the long long type . """
12 | return 8 * struct.calcsize("q")
13 |
14 | def get_interpreter_arch_size():
15 | """ Returns the number of bits the python environment was compiled for.
16 | Note that this could be 32 on a 64 bit machine if running python in 32 bit. """
17 | return 8 * struct.calcsize("P")
18 |
19 | def get_max_signed_value_bit_field(num_bits):
20 | return (long(1) << (num_bits - 1)) - 1
21 |
22 | def get_min_signed_value_bit_field(num_bits):
23 | return (long(1) << (num_bits - 1))
24 |
25 | def get_max_unsigned_value_bit_field(num_bits):
26 | return (long(1) << num_bits) - 1
27 |
28 | def get_max_signed_value_bit_field_for_arch():
29 | return get_max_signed_value_bit_field(get_small_type_size())
30 |
31 | def get_min_signed_value_bit_field_for_arch():
32 | return get_min_signed_value_bit_field(get_small_type_size())
33 |
34 | def get_max_unsigned_value_bit_field_for_arch():
35 | return get_max_unsigned_value_bit_field(get_small_type_size())
36 |
37 | def convert_float_to_bit_field(value, int_bits, frac_bits):
38 | mant, exp = math.frexp(value)
39 |
40 | # Need to operate on magnitude bits and then do the 2's complement
41 | # to get the appropriate truncation behavior for negative numbers.
42 | value_mag = abs(mant)
43 |
44 | scaled_value = long(value_mag * 2.0 ** (exp + frac_bits))
45 |
46 | # Now convert back to negative representation if needed
47 | if value < 0.0:
48 | scaled_value = (~scaled_value) + 1
49 |
50 | # Make sure only desired bits are used (note that this will convert
51 | # the number back to a positive long integer - its a bit field.
52 | return scaled_value & ((long(1) << (int_bits + frac_bits)) - 1)
53 |
54 |
55 | def set_float_bit_precision(value, int_bits, frac_bits, is_signed):
56 | """
57 | Modifies value to the precision defined by int_bits and frac_bits.
58 |
59 | :param value: input float
60 | :param int_bits: number of integer bits to restrict to
61 | :param frac_bits: number of fractional bits to restrict to
62 | :param is_signed: Determines how many int bits can actually be used for magnitude
63 | :return: float - the input restricted to (int_bits + frac_bits) bits
64 | """
65 |
66 | bit_field = convert_float_to_bit_field(value, int_bits, frac_bits)
67 |
68 | # Convert to an actual negative number if required
69 | if is_signed and value < 0.0:
70 | bit_field -= (long(1) << (int_bits + frac_bits))
71 |
72 | # And convert back to float
73 | return bit_field / 2.0**frac_bits
74 |
75 |
76 | def fp_binary_fields_equal(op1, op2):
77 | """
78 | Returns true if the FpBinary fields of op1 and op2 are equal (including value)
79 | """
80 | return (op1.format == op2.format and op1.is_signed == op2.is_signed and op1 == op2)
81 |
82 | def fp_binary_complex_fields_equal(op1, op2):
83 | """
84 | Returns true if the FpBinaryComplex fields of op1 and op2 are equal (including value)
85 | """
86 | return (op1.format == op2.format and op1 == op2)
87 |
88 | def fp_binary_instances_are_totally_equal(op1, op2):
89 | """
90 | The point of this is to check instances of FpBinary/Switchable/Complex are equal
91 | in value and other properties. If python numeric objects are passed, only value is checked.
92 | Returns True if the properties of the two objects are the same.
93 | """
94 |
95 | if isinstance(op1, FpBinary) or isinstance(op2, FpBinary):
96 | return (isinstance(op1, FpBinary) and isinstance(op2, FpBinary) and op1.format == op2.format and
97 | op1.is_signed == op2.is_signed and op1 == op2)
98 | elif isinstance(op1, FpBinarySwitchable) or isinstance(op2, FpBinarySwitchable):
99 | return (isinstance(op1, FpBinarySwitchable) and isinstance(op2, FpBinarySwitchable) and
100 | op1.fp_mode == op2.fp_mode and op1.min_value == op2.min_value and op1.max_value == op2.max_value and
101 | fp_binary_instances_are_totally_equal(op1.value, op2.value))
102 | elif isinstance(op1, FpBinaryComplex) or isinstance(op2, FpBinaryComplex):
103 | return (isinstance(op1, FpBinaryComplex) and isinstance(op2, FpBinaryComplex) and op1.format == op2.format and
104 | op1.is_signed == op2.is_signed and op1 == op2)
105 |
106 |
107 | return op1 == op2
108 |
109 |
110 | # ================================================================================
111 | # Generating and getting back pickled data from multiple versions.
112 | # This includes FpBinary and FpBinarySwitchable instances
113 | # ================================================================================
114 |
115 | pickle_static_file_prefix = 'pickletest'
116 | pickle_static_file_dir = 'data'
117 | pickle_static_data = [
118 | FpBinary(8, 8, signed=True, value=0.01234),
119 | FpBinary(8, 8, signed=True, value=-3.01234),
120 | FpBinary(8, 8, signed=False, value=0.01234),
121 |
122 | FpBinary(64 - 2, 2, signed=True, value=56.789),
123 | FpBinary(64 - 2, 2, signed=False, value=56.789),
124 |
125 | FpBinarySwitchable(fp_mode=False, fp_value=FpBinary(16, 16, signed=True, value=5.875)),
126 | FpBinarySwitchable(fp_mode=False, float_value=-45.6),
127 |
128 |
129 | # All ones, small size
130 | FpBinary(64 - 2, 2, signed=True, bit_field=(1 << 64) - 1),
131 | FpBinary(64 - 2, 2, signed=False, bit_field=(1 << 64) - 1),
132 |
133 | FpBinary(64 - 2, 3, signed=True, value=56436.25),
134 | FpBinary(64 - 2, 3, signed=False, value=56436.25),
135 |
136 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64 - 2, 2, signed=True)),
137 |
138 | # All ones, large size
139 | FpBinary(64 - 2, 3, signed=True, bit_field=(1 << (64 + 1)) - 1),
140 | FpBinary(64 - 2, 3, signed=False, bit_field=(1 << (64 + 1)) - 1),
141 |
142 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64, 64, signed=False,
143 | bit_field=(1 << (64 * 2)) - 1)),
144 |
145 |
146 | FpBinary(64, 64, signed=True, bit_field=(1 << (64 + 5)) + 23),
147 | FpBinary(64, 64, signed=False, bit_field=(1 << (64 * 2)) - 1),
148 |
149 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(16, 16, signed=True, value=5.875)),
150 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64 - 2, 3, signed=True)),
151 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64, 64, signed=True,
152 | bit_field=(1 << (64 + 5)) + 23)),
153 |
154 | ]
155 |
156 | def gen_static_pickle_files():
157 | """
158 | File name format: pickle_test_v[python_version]_p[pickle protocol]_[os_name]_[word_len].data
159 | """
160 |
161 | this_dir = os.path.dirname(os.path.abspath(__file__))
162 | data_dir = os.path.join(this_dir, pickle_static_file_dir)
163 |
164 | for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1):
165 | fname = '{}_v{}_{}_{}_p{}_{}_{}.data'.format(
166 | pickle_static_file_prefix,
167 | sys.version_info.major, sys.version_info.minor, sys.version_info.micro,
168 | protocol, sys.platform, get_interpreter_arch_size()
169 | )
170 |
171 | with open(os.path.join(data_dir, fname), 'wb') as f:
172 | pickle.dump(pickle_static_data, f, protocol)
173 |
174 | def get_static_pickle_file_paths():
175 | result = []
176 |
177 | this_dir = os.path.dirname(os.path.abspath(__file__))
178 | data_dir = os.path.join(this_dir, pickle_static_file_dir)
179 |
180 | for f in os.listdir(data_dir):
181 | if pickle_static_file_prefix in f:
182 | file_protocol = int(f.split('_')[4].split('.')[0].strip('p'))
183 | if file_protocol <= pickle.HIGHEST_PROTOCOL:
184 | result.append(os.path.join(data_dir, f))
185 |
186 | return result
187 |
188 |
189 | if __name__ == '__main__':
190 | # Generate pickling data
191 | gen_static_pickle_files()
192 | print(get_static_pickle_file_paths())
193 |
194 |
195 |
--------------------------------------------------------------------------------
/release/lib/appveyor.py:
--------------------------------------------------------------------------------
1 | import logging, ntpath, os
2 | import requests
3 | from . import common
4 | import time
5 |
6 |
7 | appveyor_api_url_prefix = 'https://ci.appveyor.com/api/'
8 |
9 |
10 | # Need to allow for uploading/installing from test pypi which seems to require
11 | # a 10 minute delay PER artifact produced. Allow for about 10 artifacts, round
12 | # to 2 hours.
13 | max_time_to_wait_for_build_secs = 7200
14 |
15 |
16 | def _appveyor_rest_api_build_headers(auth_token, customer_headers={},
17 | content_type='json'):
18 | customer_headers['Authorization'] = 'Bearer {}'.format(auth_token)
19 |
20 | if content_type == 'json':
21 | customer_headers['Content-type'] = 'application/json'
22 |
23 | return customer_headers
24 |
25 |
26 | def _appveyor_get_full_url(relative_url):
27 | return common.concat_urls([appveyor_api_url_prefix, relative_url])
28 |
29 |
30 | def _get_projects(auth_token):
31 | r = requests.get(_appveyor_get_full_url('projects'),
32 | headers=_appveyor_rest_api_build_headers(auth_token))
33 | r.raise_for_status()
34 | return r.json()
35 |
36 |
37 | def get_project_id(auth_token, project_name):
38 | js = _get_projects(auth_token)
39 | for project in js:
40 | if project['slug'] == project_name:
41 | return project['projectId']
42 |
43 | return None
44 |
45 |
46 | def get_last_build(auth_token, account_name, project_name, branch=None):
47 | """
48 | Returns the 'build' dict of the last build on branch. If branch is None,
49 | will return the 'build' dict of the last build on the project.
50 | """
51 |
52 | if branch is None:
53 | request_url = 'projects/{}/{}'.format(account_name, project_name)
54 | else:
55 | request_url = 'projects/{}/{}/branch/{}'.format(
56 | account_name, project_name, branch)
57 |
58 | r = requests.get(
59 | _appveyor_get_full_url(request_url),
60 | headers=_appveyor_rest_api_build_headers(auth_token))
61 |
62 | if r.status_code == 404:
63 | # Not found error
64 | # Assume there has been no build for this branch and return None.
65 | return None
66 |
67 | r.raise_for_status()
68 | return r.json()['build']
69 |
70 |
71 | def get_build_from_id(auth_token, account_name, project_name, build_id):
72 | """
73 | Returns the 'build' dict of the build with id build_id.
74 | """
75 |
76 | # Note that this url doesn't seem to be documented. I found it at
77 | # https://help.appveyor.com/discussions/problems/17648-build-api-seems-to-have-changed-to-buildsbuildid
78 | r = requests.get(
79 | _appveyor_get_full_url('projects/{}/{}/builds/{}'.format(
80 | account_name, project_name, build_id)),
81 | headers=_appveyor_rest_api_build_headers(auth_token))
82 | r.raise_for_status()
83 | return r.json()['build']
84 |
85 |
86 | def get_build_from_name(auth_token, account_name, project_name, build_name):
87 | """
88 | Returns the 'build' dict of the build with build_name.
89 | """
90 | r = requests.get(
91 | _appveyor_get_full_url('projects/{}/{}/build/{}'.format(
92 | account_name, project_name, build_name)),
93 | headers=_appveyor_rest_api_build_headers(auth_token))
94 | r.raise_for_status()
95 | return r.json()['build']
96 |
97 |
98 | def set_build_number(auth_token, account_name, project_name, build_number):
99 | """
100 | Sets the next build number to build_number.
101 | """
102 | r = requests.put(
103 | _appveyor_get_full_url('projects/{}/{}/settings/build-number'.format(
104 | account_name, project_name)),
105 | headers=_appveyor_rest_api_build_headers(auth_token),
106 | json={'nextBuildNumber': build_number})
107 | r.raise_for_status()
108 |
109 |
110 | def get_artifact_list(auth_token, job_id):
111 | """
112 | Returns a list of artifacts dicts for job_id.
113 | """
114 | r = requests.get(
115 | _appveyor_get_full_url('buildjobs/{}/artifacts'.format(job_id)),
116 | headers=_appveyor_rest_api_build_headers(auth_token))
117 | r.raise_for_status()
118 | return r.json()
119 |
120 |
121 | def download_job_artifacts(auth_token, job_id, output_dir_path):
122 | artifacts = get_artifact_list(auth_token, job_id)
123 |
124 | for artifact in artifacts:
125 | # Artifact file names can have directory paths in them, including
126 | # windows format, so need to use ntpath instead of os.path
127 | filename = ntpath.basename(artifact['fileName'])
128 |
129 | logging.info('Downloading file {} to {}'.format(
130 | filename, output_dir_path
131 | ))
132 |
133 | r = requests.get(
134 | _appveyor_get_full_url('buildjobs/{}/artifacts/{}'.format(
135 | job_id, artifact['fileName'])),
136 | headers=_appveyor_rest_api_build_headers(auth_token, content_type='content'))
137 | r.raise_for_status()
138 |
139 | with open(os.path.join(output_dir_path, filename), mode='wb') as f:
140 | f.write(r.content)
141 |
142 |
143 | def download_build_artifacts(auth_token, account_name, project_name, output_dir_path,
144 | build_name=None, build_id=None):
145 | """
146 | If neither build_name or build_id is specified, the latest build will be downloaded.
147 | """
148 |
149 | if build_name is not None:
150 | build = get_build_from_name(auth_token, account_name, project_name, build_name)
151 | elif build_id is not None:
152 | build = get_build_from_id(auth_token, account_name, project_name, build_id)
153 | else:
154 | build = get_last_build(auth_token, account_name, project_name)
155 |
156 | if not os.path.exists(output_dir_path):
157 | os.mkdir(output_dir_path)
158 |
159 | # Clear download directory
160 | [os.remove(os.path.join(output_dir_path, p)) for p in os.listdir(output_dir_path)]
161 |
162 | for job in build['jobs']:
163 | download_job_artifacts(auth_token, job['jobId'], output_dir_path)
164 |
165 |
166 | def start_build(auth_token, account_name, project_name, branch,
167 | install_from_testpypi=False,
168 | is_release_build=False, wait_for_finish=False):
169 | """
170 | If successfully started, returns a tuple with (success, build id).
171 | Else, returns None.
172 | """
173 |
174 | logging.info('Starting build...')
175 |
176 | data = {'accountName': account_name, 'projectSlug': project_name, 'branch': branch,
177 | 'environmentVariables': {}}
178 |
179 | if is_release_build is True:
180 | data['environmentVariables']['is_release_build'] = '1'
181 |
182 | if install_from_testpypi is True:
183 | data['environmentVariables']['install_from_pypi'] = '1'
184 |
185 | r = requests.post(
186 | _appveyor_get_full_url('builds'),
187 | headers=_appveyor_rest_api_build_headers(auth_token), json=data)
188 | r.raise_for_status()
189 | build_id = r.json()['buildId']
190 |
191 | accum_secs = 0
192 | sleep_secs = 30
193 |
194 | logging.info('Started build: {}'.format(build_id))
195 |
196 | if wait_for_finish:
197 | logging.info('Waiting for build to finish...')
198 | while accum_secs < max_time_to_wait_for_build_secs:
199 |
200 | time.sleep(sleep_secs)
201 | accum_secs += sleep_secs
202 |
203 | build = get_build_from_id(auth_token, account_name, project_name, build_id)
204 |
205 | if 'finished' in build:
206 | logging.info('Build {} completed with status {}'.format(
207 | build['version'], build['status']))
208 | return build_is_successful(build), build_id
209 |
210 | logging.error('Build completion timed out')
211 |
212 | return build_id
213 |
214 |
215 | def build_is_successful(appveyor_build_dict):
216 | return appveyor_build_dict['status'] == 'success'
217 |
218 |
219 | def get_build_summary(appveyor_build_dict):
220 | return 'Name: {}\nBranch: {}\nCommit: {}\nFinished: {}\nStatus: {}'.format(
221 | appveyor_build_dict['version'], appveyor_build_dict['branch'],
222 | appveyor_build_dict['commitId'], appveyor_build_dict['finished'],
223 | appveyor_build_dict['status']
224 | )
225 |
--------------------------------------------------------------------------------
/src/fpbinarycommon.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | #ifndef FPBINARYCOMMON_H_
6 | #define FPBINARYCOMMON_H_
7 |
8 | #include "Python.h"
9 | #include
10 |
11 | /* defines for compatability between v2 and v3 */
12 | #if PY_MAJOR_VERSION >= 3
13 |
14 | #define nb_nonzero nb_bool
15 | #define nb_long nb_int
16 |
17 | #endif
18 |
19 | #ifndef Py_TPFLAGS_CHECKTYPES
20 | #define Py_TPFLAGS_CHECKTYPES 0
21 | #endif
22 |
23 | #define FP_INT_TYPE long long
24 | #define FP_INT_NUM_BITS (sizeof(FP_INT_TYPE) * 8)
25 | #define FP_INT_ALL_BITS_MASK (~((FP_INT_TYPE)0))
26 |
27 | #define FP_UINT_TYPE unsigned long long
28 | #define FP_UINT_NUM_BITS (sizeof(FP_INT_TYPE) * 8)
29 | #define FP_UINT_MAX_SIGN_BIT (((FP_UINT_TYPE)1) << (FP_UINT_NUM_BITS - 1))
30 | #define FP_UINT_ALL_BITS_MASK (~((FP_UINT_TYPE)0))
31 | #define FP_UINT_MAX_VAL FP_UINT_ALL_BITS_MASK
32 |
33 | /* Not sure if this is already done somewhere - couldn't find it...
34 | * I've chosen to call "abstract" methods directly (rather than using the
35 | * PyObject_CallMethodObjArgs function) for speed. */
36 | #define FP_METHOD(ob, method_name) Py_TYPE(ob)->method_name
37 | #define FP_NUM_METHOD(ob, method_name) Py_TYPE(ob)->tp_as_number->method_name
38 | #define FP_SQ_METHOD(ob, method_name) Py_TYPE(ob)->tp_as_sequence->method_name
39 | #define FP_MP_METHOD(ob, method_name) Py_TYPE(ob)->tp_as_mapping->method_name
40 | #define FP_NUM_METHOD_PRESENT(ob, method_name) \
41 | (ob && (Py_TYPE(ob)->tp_as_number && FP_NUM_METHOD(ob, method_name)))
42 |
43 | #define xstr(s) str(s)
44 | #define str(s) #s
45 |
46 | /*
47 | * Will carry out method on op1 and steal the original reference to op1.
48 | */
49 | #define FP_NUM_UNI_OP_INPLACE(op1, method) \
50 | do \
51 | { \
52 | PyObject *tmp = op1; \
53 | op1 = FP_NUM_METHOD(tmp, method)(tmp); \
54 | Py_XDECREF(tmp); \
55 | } while (0)
56 |
57 | /*
58 | * Will carry out method on op1 and op2 and steal the original reference
59 | * to op1.
60 | */
61 | #define FP_NUM_BIN_OP_INPLACE(op1, op2, method) \
62 | do \
63 | { \
64 | PyObject *tmp = op1; \
65 | op1 = FP_NUM_METHOD(tmp, method)(tmp, op2); \
66 | Py_XDECREF(tmp); \
67 | } while (0)
68 |
69 | #define FP_GLOBAL_Doc_VAR(name) char name[]
70 | #define FP_GLOBAL_Doc_STRVAR(name, str) FP_GLOBAL_Doc_VAR(name) = PyDoc_STR(str)
71 |
72 | /* Packaging up code that changes the point of a PyObject field */
73 | #define FP_ASSIGN_PY_FIELD(obj, value, field) \
74 | do \
75 | { \
76 | PyObject *tmp = obj->field; \
77 | Py_XINCREF(value); \
78 | obj->field = value; \
79 | Py_XDECREF(tmp); \
80 | } while (0)
81 |
82 | #define FP_BASE_PYOBJ(ob) ((PyObject *)ob)
83 | #define PYOBJ_FP_BASE(ob) ((fpbinary_base_t *)ob)
84 | #define FP_BASE_METHOD(ob, method_name) \
85 | PYOBJ_FP_BASE(ob)->private_iface->method_name
86 |
87 | /* Similarly, not sure if this is defined in python 2, so adding my own to make
88 | * porting to 3 easier.
89 | */
90 |
91 | #define FPBINARY_RETURN_NOT_IMPLEMENTED \
92 | return Py_INCREF(Py_NotImplemented), Py_NotImplemented
93 |
94 | typedef enum {
95 | ROUNDING_NEAR_POS_INF = 1,
96 | ROUNDING_DIRECT_NEG_INF = 2,
97 | ROUNDING_NEAR_ZERO = 3,
98 | ROUNDING_DIRECT_ZERO = 4,
99 | ROUNDING_NEAR_EVEN = 5,
100 | } fp_round_mode_t;
101 |
102 | typedef enum {
103 | OVERFLOW_WRAP = 0,
104 | OVERFLOW_SAT = 1,
105 | OVERFLOW_EXCEP = 2,
106 | } fp_overflow_mode_t;
107 |
108 | /* Pseudo polymorphism to speed up calling functions that are in the "methods"
109 | * struct or not exposed to python users at all.
110 | */
111 | typedef struct
112 | {
113 | FP_INT_TYPE (*get_int_bits)(PyObject *);
114 | FP_INT_TYPE (*get_frac_bits)(PyObject *);
115 | FP_UINT_TYPE (*get_total_bits)(PyObject *);
116 | bool (*is_signed)(PyObject *);
117 | PyObject *(*resize)(PyObject *self, PyObject *, PyObject *);
118 | PyObject *(*str_ex)(PyObject *self);
119 | PyObject *(*to_signed)(PyObject *obj, PyObject *args);
120 | PyObject *(*bits_to_signed)(PyObject *, PyObject *);
121 | PyObject *(*copy)(PyObject *, PyObject *);
122 | PyObject *(*fp_getformat)(PyObject *, void *);
123 | PyObject *(*fp_from_double)(double, FP_INT_TYPE, FP_INT_TYPE, bool,
124 | fp_overflow_mode_t, fp_round_mode_t);
125 | PyObject *(*fp_from_bits_pylong)(PyObject *, FP_INT_TYPE, FP_INT_TYPE,
126 | bool);
127 |
128 | PyObject *(*getitem)(PyObject *, PyObject *);
129 |
130 | bool (*build_pickle_dict)(PyObject *self, PyObject *dict);
131 |
132 | } fpbinary_private_iface_t;
133 |
134 | typedef struct
135 | {
136 | PyObject_HEAD fpbinary_private_iface_t *private_iface;
137 | } fpbinary_base_t;
138 |
139 | extern PyObject *FpBinaryOverflowException;
140 | extern PyObject *py_zero;
141 | extern PyObject *py_one;
142 | extern PyObject *py_two;
143 | extern PyObject *py_minus_one;
144 |
145 | /* For pickling base objects */
146 | extern PyObject *fp_small_type_id;
147 | extern PyObject *fp_large_type_id;
148 |
149 | extern PyObject *copy_method_name_str;
150 | extern PyObject *resize_method_name_str;
151 | extern PyObject *get_format_method_name_str;
152 | extern PyObject *get_is_signed_method_name_str;
153 | extern PyObject *str_ex_method_name_str;
154 | extern PyObject *complex_real_property_name_str;
155 | extern PyObject *complex_imag_property_name_str;
156 | extern PyObject *py_default_format_tuple;
157 |
158 | /* Useful, reusable strings.
159 | * Careful using these with concat methods - check if a reference is stolen...
160 | */
161 | extern PyObject *decimal_point_str;
162 | extern PyObject *add_sign_str;
163 | extern PyObject *j_str;
164 | extern PyObject *open_bracket_str;
165 | extern PyObject *close_bracket_str;
166 |
167 | FP_UINT_TYPE fp_uint_lshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts);
168 | FP_UINT_TYPE fp_uint_rshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts);
169 |
170 | void unicode_concat(PyObject **left, PyObject *right);
171 |
172 | /* Required for compatibility between v2 and v3 */
173 | bool FpBinary_IntCheck(PyObject *ob);
174 | PyObject *FpBinary_EnsureIsPyLong(PyObject *ob);
175 | PyObject *FpBinary_TryConvertToPyInt(PyObject *ob);
176 | int FpBinary_TpCompare(PyObject *op1, PyObject *op2);
177 |
178 | void FpBinaryCommon_InitModule(void);
179 |
180 | bool fp_binary_new_params_parse(PyObject *args, PyObject *kwds, long *int_bits,
181 | long *frac_bits, bool *is_signed, double *value,
182 | PyObject **bit_field,
183 | PyObject **format_instance);
184 | bool fp_binary_subscript_get_item_index(PyObject *item, Py_ssize_t *index);
185 | bool fp_binary_subscript_get_item_start_stop(PyObject *item, Py_ssize_t *start,
186 | Py_ssize_t *stop,
187 | Py_ssize_t assumed_length);
188 | PyObject *calc_scaled_val_bits(PyObject *obj, FP_UINT_TYPE frac_bits);
189 | void calc_double_to_fp_params(double input_value, double *scaled_value,
190 | FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits);
191 | void calc_pyint_to_fp_params(PyObject *input_value, PyObject **scaled_value,
192 | FP_INT_TYPE *int_bits);
193 | bool
194 | get_best_int_frac_bits(PyObject *obj, FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits);
195 | PyObject *fp_uint_as_pylong(FP_UINT_TYPE value);
196 | PyObject *fp_int_as_pylong(FP_INT_TYPE value);
197 | FP_UINT_TYPE pylong_as_fp_uint(PyObject *val);
198 | FP_INT_TYPE pylong_as_fp_int(PyObject *val);
199 | void build_scaled_bits_from_pyfloat(PyObject *value, PyObject *frac_bits,
200 | fp_round_mode_t round_mode,
201 | PyObject **output_obj);
202 | bool extract_fp_format_from_tuple(PyObject *format_tuple_param,
203 | PyObject **int_bits, PyObject **frac_bits);
204 | bool extract_fp_format_ints_from_tuple(PyObject *format_tuple_param, FP_INT_TYPE *int_bits,
205 | FP_INT_TYPE *frac_bits);
206 | bool check_new_method_input_types(PyObject *py_is_signed, PyObject *bit_field);
207 | PyObject *scaled_long_to_float_str(PyObject *scaled_value, PyObject *int_bits,
208 | PyObject *frac_bits);
209 | PyObject *
210 | forward_call_with_args(PyObject *obj, PyObject *method_name, PyObject *args,
211 | PyObject *kwds);
212 |
213 | /*
214 | * Macro to check if the PyObject obj is of a type that FpBinary should be able
215 | * to do arithmetic operations with.
216 | */
217 | #define check_supported_builtin_int(obj) (PyLong_Check(obj) || FpBinary_IntCheck(obj))
218 | #define check_supported_builtin_float(obj) (PyFloat_Check(obj))
219 | #define check_supported_builtin(obj) (check_supported_builtin_int(obj) || check_supported_builtin_float(obj))
220 |
221 | #endif /* FPBINARYCOMMON_H_ */
222 |
--------------------------------------------------------------------------------
/release/building_and_releasing.rst:
--------------------------------------------------------------------------------
1 | Building
2 | ========
3 |
4 | fpbinary is simple to build locally using:
5 |
6 | .. code-block:: bash
7 |
8 | python setup.py install
9 |
10 | setuptools picks up the right compiler/linker tools for the local machine and runs them and places the object file in the right place.
11 |
12 | Because Linux systems usually have a compiler pre installed, I only do a source distribution for Linux. It should just work.
13 |
14 | Building for platforms like Windows and macOS is more complicated because a user ideally wouldn't need to find and install a compiler.
15 | This is particularly important for Windows because Visual Studio is the recommeded compiler to use but it is
16 | pretty bloaty and not installed by default. Also, you need the correct version of the VS C++ compiler.
17 |
18 | The chosen solution is to use an online service to build and test the fpbinary library. We are currently using Appveyor.
19 | `This `_ page is very helpful re the compiler versions required for
20 | windows builds. I currently only build for >= Python 3.5 because the required older versions of the VS C++ compiler don't
21 | fully support C99. Specifically, it craps out on use of `` and the dot notation in struct definitions. We
22 | could use the MinGW compiler for older versions, but that is a low priority job.
23 |
24 | Appveyor
25 | --------
26 |
27 | I have an account at ``_ with username *smlgit*. Appveyor builds Linux, Windows and macOS.
28 |
29 | Appveyor has a decent REST API that the python scripts in /release use to start builds and download archive files.
30 |
31 | A Github authorization has been granted to Appveyor for fpbinary for web hook access. This can be revoked at either
32 | Github or Appveyor.
33 |
34 | An Appveyor API token has been generated on the *smlgit->My Profile->API Keys* page for access via the REST API. A new
35 | token can be generated at any time.
36 |
37 | The Appveyor build is controlled via the appveyor.yml file in the repo root. Note that web hooks **are** required for this
38 | to work. The only setting that is really used *outside* the .yml file or REST API is the 'Next build number' setting in
39 | fpbinary settings on the Appveyor website. This number is untouched and is incremented every build automatically.
40 |
41 | Powershell scripting is used in the .yml file whenever flow needs to be controlled (Powershell is the only
42 | scripting available in the .yml for **every** OS. 'cmd:' lines run Windows-only commands.
43 |
44 | The cibuildwheel tool is used for building windows and macOS wheels because it installs the Python runtimes needed for
45 | supported of older macOS versions and it was cleaner to also use it for Windows. Because we only do a source dist for
46 | Linux, it has its own code.
47 |
48 | The run_build.py makes it easy to start a build for a branch and download the resultant files. It can also upload the
49 | files to test.pypi for testing the release. A build can also be started in the web interface, but you'll need to set the
50 | branch in the project settings page. An environment variable also needs to be set if it is a release build, so best to
51 | stick with the script.
52 |
53 | Build names and release numbers
54 | -------------------------------
55 |
56 | It seems that semantic versioning has become big and most python packages seem to use it. So I decided to use
57 | a MAJOR.MINOR.PATCH format for released packages. The patch number is incremented after each release (in the
58 | VERSION file manually). So packages that are released to the public have no build number information in them.
59 |
60 | I want development builds to have build number info in them for easy distinguishing. But note that PEP 440
61 | is rather strict on the formats of public packages, which test.pypi adheres to. I could have used
62 | MAJOR.MINOR.PATCH.BUILD_NUMBER but that makes the final release the oldest version among same patch builds.
63 | The only possible alternative was to use the MAJOR.MINOR.PATCHaBUILD_NUMER 'alpha' format.
64 |
65 | A package version number is set in setup.py in the setup function:
66 |
67 | .. code-block:: python
68 |
69 | setup(name='fpbinary', version=version, ...)
70 |
71 | The build number comes from Appveyor. In order to get this information into setup.py, the build process can
72 | write to a file called 'ALPHA' in the root of fpbinary. The text written will be appended to the MAJOR.MINOR.PATCH
73 | obtained from the 'VERSION' file. See setup.py. Note that this is only done on a development (non-release)
74 | build. On a release build, the `is_release_build` env variable must be created (and set to anything) in Appveyor.
75 | This is easy to do via the REST API. This variable prevents the Appveyor yaml from creating the 'ALPHA' file.
76 |
77 | There is also code in appveyor.yml that sets the build name (or version as Appveyor calls it). I've made all
78 | build names have the build number in it. See the comment in release.lib.common.get_version_from_appveyor_build_name()
79 | for the build name format I use.
80 |
81 | The run_build.py script has an option to upload binaries/source to test.pypi and install from there during
82 | the Appveyor build. Note that while test.pypi.org is useful for testing a new package, it (ridiculously)
83 | prevents you from uploading the same file (by name) twice for a given version (forever - you can't even remove a
84 | release and redo it). But it does recognize the alpha release format, so it isn't a problem unless a
85 | *release* build is uploaded and you need to re-do it. In that case, you would have to increment the PATCH number
86 | if you wanted to upload to the test server with another release build. This applies also to the online pypi.org server
87 | too. So best to do a non-release build with upload to the test server, make sure it works and then do the same with the
88 | release build (without any code changes).
89 |
90 | In order to upload to test.pypi, you need a password token. The Appveyor yaml file has an *encrypted* version of the
91 | test.pypi API token. The mechanism used is called a 'secure variable'. A secure variable can be created in the
92 | *Account->Encrypt YAML* page.
93 |
94 | Test PYPI and PYPI
95 | ------------------
96 |
97 | test.pypi.org is a clone of pypi.org and allows you to test your release file uploads before uploading to the real
98 | pypi.org server. I have an account with username *smlgit*. Access isn't via an API. We just use `twine` to upload to the
99 | server as we would to pypi.org, but with a url option. Both test.pypi.org and pypi.org require an API token that must be
100 | passed in via the `twine -p` option. The tokens are generated on the *Settings* page of the fpbinary project. Note that
101 | test.pypi.org and pypi.org have their own distinct settings, they don't share these credentials.
102 |
103 | Github
104 | ------
105 |
106 | Aside from the usual operations on Github, the only thing we do on Github for release is to generate a tag for a public
107 | release. This is done in the release.py script via the Github API. This needs an access token. I've used a *Personal
108 | Access Token*. These are lightweight tokens that are used for authorization over the https REST API. They are generated
109 | on the *Profile Picture->Settings->Developer Settings->Personal Access Tokens* page.
110 |
111 | Security File
112 | -------------
113 |
114 | For the release/build scripts to get access to the various online services, a file named *security.json* file must be
115 | placed in the release directory with the following structure:
116 |
117 | .. code-block:: python
118 |
119 | {
120 | "APPVEYOR": {"token": , "account": "smlgit"},
121 | "TESTPYPI": {"token": },
122 | "PYPI": {"token": },
123 | "GITHUB": {"token": }
124 | }
125 |
126 | Releasing
127 | =========
128 |
129 | A release comprises the following steps:
130 |
131 | #. Make sure the MAJOR.MINOR.PATCH version is set correctly in the VERSION file
132 | #. Make sure CHANGELOG.rst is updated with the new release enhancements and fixes
133 | #. Do a non-release build, preferably with installation from test.pypi.org, so that tests are run on all possible platforms:
134 |
135 | .. code-block:: bash
136 |
137 | python release/run_build.py --install-from-testpypi
138 |
139 | #. If everything passes, do the same as a release build:
140 |
141 | .. code-block:: bash
142 |
143 | python release/run_build.py --install-from-testpypi --release
144 |
145 | The only difference here is that the version in the packaging info won't have an `a` appendage.
146 |
147 | #. If everything is ok, run the release script:
148 |
149 | .. code-block:: bash
150 |
151 | python release/release.py
152 |
153 | This should download the package files from Appveyor, upload them to pypi.org, run the `test_all_pypi.sh` script
154 | (which just tests that you can install the package in virtualenvs of each pyenv version on the local PC) and finally
155 | creates a release tag on the commit that Appveyor reports the build was done on.
156 |
157 | .. note::
158 |
159 | * This process can be done on any branch but we should be releasing off of the master branch
160 |
161 | Documentation
162 | =============
163 |
164 | readthedocs
165 | -----------
166 |
167 | We have a readthedocs account under the username *smlgit*.
168 |
169 | .rst files are used to add documentation for the library that readthedocs can build to produce
170 | ``_. The .rst files are in the `doc` directory. The html files that will be
171 | produced by readthedocs can be generated locally (after `sphinx` and its `napoleon` and `autodoc` extensions are
172 | installed) by running:
173 |
174 | .. code-block:: bash
175 |
176 | make html
177 |
178 | in the `doc` directory. The resultant html will located in the `_build/html` directory.
179 |
180 | Currently, readthedocs will automatically re-build the docs whenever there is a commit on the master branch. This
181 | requires Github web hook access to readthedocs.
182 |
183 | help() docstrings
184 | -----------------
185 |
186 | The main documentation for fpbinary is written in the source code itself via docstrings. The format follows the numpy
187 | documentation standard (as far as possible) (see ``_ ).
188 |
189 | Not only does this give the user access to the documentation in the interpreter shell, but rst/html files are
190 | generated from the interpreter help via the `sphinx` tool and the `autodoc` extension. This is done by readthedocs to
191 | produce the page ``_.
192 |
193 |
194 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/doc/intro.rst:
--------------------------------------------------------------------------------
1 |
2 | Introduction
3 | ================
4 |
5 | fpbinary is a binary fixed point package for Python. It is written as an extension module for the CPython implementation of Python.
6 |
7 | fpbinary was created with **fast** simulation of math-intensive systems destined for digital hardware (e.g. signal processing) in mind. While Python has great support for signal processing functions, there is no offical fixed point package. Implementaions written completely in Python tend to be frustratingly slow, so fpbinary is an attempt to make fixed point simulation of large, complex hardware systems in Python viable.
8 |
9 |
10 | Features
11 | --------
12 |
13 |
14 | * Arbitrary precision representation of real numbers (including a ``str_ex()`` method for string display of high precision numbers)
15 | * Complex number object
16 | * Definable integer and fractional bit formats
17 | * Fixed point basic math operations
18 | * Bitwise/index/slice operations
19 | * Gracefully plays with int and float Python types
20 | * Switch between fixed and floating point math without changing code
21 | * Tracking of min/max values for prototyping
22 | * Follows the VHDL fixed point library conventions (relatively) closely
23 | * Objects are picklable (only pickle protocols >= 2 are supported)
24 | * The fpbinary objects **ARE NOT** subclassable at present
25 |
26 |
27 | Installation
28 | ------------
29 |
30 | The easiest way is to install using the pip utility. If you don't already have pip installed, first follow the instructions at `pip Installation `_ . Then follow the instructions for your operating system below.
31 |
32 |
33 | Linux
34 | ^^^^^
35 |
36 | pip will install fpbinary on Linux using a source distribution. This does require a C99 compliant compiler on your system, which your distribution is likely to already have. If not, install something like gcc. Then, to install fpbinary:
37 |
38 | .. code-block:: bash
39 |
40 | pip install fpbinary
41 |
42 | If you come across a missing Python.h error, you may need to install the python3-dev package:
43 |
44 | .. code-block:: bash
45 |
46 | sudo apt-get update
47 | sudo apt-get install python3-dev
48 |
49 |
50 | Windows
51 | ^^^^^^^
52 |
53 | fpbinary is tested on Windows 10 and binaries are produced on PyPi. Only python versions >= 3.5 are supported for Windows. Install via:
54 |
55 | .. code-block:: bash
56 |
57 | pip install fpbinary
58 |
59 | macOS
60 | ^^^^^
61 |
62 | fpbinary is currently tested on macOS 10.14 (Mojave) but binaries are produced on PyPi that *should* install on macOS 10.9 and newer. Install via:
63 |
64 | .. code-block:: bash
65 |
66 | pip install fpbinary
67 |
68 | These binaries only support 64 bit systems. If you are running 32 bit or an older OS, the above command will still probably work as long as you have a C99 compliant compiler installed.
69 |
70 | Going Bush
71 | ^^^^^^^^^^
72 |
73 | If you want to test the very latest source code or for some other reason you want to install directly from the repository, you can install via:
74 |
75 | .. code-block:: bash
76 |
77 | pip install git+https://github.com/smlgit/fpbinary.git
78 |
79 | or:
80 |
81 | .. code-block:: bash
82 |
83 | git clone https://github.com/smlgit/fpbinary.git
84 |
85 | cd fpbinary
86 | python setup install
87 |
88 |
89 | Use
90 | ---
91 |
92 | fpbinary provides three main objects - ``FpBinary``, ``FpBinaryComplex`` and ``FpBinarySwitchable``. The best way to learn how they work is to read the help documentation:
93 |
94 | .. code-block:: python
95 |
96 | from fpbinary import FpBinary, FpBinarySwitchable
97 | help(FpBinary)
98 | help(FpBinaryComplex)
99 | help(FpBinarySwitchable)
100 |
101 | This documentation is also avaliable at `Read the Docs `_. There are also some useful `demos `_.
102 |
103 | Below is a very brief introduction to the objects.
104 |
105 | Objects
106 | ^^^^^^^
107 |
108 | ``FpBinary``
109 | ~~~~~~~~~~~~~~~~
110 |
111 | This object represents a real number with a specified number of integer and fractional bits.
112 |
113 | Some basic usage:
114 |
115 | .. code-block:: python
116 |
117 | >>> fp_num = FpBinary(int_bits=4, frac_bits=4, signed=True, value=2.5)
118 | >>> fp_num
119 | 2.5
120 | >>> fp_num.format
121 | (4, 4)
122 | >>> fp_num * 2.0
123 | 5.0
124 | >>> fp_num.resize((1,4))
125 | 0.5
126 |
127 | ``FpBinarySwitchable``
128 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
129 |
130 | This object is intended to be used in simulation code where the user wants to switch between fixed and floating point math operation. It allows a simulation to be coded with fixed point method calls (like resize()) but to be run in floating point mode at the flick of a constructor switch:
131 |
132 | .. code-block:: python
133 |
134 | def dsp_sim(fp_mode):
135 | num1 = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7)
136 | num2 = FpBinary(16, 16, value=0.005)
137 |
138 | num3 = (num1 * num2).resize((8, 8), overflow_mode=OverflowEnum.wrap,
139 | rounding_mode=RoundingEnum.direct_neg_inf)
140 |
141 | # Do other stuff...
142 |
143 | return num3
144 |
145 | ``FpBinarySwitchable`` also provides the ``value`` property. This can be set to fixed or floating point objects (depending on the mode) and the min and max values over the lifetime of the object are tracked. This gives the designer an indication of the required fixed point format of the various data points in their design:
146 |
147 | .. code-block:: python
148 |
149 |
150 | inp = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0)
151 | scaled = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(16, 16, value=0.0), float_value=0.0)
152 |
153 | def some_dsp_next_sample(sample):
154 | inp.value = sample.resize(format_inst=inp)
155 | scaled.value = inp * scale_factor
156 |
157 | # ....
158 | return val
159 |
160 | def run(fp_mode):
161 | # call some_dsp_next_sample a whole heap
162 |
163 | return inp.min_value, inp.max_value, scaled.min_value, scaled.max_value
164 |
165 |
166 | Development
167 | -----------
168 |
169 | fpbinary was designed from the point of view of a frustrated FPGA designer. Speed and useability for FPGA/hardware engineers drove the implementation decisions.
170 |
171 | Architecture
172 | ^^^^^^^^^^^^
173 |
174 | The main objects are ``FpBinary``, ``FpBinaryComplex`` and ``FpBinarySwitchable``.
175 |
176 | ``FpBinary``
177 | ~~~~~~~~~~~~~~~~
178 |
179 | Is a wrapper that is composed of an instance of one of two "base" types:
180 |
181 |
182 | * ``_FpBinarySmall``\ : this object uses native c types for the underlying value representation. This makes operations as fast as possible. However, use of this object is limited by the machine bit width.
183 | * ``_FpBinaryLarge``\ : this object uses Python integer objects (\ ``PyLong``\ ) for the value representation. This allows arbitrary length data at the expense of slower operation (and messier c code...).
184 |
185 | The purpose of ``FpBinary`` is to work out whether the faster object can be used for a representation or operation result and select between the two base types accordingly. It also must make sure the operands of binary/ternary operations are cast to the base type before forwarding them on.
186 |
187 | This architecture does make the code and maintenance more complicated and it is questionable whether it is worth having the small object at all. Basic profiling does suggest that ``FpBinary`` is faster than ``_FpBinaryLarge`` on its own (for numbers < 64 bits), but the difference isn't that big (and is mostly in the creation of objects rather than the math ops).
188 |
189 | ``FpBinaryComplex``
190 | ~~~~~~~~~~~~~~~~~~~~~
191 |
192 | This object holds two FpBinary objects, one for the real part and one for the imaginary part of a fixed point complex number.
193 |
194 | Only signed numbers are supported.
195 |
196 | ``FpBinarySwitchable``
197 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
198 |
199 | The point of this object is to allow a designer to write their simulation code assuming fixed point operation (i.e. with fixed point operations like the ``resize()``\ ) method, but to be able to force floating point math with the flick of a switch. Not only is the normal workflow to try out a design using floating point math first, it is also incredibly handy to be able to switch back and forth through the entire project lifecycle.
200 |
201 | ``FpBinarySwitchable`` is composed of a ``FpBinary`` instance and a native c ``double`` variable. Which variable is actually used when an operation is invoked on the instance is dictated by the ``fp_mode``\ , which is defined at constructor time. The ``FpBinarySwitchable`` code is essentially tasked with casting the other operand to the right type (fixed or float) and then forwarding on the underlying operation to the right object.
202 |
203 | ``FpBinarySwitchable`` also implements a ``value`` property that can be used to set the composition instances. This makes it easy for the designer to write simulation code with apparently mutable data points. The advantage of this is that minimum and maximum values can be tracked during the lifetime of the object - Matlab implements a similar feature for its fixed point variables and it allows the user to get an idea for the required format of each data point. ``FpBinarySwitchable`` implements this functionality with simple logic in the property setter method. Note that this is only done when in floating point mode.
204 |
205 | ``FpBinarySwitchable`` is designed to "look" like an ``FpBinary`` object, at least when it makes sense to flick the operation to float mode. So I have implemented ``resize()`` operations (no change in float mode) and shifting operations (mult/div by powers of 2) as well as the math operations. But index/slice and bitwise operations have **not** been implemented.
206 |
207 | Coding Notes
208 | ^^^^^^^^^^^^
209 |
210 |
211 | * Direct calls to object methods (like the tp_as_number methods) was favoured over the c api PyNumber abstract methods where possible. This was done for speed.
212 | * Similarly, a private interface was created for ``_FpBinarySmall`` and ``_FpBinaryLarge`` to implement so ``FpBinary`` could access them without going through the abstract call functions (that use string methods for lookup). This provided some type of polymorphism via the ``fpbinary_base_t`` type placed at the top of the base's object definitions.
213 |
214 |
215 | Enhancements
216 | ------------
217 |
218 |
219 | * [ ] Possibly jettison the base class architecure and use ``_FpBinaryLarge`` as the main object.
220 | * [ ] Add global contexts that allows the user to define "hardware" specifications so inputs and outputs to math operations can be resized automatically (i.e. without the need for explicit resizing code).
221 | * [ ] Add more advanced operations like log, exp, sin/cos/tan. I have stopped short of doing these thus far because a user may rather simulate the actual hardware implementation (e.g. a lookup table would likely be used for sin). Having said that, a convienient fpbinary method should give the same result.
222 | * [ ] Allow ``FpBinary`` and ``FpBinarySwitchable`` to be subclassable. Would require some basic changes to (mostly) ``FpBinarySwitchable`` to use the abstract methods from the Python Numeric/Sequence interfaces rather than direct accessing via the type memory. Might reduce speed slightly.
223 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # ============================================================================================
2 | # For windows-only code, we use the cmd: prefix - linux/macOS VMs ignore this.
3 | # We also use PowerShell code with the $isLinux flag to do linux-only scripting.
4 | #
5 | # Using cibuildwheel
6 | # ------------------
7 | # Creating macOS binaries is a bit tricky because setuptools/python will use the
8 | # macOS SDK that Python was built with. SDKs are backwards compatible, so the older
9 | # SDK used the more users are catered for with one binary. The problem is that package
10 | # managers like Homebrew have python binaries that are built with the latest SDK present.
11 | # So, e.g., the Python binary from Homebrew on macOS 10.15 will use the 10.15 SDK.
12 | # Appveyor has the latest macOS, and appears to have Python binaries from Homebrew.
13 | # The binaries from Python.org, however, are built with older SDKs so as many OS versions
14 | # as possible are supported. So, we want to build our wheels with the Python.org python installs.
15 | #
16 | # cibuildwheel does this for us. It will install the Python.org binaries and do the wheel build
17 | # and then test. The tool is pretty simple and I think it is safe to rely on it.
18 | #
19 | # cibuildwheel also supports windows builds. It downloads nuget windows python binaries
20 | # and runs the wheel building in vitrualenvs. This is nicer that listing an Appveyor
21 | # job for every windows wheel, so I've decided to use cibuildwheel for windows too.
22 | #
23 | # We aren't using cibuildwheel for Linux because we only do a source distribution for linux
24 | # and cibuildwheel doesn't support NOT building wheels. It also needs a manylinux VM to run.
25 | #
26 | # Hacking the options
27 | # -------------------
28 | # One issue with cibuildwheel is that it insists on installing the wheels it builds locally
29 | # and running the tests. There is no explicit option to specify different install behavior
30 | # (like, e.g., uploading to testpypi and installing from there). So I have abused the
31 | # CIBW_REPAIR_WHEEL_COMMAND, CIBW_BEFORE_TEST and CIBW_TEST_COMMAND options to (when desired)
32 | # upload to testpypi, uninstall the local install and install from pypi all before running
33 | # the tests.
34 |
35 | matrix:
36 | fast_finish: true
37 |
38 | environment:
39 | testpypi_pw:
40 | secure: /Wa2jI4VqSUhZRCL2vxfb9y36bGjzg+oN5Jm5g+/8jxcekWfv3YkEdF51D2+X/gT5m6rDXIYCLJixDBP4sIHsyULuhyGS9MATmakFhPT2fXvaaXiR2Rjpkt36w1m/Hv2Gz7t6xMljCbIREDJGYtNF1GCMm3a5HrtWiUl7zVI+198cLMEjfw0ZjcUBccHYaPf77boJwaxJRzwR3VBFNjbFiLOq5WyxJR8ava2yVsnMAFJUKo8w3BKVr1AMD/0bCKE
41 |
42 | matrix:
43 |
44 |
45 | # ============================================================================================
46 | # Windows builds.
47 | # We use cibuildwheel.
48 | #
49 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
50 | CIBW_BUILD: cp311-win_amd64 cp310-win_amd64
51 | CIBW_BUILD_VERBOSITY: 2
52 | CIBW_VERSION: ">2.11"
53 | WIN_LATEST_PYTHON: "C:\\Python38-x64"
54 | PYPI_DELAY_CMD: "TIMEOUT 420"
55 |
56 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
57 | CIBW_BUILD: cp39-win32 cp39-win_amd64 cp38-win32 cp38-win_amd64
58 | CIBW_BUILD_VERBOSITY: 2
59 | CIBW_VERSION: ">2.11"
60 | WIN_LATEST_PYTHON: "C:\\Python38-x64"
61 | PYPI_DELAY_CMD: "TIMEOUT 420"
62 |
63 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
64 | CIBW_BUILD: cp37-win32 cp37-win_amd64 cp36-win32 cp36-win_amd64
65 | CIBW_BUILD_VERBOSITY: 2
66 | CIBW_VERSION: ">2.11"
67 | WIN_LATEST_PYTHON: "C:\\Python38-x64"
68 | PYPI_DELAY_CMD: "TIMEOUT 420"
69 |
70 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
71 | CIBW_BUILD: cp35-win32 cp35-win_amd64
72 | CIBW_BUILD_VERBOSITY: 2
73 | CIBW_VERSION: "==1.12.0"
74 | WIN_LATEST_PYTHON: "C:\\Python38-x64"
75 | PYPI_DELAY_CMD: "TIMEOUT 420"
76 |
77 | # ============================================================================================
78 | # MacOS builds.
79 | #
80 | # We use cibuildwheel.
81 | #
82 | - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina
83 | CIBW_BUILD: cp311-macosx_x86_64 cp310-macosx_x86_64
84 | CIBW_BUILD_VERBOSITY: 2
85 | CIBW_VERSION: ">2.11"
86 | PYPI_DELAY_CMD: "SLEEP 420"
87 |
88 | - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina
89 | CIBW_BUILD: cp39-macosx_x86_64 cp38-macosx_x86_64 cp37-macosx_x86_64 cp36-macosx_x86_64
90 | CIBW_BUILD_VERBOSITY: 2
91 | CIBW_VERSION: ">2.11"
92 | PYPI_DELAY_CMD: "SLEEP 420"
93 |
94 | - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina
95 | CIBW_BUILD: cp27-macosx_x86_64 cp35-macosx_x86_64
96 | CIBW_BUILD_VERBOSITY: 2
97 | CIBW_VERSION: "==1.12.0"
98 | PYPI_DELAY_CMD: "SLEEP 420"
99 |
100 |
101 | # ============================================================================================
102 | # Linux builds.
103 | #
104 | # We use the pre-installed python virtual environments to do all our Linux building/testing
105 | # in one job.
106 | - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
107 | LINUX_VENVS: venv2.7,venv3.5,venv3.6,venv3.7,venv3.8,venv3.9,venv3.10,venv3.11
108 |
109 |
110 | # Need to make sure initial setup is done with latest python (especially for cibuildwheel)
111 | stack: python 3.9
112 |
113 |
114 | install:
115 |
116 | # Set the build name and alpha release number
117 | - ps: |
118 |
119 | $base_version = Get-Content .\VERSION
120 | $base_version | Select-Object -First 1
121 |
122 | if (Test-Path env:is_release_build)
123 | {
124 | Update-AppveyorBuild -Version "$env:APPVEYOR_REPO_BRANCH-$($base_version)rc$env:APPVEYOR_BUILD_NUMBER"
125 | }
126 | else
127 | {
128 | Update-AppveyorBuild -Version "$env:APPVEYOR_REPO_BRANCH-$($base_version)a$env:APPVEYOR_BUILD_NUMBER"
129 |
130 | <#
131 | If the package files need extra information in the version field,
132 | a file called ALPHA with the string to append must be written to
133 | the root of the project. This will occur unless the environment
134 | variable is_release_build is set.
135 | #>
136 | Set-Content -Path .\ALPHA -Value "a$env:APPVEYOR_BUILD_NUMBER"
137 | }
138 |
139 |
140 | # Need to make sure initial setup is done with latest python (especially for cibuildwheel)
141 | # This cmd is windows-specific.
142 | - cmd: "SET PATH=%WIN_LATEST_PYTHON%;%WIN_LATEST_PYTHON%\\Scripts;%PATH%"
143 |
144 | # Check that we have the expected version for Python
145 | - "python --version"
146 |
147 | # Upgrade to the latest version of pip to avoid it displaying warnings
148 | # about it being out of date on windows.
149 | - cmd: "python -m pip install --upgrade pip"
150 |
151 | - ps: |
152 |
153 | <#
154 |
155 | Set up cibuildwheel environment variables.
156 | If we are installing locally, this just requires setting the test script.
157 | If we want to install from testpypi, we need to store the built wheel in our own dir,
158 | make sure the wheel is present in the "repaired wheel" dir (because when we set our
159 | own CIBW_REPAIR_WHEEL_COMMAND, cibuildwheel still expects this file to exist but doesn't
160 | do it itself), upload to testpypi, then uninstall the local version that cibuildwheel
161 | installs just before the test command is run, install from testpypi and then finally
162 | run our actual tests.
163 |
164 | Note that the {wheel} file path placeholder isn't accessible at all parts of the flow,
165 | so that is why CIBW_REPAIR_WHEEL_COMMAND is being used.
166 |
167 | #>
168 | if (-Not $isLinux)
169 | {
170 | if (Test-Path env:install_from_pypi)
171 | {
172 | $install_version = python setup.py --version
173 | Set-Item -Path Env:CIBW_REPAIR_WHEEL_COMMAND -Value ("rm -rf upload_dir && mkdir upload_dir && cp {wheel} upload_dir && cp {wheel} {dest_dir}")
174 | Set-Item -Path Env:CIBW_BEFORE_TEST -Value ("pip install --upgrade twine && twine upload --repository testpypi -u __token__ -p " + $env:testpypi_pw + " upload_dir/* && " + $env:PYPI_DELAY_CMD)
175 | Set-Item -Path Env:CIBW_TEST_COMMAND -Value ("pip uninstall -y fpbinary && pip install -I --pre --index-url https://test.pypi.org/simple/ --no-deps fpbinary==" + $install_version + " && cd {project} && pip install numpy && pip install --prefer-binary scipy && python -m unittest discover -s tests -p testFpBinary*")
176 | }
177 | else
178 | {
179 | Set-Item -Path Env:CIBW_TEST_COMMAND -Value ("cd {project} && pip install numpy && pip install --prefer-binary scipy && python -m unittest discover -s tests -p testFpBinary*")
180 | }
181 |
182 | python -m pip install cibuildwheel$env:CIBW_VERSION
183 | }
184 |
185 |
186 | build_script:
187 |
188 | # ============================================================================================
189 | # Linux builds.
190 | #
191 | # We only do a source distribution for Linux, so we use the latest python version virtual env
192 | # to build the source dist.
193 | #
194 | # macOS builds.
195 | # Just run cibuildwheel - it gets everything from the env variables previously set.
196 | #
197 | - ps: |
198 | if ($isLinux)
199 | {
200 | python -m pip install --upgrade wheel
201 | python setup.py sdist --formats=zip
202 |
203 | <# Linux doesn't use cibuildwheel, so need to do the wheel upload here. #>
204 | if (Test-Path env:install_from_pypi)
205 | {
206 | python -m pip install --upgrade twine
207 | python -m twine upload --repository testpypi -u __token__ -p "$env:testpypi_pw" dist/*
208 | Start-Sleep -s 420
209 | }
210 | }
211 | elseif (-Not $isWindows)
212 | {
213 | python -m cibuildwheel --output-dir dist
214 | }
215 |
216 | # ============================================================================================
217 | # Windoes builds.
218 | # Also uses cibuildwheel to do everything, but we can't run the wheel build in PowerShell
219 | # because it errors out on compiler flag warnings (https://github.com/pypa/pip/issues/3383).
220 | # Windows cmd doesn't error out on the warnings, so using cmd for windows.
221 | - cmd: python -m cibuildwheel --output-dir dist
222 |
223 |
224 | # ============================================================================================
225 | # All builds.
226 | #
227 | # Upload files to test pypi if requested.
228 | - ps: |
229 |
230 |
231 |
232 | test_script:
233 |
234 | # ============================================================================================
235 | # Windows and macOS builds.
236 | #
237 | # Tests are specified in env variables for cibuildwheel to do the tests as part of the build
238 | # process.
239 |
240 |
241 | # ============================================================================================
242 | # Linux builds.
243 | #
244 | # For each python version, we use pip to install the source distribution in the dist directory.
245 | - ps: |
246 | if ($isLinux)
247 | {
248 | $ar = $env:LINUX_VENVS.Split(",")
249 | Foreach ($e in $ar)
250 | {
251 | . $HOME/$e/bin/activate.ps1
252 |
253 | pip install numpy
254 | pip install --prefer-binary scipy
255 |
256 | if (Test-Path env:install_from_pypi)
257 | {
258 | $install_version = python setup.py --version
259 | pip install -I --pre --index-url https://test.pypi.org/simple/ --no-deps fpbinary=="$install_version"
260 | }
261 | else
262 | {
263 | pip install fpbinary --no-index --find-links dist/
264 | }
265 |
266 | python -m unittest discover -s tests -p testFpBinary*
267 | }
268 | }
269 |
270 |
271 | artifacts:
272 | - path: dist\*
273 |
274 |
--------------------------------------------------------------------------------
/src/fpbinaryarrayfuncs.c:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | /******************************************************************************
6 | *
7 | * Functions to create or modify lists or arrays of fixed point objects.
8 | *
9 | *****************************************************************************/
10 |
11 | #include "fpbinaryarrayfuncs.h"
12 | #include "fpbinarycomplexobject.h"
13 | #include "fpbinaryobject.h"
14 |
15 | /*
16 | * Assumes kwds is NOT NULL.
17 | */
18 | static PyObject *
19 | fpbinary_from_array_nested(PyObject *array, PyObject *fpbinary_args,
20 | PyObject *kwds)
21 | {
22 | Py_ssize_t len = PySequence_Length(array);
23 | PyObject *result_list = NULL;
24 |
25 | if (len < 0)
26 | {
27 | PyErr_SetString(PyExc_TypeError, "Could not determine array length "
28 | "when creating FpBinary from array.");
29 | return NULL;
30 | }
31 |
32 | result_list = PyList_New(len);
33 |
34 | for (int i = 0; i < len; i++)
35 | {
36 | PyObject *cur_item = PySequence_GetItem(array, i);
37 |
38 | if (PySequence_Check(cur_item))
39 | {
40 | PyObject *nested_list =
41 | fpbinary_from_array_nested(cur_item, fpbinary_args, kwds);
42 | if (nested_list)
43 | {
44 | PyList_SET_ITEM(result_list, i, nested_list);
45 | }
46 | else
47 | {
48 | return NULL;
49 | }
50 | }
51 | else
52 | {
53 | PyObject *new_fp_object = NULL;
54 |
55 | /* Set the fpbinary keyword argument "value" to the current array
56 | * item */
57 | PyDict_SetItemString(kwds, "value", cur_item);
58 |
59 | new_fp_object =
60 | PyObject_Call((PyObject *)&FpBinary_Type, fpbinary_args, kwds);
61 |
62 | if (new_fp_object)
63 | {
64 | PyList_SET_ITEM(result_list, i, new_fp_object);
65 | }
66 | else
67 | {
68 | return NULL;
69 | }
70 | }
71 |
72 | Py_DECREF(cur_item);
73 | }
74 |
75 | return result_list;
76 | }
77 |
78 | /*
79 | * Assumes kwds is NOT NULL.
80 | */
81 | static PyObject *
82 | fpbinarycomplex_from_array_nested(PyObject *array,
83 | PyObject *fpbinarycomplex_args,
84 | PyObject *kwds)
85 | {
86 | Py_ssize_t len = PySequence_Length(array);
87 | PyObject *result_list = NULL;
88 |
89 | if (len < 0)
90 | {
91 | PyErr_SetString(PyExc_TypeError, "Could not determine array length "
92 | "when creating FpBinary from array.");
93 | return NULL;
94 | }
95 |
96 | result_list = PyList_New(len);
97 |
98 | for (int i = 0; i < len; i++)
99 | {
100 | PyObject *cur_item = PySequence_GetItem(array, i);
101 |
102 | if (PySequence_Check(cur_item))
103 | {
104 | PyObject *nested_list = fpbinarycomplex_from_array_nested(
105 | cur_item, fpbinarycomplex_args, kwds);
106 | if (nested_list)
107 | {
108 | PyList_SET_ITEM(result_list, i, nested_list);
109 | }
110 | else
111 | {
112 | return NULL;
113 | }
114 | }
115 | else
116 | {
117 | PyObject *new_fp_object = NULL;
118 |
119 | /* Set the fpbinary keyword argument "value" to the current array
120 | * item */
121 | PyDict_SetItemString(kwds, "value", cur_item);
122 |
123 | new_fp_object = PyObject_Call((PyObject *)&FpBinaryComplex_Type,
124 | fpbinarycomplex_args, kwds);
125 |
126 | if (new_fp_object)
127 | {
128 | PyList_SET_ITEM(result_list, i, new_fp_object);
129 | }
130 | else
131 | {
132 | return NULL;
133 | }
134 | }
135 |
136 | Py_DECREF(cur_item);
137 | }
138 |
139 | return result_list;
140 | }
141 |
142 | static bool
143 | fpbinary_array_resize_nested(PyObject *array, PyObject *fpbinary_args,
144 | PyObject *kwds)
145 | {
146 | Py_ssize_t len = PySequence_Length(array);
147 | bool result = true;
148 |
149 | if (len < 0)
150 | {
151 | PyErr_SetString(PyExc_TypeError, "Could not determine array length "
152 | "when resizing FpBinary in array.");
153 | return false;
154 | }
155 |
156 | for (int i = 0; i < len; i++)
157 | {
158 | PyObject *cur_item = PySequence_GetItem(array, i);
159 |
160 | if (PySequence_Check(cur_item))
161 | {
162 | result =
163 | fpbinary_array_resize_nested(cur_item, fpbinary_args, kwds);
164 | }
165 | else
166 | {
167 | PyObject *resized_obj = forward_call_with_args(
168 | cur_item, resize_method_name_str, fpbinary_args, kwds);
169 | ;
170 | result = (resized_obj != NULL);
171 |
172 | if (result)
173 | {
174 | Py_DECREF(resized_obj);
175 | }
176 | }
177 |
178 | Py_DECREF(cur_item);
179 | }
180 |
181 | return result;
182 | }
183 |
184 | /*
185 | * First argument must be an object that implements the __getitem__ method.
186 | * The rest of the arguments:
187 | * int_bits, frac_bits, signed, format_inst.
188 | *
189 | * We use the underlying FpBinary constructor format, so if format_inst is used,
190 | * it needs to be used as a keyword arg.
191 | *
192 | * Returns a list of FpBinary objects.
193 | */
194 |
195 | FP_GLOBAL_Doc_STRVAR(
196 | FpBinary_FromArray_doc,
197 | "fpbinary_list_from_array(array, int_bits=1, frac_bits=0, signed=True, "
198 | "format_inst=None)\n"
199 | "--\n"
200 | "\n"
201 | "Converts the elements of array to a list of FpBinary objects using the "
202 | "format "
203 | "specified by int_bits/frac_bits or format_inst.\n"
204 | "If format_inst is used, it must be specified by keyword.\n"
205 | "\n"
206 | "Parameters\n"
207 | "----------\n"
208 | "array : Any object that implements __getitem__.\n"
209 | "\n"
210 | "int_bits, frac_bits, signed, format_inst : As per FpBinary constructor\n"
211 | "\n"
212 | "Returns\n"
213 | "----------\n"
214 | "list\n"
215 | " Elements are FpBinary objects. Dimension of input array is "
216 | "maintained.\n");
217 |
218 | PyObject *
219 | FpBinary_FromArray(PyObject *self, PyObject *args, PyObject *kwds)
220 | {
221 | Py_ssize_t args_len;
222 | PyObject *fpbinary_args = NULL;
223 | PyObject *temp_kwds = NULL;
224 | PyObject *result = NULL;
225 |
226 | if (!PyTuple_Check(args))
227 | {
228 | PyErr_SetString(
229 | PyExc_ValueError,
230 | "Unexpected parameter list when creating FpBinary from array.");
231 | return NULL;
232 | }
233 |
234 | args_len = PyTuple_Size(args);
235 | if (args_len < 1)
236 | {
237 | PyErr_SetString(PyExc_TypeError, "An array or list must be specified "
238 | "when creating FpBinary from array.");
239 | return NULL;
240 | }
241 |
242 | if (!PySequence_Check(PyTuple_GET_ITEM(args, 0)))
243 | {
244 | PyErr_SetString(PyExc_TypeError, "First argument must be an array or "
245 | "list when creating FpBinary from "
246 | "array.");
247 | return NULL;
248 | }
249 |
250 | if (args_len > 4)
251 | {
252 | PyErr_SetString(PyExc_ValueError,
253 | "The only positional arguments allowed when when "
254 | "creating FpBinary from array"
255 | " are array, int_bits, frac_bits and signed.");
256 | return NULL;
257 | }
258 |
259 | fpbinary_args = PyTuple_GetSlice(args, 1, args_len);
260 |
261 | if (!kwds)
262 | {
263 | temp_kwds = PyDict_New();
264 | result = fpbinary_from_array_nested(PyTuple_GET_ITEM(args, 0),
265 | fpbinary_args, temp_kwds);
266 | Py_DECREF(temp_kwds);
267 | }
268 | else
269 | {
270 | result = fpbinary_from_array_nested(PyTuple_GET_ITEM(args, 0),
271 | fpbinary_args, kwds);
272 | }
273 |
274 | Py_DECREF(fpbinary_args);
275 |
276 | return result;
277 | }
278 |
279 | /*
280 | * First argument must be an object that implements the __getitem__ method.
281 | * The rest of the arguments:
282 | * int_bits, frac_bits, format_inst.
283 | *
284 | * We use the underlying FpBinaryComplex constructor format, so if format_inst
285 | * is used,
286 | * it needs to be used as a keyword arg.
287 | *
288 | * Returns a list of FpBinaryComplex objects.
289 | */
290 |
291 | FP_GLOBAL_Doc_STRVAR(
292 | FpBinaryComplex_FromArray_doc,
293 | "fpbinarycomplex_list_from_array(array, int_bits=1, frac_bits=0, "
294 | "format_inst=None)\n"
295 | "--\n"
296 | "\n"
297 | "Converts the elements of array to a list of FpBinaryComplex objects using "
298 | "the format "
299 | "specified by int_bits/frac_bits or format_inst.\n"
300 | "If format_inst is used, it must be specified by keyword.\n"
301 | "\n"
302 | "Parameters\n"
303 | "----------\n"
304 | "array : Any object that implements __getitem__.\n"
305 | "\n"
306 | "int_bits, frac_bits, format_inst : As per FpBinaryComplex constructor\n"
307 | "\n"
308 | "Returns\n"
309 | "----------\n"
310 | "list\n"
311 | " Elements are FpBinaryComplex objects. Dimension of input array is "
312 | "maintained.\n");
313 |
314 | PyObject *
315 | FpBinaryComplex_FromArray(PyObject *self, PyObject *args, PyObject *kwds)
316 | {
317 | Py_ssize_t args_len;
318 | PyObject *fpbinarycomplex_args = NULL;
319 | PyObject *temp_kwds = NULL;
320 | PyObject *result = NULL;
321 |
322 | if (!PyTuple_Check(args))
323 | {
324 | PyErr_SetString(PyExc_ValueError, "Unexpected parameter list when "
325 | "creating FpBinaryComplex from "
326 | "array.");
327 | return NULL;
328 | }
329 |
330 | args_len = PyTuple_Size(args);
331 | if (args_len < 1)
332 | {
333 | PyErr_SetString(PyExc_TypeError, "An array or list must be specified "
334 | "when creating FpBinaryComplex from "
335 | "array.");
336 | return NULL;
337 | }
338 |
339 | if (!PySequence_Check(PyTuple_GET_ITEM(args, 0)))
340 | {
341 | PyErr_SetString(PyExc_TypeError, "First argument must be an array or "
342 | "list when creating FpBinaryComplex "
343 | "from array.");
344 | return NULL;
345 | }
346 |
347 | if (args_len > 3)
348 | {
349 | PyErr_SetString(PyExc_ValueError,
350 | "The only positional arguments allowed when when "
351 | "creating FpBinaryComplex from array"
352 | " are array, int_bits and frac_bits.");
353 | return NULL;
354 | }
355 |
356 | fpbinarycomplex_args = PyTuple_GetSlice(args, 1, args_len);
357 |
358 | if (!kwds)
359 | {
360 | temp_kwds = PyDict_New();
361 | result = fpbinarycomplex_from_array_nested(
362 | PyTuple_GET_ITEM(args, 0), fpbinarycomplex_args, temp_kwds);
363 | Py_DECREF(temp_kwds);
364 | }
365 | else
366 | {
367 | result = fpbinarycomplex_from_array_nested(PyTuple_GET_ITEM(args, 0),
368 | fpbinarycomplex_args, kwds);
369 | }
370 |
371 | Py_DECREF(fpbinarycomplex_args);
372 |
373 | return result;
374 | }
375 |
376 | /*
377 | * First argument must be an object that implements the __getitem__ method.
378 | * The rest of the arguments:
379 | * format, overflow_mode, round_mode.
380 | */
381 |
382 | FP_GLOBAL_Doc_STRVAR(
383 | FpBinary_ArrayResize_doc,
384 | "array_resize(array, format, overflow_mode=0, round_mode=2)\n"
385 | "--\n"
386 | "\n"
387 | "Resizes the fixed point objects in array IN PLACE to the format described "
388 | "by format.\n"
389 | "See the documentation for FpBinary.resize for more information.\n"
390 | "\n"
391 | "Parameters\n"
392 | "----------\n"
393 | "array : Any object that implements __getitem__. Elements must be nested "
394 | "arrays or FpBinary or FpBinaryComplex objects.\n"
395 | "\n"
396 | "array, format, overflow_mode=0, round_mode : As per FpBinary.resize "
397 | "method.\n"
398 | "\n"
399 | "Returns\n"
400 | "----------\n"
401 | "None\n");
402 |
403 | PyObject *
404 | FpBinary_ArrayResize(PyObject *self, PyObject *args, PyObject *kwds)
405 | {
406 | Py_ssize_t args_len;
407 | PyObject *fpbinary_args = NULL;
408 |
409 | if (!PyTuple_Check(args))
410 | {
411 | PyErr_SetString(
412 | PyExc_ValueError,
413 | "Unexpected parameter list when resizing array elements.");
414 | return NULL;
415 | }
416 |
417 | args_len = PyTuple_Size(args);
418 | if (args_len < 1)
419 | {
420 | PyErr_SetString(
421 | PyExc_TypeError,
422 | "An array or list must be specified when resizing array elements.");
423 | return NULL;
424 | }
425 |
426 | if (!PySequence_Check(PyTuple_GET_ITEM(args, 0)))
427 | {
428 | PyErr_SetString(PyExc_TypeError, "First argument must be an array or "
429 | "list when resizing array elements.");
430 | return NULL;
431 | }
432 |
433 | fpbinary_args = PyTuple_GetSlice(args, 1, args_len);
434 | if (fpbinary_array_resize_nested(PyTuple_GET_ITEM(args, 0), fpbinary_args,
435 | kwds))
436 | {
437 | Py_DECREF(fpbinary_args);
438 | Py_RETURN_NONE;
439 | }
440 | else
441 | {
442 | return NULL;
443 | }
444 | }
445 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/src/fpbinarycommon.c:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Licensed under GNU General Public License 2.0 - see LICENSE
3 | *****************************************************************************/
4 |
5 | /******************************************************************************
6 | *
7 | * Useful functions available to all fpbinary source.
8 | *
9 | *****************************************************************************/
10 |
11 | #include "fpbinarycommon.h"
12 |
13 | #include
14 | #include
15 |
16 | PyObject *py_zero;
17 | PyObject *py_one;
18 | PyObject *py_two;
19 | PyObject *py_minus_one;
20 | PyObject *py_ten;
21 | PyObject *py_five;
22 |
23 | /* For pickling base objects */
24 | PyObject *fp_small_type_id;
25 | PyObject *fp_large_type_id;
26 |
27 | /* Non-standard method/property names */
28 | PyObject *copy_method_name_str = NULL;
29 | PyObject *resize_method_name_str = NULL;
30 | PyObject *get_is_signed_method_name_str = NULL;
31 | PyObject *get_format_method_name_str = NULL;
32 | PyObject *str_ex_method_name_str = NULL;
33 | PyObject *complex_real_property_name_str = NULL;
34 | PyObject *complex_imag_property_name_str = NULL;
35 | PyObject *py_default_format_tuple = NULL;
36 |
37 | /* Useful, reusable strings.
38 | * Careful using these with concat methods - check if a reference is stolen...
39 | */
40 | PyObject *decimal_point_str = NULL;
41 | PyObject *add_sign_str = NULL;
42 | PyObject *j_str = NULL;
43 | PyObject *open_bracket_str = NULL;
44 | PyObject *close_bracket_str = NULL;
45 |
46 | /*
47 | * Does a left shift SAFELY (shifting by more than the length of the
48 | * type is undefined).
49 | */
50 | FP_UINT_TYPE
51 | fp_uint_lshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts)
52 | {
53 | if (num_shifts == 0)
54 | {
55 | return value;
56 | }
57 |
58 | if (num_shifts >= FP_UINT_NUM_BITS)
59 | {
60 | return 0;
61 | }
62 |
63 | return value << num_shifts;
64 | }
65 |
66 | /*
67 | * Does a right shift SAFELY (shifting by more than the length of the
68 | * type is undefined).
69 | */
70 | FP_UINT_TYPE
71 | fp_uint_rshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts)
72 | {
73 | if (num_shifts == 0)
74 | {
75 | return value;
76 | }
77 |
78 | if (num_shifts >= FP_UINT_NUM_BITS)
79 | {
80 | return 0;
81 | }
82 |
83 | return value >> num_shifts;
84 | }
85 |
86 | /*
87 | * FFS, this does what the 2.7 PyString concat does...
88 | *
89 | * Probably could change all code to use unicode.
90 | *
91 | * The reference in left is stolen and reassigned to the result of the
92 | * concatenation.
93 | */
94 | void
95 | unicode_concat(PyObject **left, PyObject *right)
96 | {
97 | PyObject *tmp = *left;
98 | *left = PyUnicode_Concat(tmp, right);
99 | Py_DECREF(tmp);
100 | }
101 |
102 | /*
103 | * To minimise impact of v2->v3 ...
104 | */
105 | int
106 | FpBinary_TpCompare(PyObject *op1, PyObject *op2)
107 | {
108 | #if PY_MAJOR_VERSION >= 3
109 | int result = -1;
110 | PyObject *gt = PyObject_RichCompare(op1, op2, Py_GT);
111 |
112 | if (gt == Py_True)
113 | {
114 | result = 1;
115 | }
116 | else
117 | {
118 | PyObject *eq = PyObject_RichCompare(op1, op2, Py_EQ);
119 |
120 | if (eq == Py_True)
121 | {
122 | result = 0;
123 | }
124 |
125 | Py_DECREF(eq);
126 | }
127 |
128 | Py_DECREF(gt);
129 | return result;
130 |
131 | #else
132 |
133 | return FP_METHOD(op1, tp_compare)(op1, op2);
134 |
135 | #endif
136 | }
137 |
138 | bool
139 | fp_binary_new_params_parse(PyObject *args, PyObject *kwds, long *int_bits,
140 | long *frac_bits, bool *is_signed, double *value,
141 | PyObject **bit_field, PyObject **format_instance)
142 | {
143 | static char *kwlist[] = {"int_bits", "frac_bits", "signed", "value",
144 | "bit_field", "format_inst", NULL};
145 |
146 | PyObject *py_int_bits = NULL, *py_frac_bits = NULL;
147 | PyObject *py_is_signed = NULL;
148 | *bit_field = NULL;
149 | *format_instance = NULL;
150 |
151 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOdOO", kwlist,
152 | &py_int_bits, &py_frac_bits, &py_is_signed,
153 | value, bit_field, format_instance))
154 | return false;
155 |
156 | /* Ensure there is information for the format */
157 | if (py_int_bits)
158 | {
159 | if (!check_supported_builtin_int(py_int_bits))
160 | {
161 | PyErr_SetString(PyExc_TypeError, "int_bits must be an integer.");
162 | return false;
163 | }
164 |
165 | *int_bits = PyLong_AsLong(py_int_bits);
166 | }
167 |
168 | if (py_frac_bits)
169 | {
170 | if (!check_supported_builtin_int(py_frac_bits))
171 | {
172 | PyErr_SetString(PyExc_TypeError, "frac_bits must be an integer.");
173 | return false;
174 | }
175 |
176 | *frac_bits = PyLong_AsLong(py_frac_bits);
177 | }
178 |
179 | if (!(*format_instance) && (!py_int_bits || !py_frac_bits))
180 | {
181 | double scaled_value;
182 | FP_INT_TYPE int_bits_uint, frac_bits_uint;
183 | calc_double_to_fp_params(*value, &scaled_value, &int_bits_uint,
184 | &frac_bits_uint);
185 |
186 | if (!py_int_bits)
187 | {
188 | *int_bits = (long)int_bits_uint;
189 | }
190 |
191 | if (!py_frac_bits)
192 | {
193 | *frac_bits = (long)frac_bits_uint;
194 | }
195 | }
196 |
197 | if (py_is_signed)
198 | {
199 | if (!PyBool_Check(py_is_signed))
200 | {
201 | PyErr_SetString(PyExc_TypeError, "signed must be True or False.");
202 | return false;
203 | }
204 |
205 | if (py_is_signed == Py_True)
206 | {
207 | *is_signed = true;
208 | }
209 | else
210 | {
211 | *is_signed = false;
212 | }
213 | }
214 |
215 | if (*bit_field)
216 | {
217 | if (!PyLong_Check(*bit_field))
218 | {
219 | PyErr_SetString(PyExc_TypeError,
220 | "bit_field must be a long integer.");
221 | return false;
222 | }
223 | }
224 |
225 | return true;
226 | }
227 |
228 | bool
229 | fp_binary_subscript_get_item_index(PyObject *item, Py_ssize_t *index)
230 | {
231 | if (PyIndex_Check(item))
232 | {
233 | *index = PyNumber_AsSsize_t(item, NULL);
234 | return true;
235 | }
236 |
237 | return false;
238 | }
239 |
240 | bool
241 | fp_binary_subscript_get_item_start_stop(PyObject *item, Py_ssize_t *start,
242 | Py_ssize_t *stop,
243 | Py_ssize_t assumed_length)
244 | {
245 | if (PySlice_Check(item))
246 | {
247 | Py_ssize_t step;
248 |
249 | /* I ain't giving up a clean record now... */
250 | #if PY_MAJOR_VERSION >= 3
251 | PyObject *cast_item = item;
252 | #else
253 | PySliceObject *cast_item = (PySliceObject *)item;
254 | #endif
255 |
256 | /*
257 | * In Python 2, the slice unpacking methods are pretty bad. To make this
258 | * as clean as possible (i.e. only upper functions care about length, we
259 | * have a bit of a hack here.
260 | */
261 | Py_ssize_t length;
262 | if (PySlice_GetIndicesEx(cast_item, PY_SSIZE_T_MAX, start, stop, &step,
263 | &length) < 0)
264 | {
265 | return false;
266 | }
267 |
268 | if (step > 0)
269 | {
270 | return true;
271 | }
272 | else
273 | {
274 | PyErr_SetString(PyExc_TypeError,
275 | "Steps in subscripts are not supported.");
276 | }
277 | }
278 |
279 | return false;
280 | }
281 |
282 | /*
283 | * This function basically converts a double to a fpbinary without creating
284 | * the actual fpbinary object. This is so a user can decide which type to
285 | * use based on the magnitude of scaled_value. If scaled value is too large
286 | * to be represented by a 64 bit fpbinary object, the values can be applied
287 | * to a fpbinarylarge object (this is why the output parameter scaled_value is
288 | * a double).
289 | *
290 | * We could have decided to just use the exp value and DBL_MANT_DIG but in
291 | * most cases, I wouldn't expect raw doubles/floats to be used with fpbinary
292 | * objects unless they were quite small and of limited precision, and if we
293 | * minimise the number of bits used, we reduce the need to use the slower
294 | * fpbinarylarge object after math operations.
295 | */
296 | void
297 | calc_double_to_fp_params(double input_value, double *scaled_value,
298 | FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits)
299 | {
300 | int exp;
301 | double mantissa = frexp(input_value, &exp);
302 |
303 | if (mantissa == 0)
304 | {
305 | *int_bits = 1;
306 | *frac_bits = 0;
307 | *scaled_value = 0.0;
308 | }
309 | else
310 | {
311 | FP_INT_TYPE i;
312 | double shifted_mant = mantissa;
313 |
314 | /* Multiply the mantissa by two and subtract the new integer part.
315 | * Continue until
316 | * the remaining value is zero. I'm doing this instead of (say)
317 | * converting to an
318 | * integer and left shifting because we can't guarantee a double will
319 | * have less bits
320 | * than a long long int.
321 | *
322 | * I'm using a for loop that WILL exit if it gets to
323 | * the max number of bits possible in the mantissa. This is so the code
324 | * doesn't
325 | * lock up if I've made a mistake...
326 | */
327 | for (i = 1; i <= DBL_MANT_DIG; i++)
328 | {
329 | shifted_mant *= 2;
330 | shifted_mant -= (int)shifted_mant;
331 |
332 | if (shifted_mant == 0.0)
333 | {
334 | break;
335 | }
336 | }
337 |
338 | /* i should now be the total number of PRECISION bits required. */
339 | if (exp > 0)
340 | {
341 | *int_bits = exp;
342 | }
343 | else
344 | *int_bits = 0;
345 |
346 | *frac_bits = 0;
347 |
348 | /* Negative exponent means its magnitude is the inital number of
349 | * FRACTIONAL bits.
350 | */
351 | if (exp < 0)
352 | {
353 | *frac_bits = abs(exp);
354 | }
355 |
356 | if (i > *int_bits)
357 | {
358 | *frac_bits += i - *int_bits;
359 | }
360 |
361 | /* And calculate the scaled_value for fixed point representation. */
362 | *scaled_value = ldexp(mantissa, exp + *frac_bits);
363 |
364 | /* We always assume a signed type, so add an extra bit for the sign. */
365 | *int_bits += 1;
366 | }
367 | }
368 |
369 | /*
370 | * This function basically converts a PyInt or PyLong to a fpbinary without
371 | * creating
372 | * the actual fpbinary object. This is so a user can decide which type to
373 | * use based on the magnitude of scaled_value. If scaled value is too large
374 | * to be represented by a 64 bit fpbinary object, the values can be applied
375 | * to a fpbinarylarge object.
376 | *
377 | * input_value must be a pointer to a PyInt or PyLong.
378 | */
379 | void
380 | calc_pyint_to_fp_params(PyObject *input_value, PyObject **scaled_value,
381 | FP_INT_TYPE *int_bits)
382 | {
383 | *scaled_value = NULL;
384 | *int_bits = 0;
385 |
386 | if (FpBinary_IntCheck(input_value) || PyLong_Check(input_value))
387 | {
388 | *scaled_value = FpBinary_EnsureIsPyLong(input_value);
389 | }
390 |
391 | if (*scaled_value)
392 | {
393 | size_t num_bits = _PyLong_NumBits(*scaled_value);
394 | /* Assume signed - need extra bit (_PyLong_NumBits returns number
395 | * of bits for magnitude).
396 | */
397 | num_bits++;
398 |
399 | *int_bits = num_bits;
400 | }
401 | }
402 |
403 | /*
404 | * Attempts to work out the smallest int and frac bit format for the passed
405 | * numberic object.
406 | * If the object type isn't supported (see check_supported_builtin) returns
407 | * false.
408 | */
409 | bool
410 | get_best_int_frac_bits(PyObject *obj, FP_INT_TYPE *int_bits,
411 | FP_INT_TYPE *frac_bits)
412 | {
413 | if (check_supported_builtin_int(obj))
414 | {
415 | PyObject *scaled_bits;
416 | calc_pyint_to_fp_params(obj, &scaled_bits, int_bits);
417 | *frac_bits = 0;
418 |
419 | if (scaled_bits)
420 | {
421 | Py_DECREF(scaled_bits);
422 | }
423 | }
424 | else if (check_supported_builtin_float(obj))
425 | {
426 | double scaled_value;
427 | calc_double_to_fp_params(PyFloat_AsDouble(obj), &scaled_value, int_bits,
428 | frac_bits);
429 | }
430 | else
431 | {
432 | return false;
433 | }
434 |
435 | return true;
436 | }
437 |
438 | PyObject *
439 | fp_uint_as_pylong(FP_UINT_TYPE value)
440 | {
441 | return PyLong_FromUnsignedLongLong(value);
442 | }
443 |
444 | PyObject *
445 | fp_int_as_pylong(FP_INT_TYPE value)
446 | {
447 | return PyLong_FromLongLong(value);
448 | }
449 |
450 | FP_INT_TYPE
451 | pylong_as_fp_int(PyObject *val) { return (FP_INT_TYPE)PyLong_AsLongLong(val); }
452 |
453 | FP_UINT_TYPE
454 | pylong_as_fp_uint(PyObject *val)
455 | {
456 | return (FP_UINT_TYPE)PyLong_AsUnsignedLongLong(val);
457 | }
458 |
459 | /*
460 | * Will build a PyLong object whose bits are the scaled value as defined
461 | * by the float value and frac_bits. Rounding will be taken care of on
462 | * the basis of round_mode.
463 | *
464 | * NOTE: Overflow is not checked for (that is why there is no int_bits param).
465 | */
466 | void
467 | build_scaled_bits_from_pyfloat(PyObject *value, PyObject *frac_bits,
468 | fp_round_mode_t round_mode,
469 | PyObject **output_obj)
470 | {
471 | PyObject *py_scale_factor =
472 | FP_NUM_METHOD(py_one, nb_lshift)(py_one, frac_bits);
473 | PyObject *py_scaled_value =
474 | FP_NUM_METHOD(value, nb_multiply)(value, py_scale_factor);
475 | double dbl_scaled_value = PyFloat_AsDouble(py_scaled_value);
476 |
477 | if (round_mode == ROUNDING_NEAR_POS_INF)
478 | {
479 | dbl_scaled_value += 0.5;
480 | }
481 |
482 | dbl_scaled_value = floor(dbl_scaled_value);
483 | *output_obj = PyLong_FromDouble(dbl_scaled_value);
484 |
485 | Py_DECREF(py_scale_factor);
486 | Py_DECREF(py_scaled_value);
487 | }
488 |
489 | /* Will attempt to convert the format_tuple_param int_bits and frac_bits PyLong
490 | * objects.
491 | *
492 | * It is assumed the calling function will decrement the reference to
493 | * *int_bits and *frac_bits.
494 | *
495 | * Returns false if the parameter could not be converted.
496 | */
497 | bool
498 | extract_fp_format_from_tuple(PyObject *format_tuple_param, PyObject **int_bits,
499 | PyObject **frac_bits)
500 | {
501 | *int_bits = NULL;
502 | *frac_bits = NULL;
503 |
504 | /* FP format is defined by a python tuple: (int_bits, frac_bits) */
505 | if (PyTuple_Check(format_tuple_param))
506 | {
507 | PyObject *new_int_bits_borrowed = NULL, *new_frac_bits_borrowed = NULL;
508 |
509 | if (PyTuple_Size(format_tuple_param) != 2)
510 | {
511 | PyErr_SetString(PyExc_TypeError, "Format tuple must be length 2.");
512 | return false;
513 | }
514 |
515 | new_int_bits_borrowed = PyTuple_GetItem(format_tuple_param, 0);
516 | if (new_int_bits_borrowed)
517 | {
518 | if (FpBinary_IntCheck(new_int_bits_borrowed) ||
519 | PyLong_Check(new_int_bits_borrowed))
520 | {
521 | /* Need to convert to long. */
522 | *int_bits = FpBinary_EnsureIsPyLong(new_int_bits_borrowed);
523 | }
524 | }
525 |
526 | new_frac_bits_borrowed = PyTuple_GetItem(format_tuple_param, 1);
527 | if (new_frac_bits_borrowed)
528 | {
529 | if (FpBinary_IntCheck(new_frac_bits_borrowed) ||
530 | PyLong_Check(new_frac_bits_borrowed))
531 | {
532 | /* Need to convert to long. */
533 | *frac_bits = FpBinary_EnsureIsPyLong(new_frac_bits_borrowed);
534 | }
535 | }
536 |
537 | if (!*int_bits || !*frac_bits)
538 | {
539 | PyErr_SetString(PyExc_TypeError,
540 | "The values in the format tuple must be integers.");
541 | }
542 | }
543 |
544 | return (*int_bits && *frac_bits);
545 | }
546 |
547 | /* Will attempt to convert the format_tuple_param to int_bits and frac_bits c
548 | * long values.
549 | *
550 | * Returns false if the parameter could not be converted.
551 | */
552 | bool
553 | extract_fp_format_ints_from_tuple(PyObject *format_tuple_param,
554 | FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits)
555 | {
556 | PyObject *int_bits_py = NULL, *frac_bits_py = NULL;
557 |
558 | if (extract_fp_format_from_tuple(format_tuple_param, &int_bits_py,
559 | &frac_bits_py))
560 | {
561 | /* Convert the py objects to the c type */
562 | *int_bits = PyLong_AsLong(int_bits_py);
563 | *frac_bits = PyLong_AsLong(frac_bits_py);
564 |
565 | Py_DECREF(int_bits_py);
566 | Py_DECREF(frac_bits_py);
567 |
568 | return true;
569 | }
570 |
571 | return false;
572 | }
573 |
574 | /*
575 | * Checks the parameters to an FpBinary new method are the correct types.
576 | * Returns false if one or more params are the wrong type.
577 | */
578 | bool
579 | check_new_method_input_types(PyObject *py_is_signed, PyObject *bit_field)
580 | {
581 | if (py_is_signed)
582 | {
583 | if (!PyBool_Check(py_is_signed))
584 | {
585 | PyErr_SetString(PyExc_TypeError, "signed must be True or False.");
586 | return false;
587 | }
588 | }
589 |
590 | if (bit_field)
591 | {
592 | if (!PyLong_Check(bit_field))
593 | {
594 | PyErr_SetString(PyExc_TypeError,
595 | "bit_field must be a long integer.");
596 | return false;
597 | }
598 | }
599 |
600 | return true;
601 | }
602 |
603 | /*
604 | * Produces a string representation of the arbitrary length fixed point number
605 | * as defined by scaled_value, int_bits, and frac_bits. Scientific notation is
606 | * NOT used.
607 | *
608 | * scaled_value must be a 2's compliment representation of the fixed point
609 | * number
610 | * multiplied by 2**frac bits.
611 | */
612 | PyObject *
613 | scaled_long_to_float_str(PyObject *scaled_value, PyObject *int_bits,
614 | PyObject *frac_bits)
615 | {
616 | /*
617 | * scaled_value: this is the number multiplied by 2**frac_bits. So
618 | * to be converted to the correct value while staying as an integer, we
619 | * multiply
620 | * by 10**frac bits and then divide by 2**frac_bits (note that each negative
621 | * power of 2 can only produce, at most, a single decimal equivalent because
622 | * 1 >> 2 produces 0.5). So this is (10/2)**frac_bits = 5**frac_bits. This
623 | * will
624 | * give us an integer that can be assessed using the standard % 10 logic.
625 | */
626 | PyObject *int_bits_is_negative, *frac_bits_is_negative;
627 | PyObject *int_string, *frac_string, *final_string;
628 | PyObject *frac_format_string, *frac_value_tuple;
629 | PyObject *scaled_value_padded;
630 | PyObject *is_negative, *scaled_value_mag, *frac_mask1, *frac_mask;
631 | PyObject *frac_part, *int_part, *frac_scale, *frac_part_corrected;
632 |
633 | long frac_bits_long, frac_dec_places, count = 0;
634 | PyObject *modulus, *modulus_is_zero;
635 |
636 | /* If we have negative int_bits, pad out the extra fractional spaces */
637 | int_bits_is_negative = PyObject_RichCompare(int_bits, py_zero, Py_LT);
638 |
639 | if (int_bits_is_negative == Py_True)
640 | {
641 | int_bits = py_zero; // No need to inc/dec this
642 | }
643 |
644 | /* If we have negative frac_bits, pad out the extra int spaces */
645 | frac_bits_is_negative = PyObject_RichCompare(frac_bits, py_zero, Py_LT);
646 |
647 | if (frac_bits_is_negative == Py_True)
648 | {
649 | PyObject *left_shift = PyNumber_Absolute(frac_bits);
650 | scaled_value_padded = PyNumber_Lshift(scaled_value, left_shift);
651 | Py_DECREF(left_shift);
652 | frac_bits = py_zero; // No need to inc/dec this
653 | }
654 | else
655 | {
656 | Py_INCREF(scaled_value);
657 | scaled_value_padded = scaled_value;
658 | }
659 |
660 | is_negative = PyObject_RichCompare(scaled_value_padded, py_zero, Py_LT);
661 | scaled_value_mag = PyNumber_Absolute(scaled_value_padded);
662 | frac_mask1 = PyNumber_Lshift(py_one, frac_bits);
663 | frac_mask = PyNumber_Subtract(frac_mask1, py_one);
664 | frac_part = PyNumber_And(scaled_value_mag, frac_mask);
665 | int_part = PyNumber_Rshift(scaled_value_mag, frac_bits);
666 |
667 | frac_scale = PyNumber_Power(py_five, frac_bits, Py_None);
668 | frac_part_corrected = PyNumber_Multiply(frac_part, frac_scale);
669 |
670 | /* Need to get rid of any trailing zeros before creating string */
671 | frac_bits_long = PyLong_AsLong(frac_bits), count = 0;
672 | frac_dec_places = frac_bits_long;
673 | modulus = PyNumber_Remainder(frac_part_corrected, py_ten);
674 | modulus_is_zero = PyObject_RichCompare(modulus, py_zero, Py_EQ);
675 |
676 | while (count < frac_bits_long && modulus_is_zero == Py_True)
677 | {
678 | PyObject *tmp = frac_part_corrected;
679 | frac_part_corrected = PyNumber_FloorDivide(tmp, py_ten);
680 | Py_DECREF(tmp);
681 |
682 | Py_DECREF(modulus);
683 | Py_DECREF(modulus_is_zero);
684 | modulus = PyNumber_Remainder(frac_part_corrected, py_ten);
685 | modulus_is_zero = PyObject_RichCompare(modulus, py_zero, Py_EQ);
686 |
687 | frac_dec_places--;
688 | count++;
689 | }
690 |
691 | Py_DECREF(modulus);
692 | Py_DECREF(modulus_is_zero);
693 |
694 | int_string = FP_METHOD(int_part, tp_str)(int_part);
695 |
696 | frac_format_string = PyUnicode_FromFormat("%%0%ldd", frac_dec_places);
697 | frac_value_tuple = PyTuple_Pack(1, frac_part_corrected);
698 | frac_string = PyUnicode_Format(frac_format_string, frac_value_tuple);
699 |
700 | if (is_negative == Py_True)
701 | {
702 | final_string = PyUnicode_FromString("-");
703 | unicode_concat(&final_string, int_string);
704 | Py_DECREF(int_string);
705 | }
706 | else
707 | {
708 | final_string = int_string;
709 | }
710 |
711 | unicode_concat(&final_string, decimal_point_str);
712 | unicode_concat(&final_string, frac_string);
713 |
714 | Py_DECREF(scaled_value_padded);
715 | Py_DECREF(frac_string);
716 | Py_DECREF(is_negative);
717 | Py_DECREF(int_bits_is_negative);
718 | Py_DECREF(frac_bits_is_negative);
719 | Py_DECREF(scaled_value_mag);
720 | Py_DECREF(frac_mask1);
721 | Py_DECREF(frac_mask);
722 | Py_DECREF(frac_part);
723 | Py_DECREF(int_part);
724 | Py_DECREF(frac_scale);
725 | Py_DECREF(frac_part_corrected);
726 | Py_DECREF(frac_format_string);
727 | Py_DECREF(frac_value_tuple);
728 |
729 | return final_string;
730 | }
731 |
732 | /*
733 | * Convenience function to forward a function call on to a PyObject with
734 | * arguments.
735 | */
736 | PyObject *
737 | forward_call_with_args(PyObject *obj, PyObject *method_name, PyObject *args,
738 | PyObject *kwds)
739 | {
740 | PyObject *callable = PyObject_GetAttr(obj, method_name);
741 | if (callable)
742 | {
743 | PyObject* result = NULL;
744 |
745 | if (!args)
746 | {
747 | PyObject *dummy_tup = PyTuple_New(0);
748 | result = PyObject_Call(callable, dummy_tup, kwds);
749 | Py_DECREF(dummy_tup);
750 | }
751 | else
752 | {
753 | result = PyObject_Call(callable, args, kwds);
754 | }
755 |
756 | Py_DECREF(callable);
757 | return result;
758 | }
759 |
760 | return NULL;
761 | }
762 |
763 | bool
764 | FpBinary_IntCheck(PyObject *ob)
765 | {
766 | #if PY_MAJOR_VERSION >= 3
767 |
768 | return false;
769 |
770 | #else
771 |
772 | return PyInt_Check(ob);
773 |
774 | #endif
775 | }
776 |
777 | /*
778 | * Will take the input and:
779 | * - if it is NOT a PyLong, will attempt to convert to a PyLong
780 | * - if it IS a PyLong, will increment the ref count and return it
781 | *
782 | * Note that this function should only be called if the input ob is either
783 | * a PyLong or a PyInt.
784 | */
785 | PyObject *
786 | FpBinary_EnsureIsPyLong(PyObject *ob)
787 | {
788 | #if PY_MAJOR_VERSION >= 3
789 |
790 | Py_INCREF(ob);
791 | return ob;
792 |
793 | #else
794 |
795 | if (PyLong_Check(ob))
796 | {
797 | Py_INCREF(ob);
798 | return ob;
799 | }
800 | else
801 | {
802 | return PyLong_FromLong(PyInt_AsLong(ob));
803 | }
804 |
805 | #endif
806 | }
807 |
808 | /*
809 | * Will take the input and:
810 | * - if it is NOT a PyInt AND platform supports distinction between Int and
811 | * Long,
812 | * will convert to a PyInt
813 | * - if it IS a PyLong, will increment the ref count and return it
814 | *
815 | * Note that this function should only be called if the input ob is either
816 | * a PyLong or a PyInt.
817 | */
818 | PyObject *
819 | FpBinary_TryConvertToPyInt(PyObject *ob)
820 | {
821 | #if PY_MAJOR_VERSION >= 3
822 |
823 | Py_INCREF(ob);
824 | return ob;
825 |
826 | #else
827 |
828 | if (PyInt_Check(ob))
829 | {
830 | Py_INCREF(ob);
831 | return ob;
832 | }
833 | else
834 | {
835 | return PyInt_FromLong(PyLong_AsLong(ob));
836 | }
837 |
838 | #endif
839 | }
840 |
841 | void
842 | FpBinaryCommon_InitModule(void)
843 | {
844 | py_zero = PyLong_FromLong(0);
845 | py_one = PyLong_FromLong(1);
846 | py_two = PyLong_FromLong(2);
847 | py_minus_one = PyLong_FromLong(-1);
848 | py_five = PyLong_FromLong(5);
849 | py_ten = PyLong_FromLong(10);
850 |
851 | /* Tells us what type of base object was pickled */
852 | fp_small_type_id = PyLong_FromLong(1);
853 | fp_large_type_id = PyLong_FromLong(2);
854 |
855 | copy_method_name_str = PyUnicode_FromString("__copy__");
856 | resize_method_name_str = PyUnicode_FromString("resize");
857 | get_is_signed_method_name_str = PyUnicode_FromString("is_signed");
858 | get_format_method_name_str = PyUnicode_FromString("format");
859 | str_ex_method_name_str = PyUnicode_FromString("str_ex");
860 | complex_real_property_name_str = PyUnicode_FromString("real");
861 | complex_imag_property_name_str = PyUnicode_FromString("imag");
862 | py_default_format_tuple = PyTuple_Pack(2, py_one, py_zero);
863 |
864 | decimal_point_str = PyUnicode_FromString(".");
865 | add_sign_str = PyUnicode_FromString("+");
866 | open_bracket_str = PyUnicode_FromString("(");
867 | close_bracket_str = PyUnicode_FromString(")");
868 | j_str = PyUnicode_FromString("j");
869 | }
870 |
--------------------------------------------------------------------------------