byteforge 0.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- byteforge-0.0.5/.github/workflows/release.yml +67 -0
- byteforge-0.0.5/.github/workflows/test.yml +26 -0
- byteforge-0.0.5/.gitignore +136 -0
- byteforge-0.0.5/CHANGELOG.md +22 -0
- byteforge-0.0.5/CLAUDE.md +35 -0
- byteforge-0.0.5/MANIFEST.in +1 -0
- byteforge-0.0.5/PKG-INFO +105 -0
- byteforge-0.0.5/README.md +96 -0
- byteforge-0.0.5/pyproject.toml +68 -0
- byteforge-0.0.5/setup.cfg +4 -0
- byteforge-0.0.5/setup.py +37 -0
- byteforge-0.0.5/src/byteforge/__init__.py +42 -0
- byteforge-0.0.5/src/byteforge/_base.py +289 -0
- byteforge-0.0.5/src/byteforge/_c/__init__.py +0 -0
- byteforge-0.0.5/src/byteforge/_c/bcd.c +78 -0
- byteforge-0.0.5/src/byteforge/_c/dec.c +238 -0
- byteforge-0.0.5/src/byteforge/_c/gray.c +37 -0
- byteforge-0.0.5/src/byteforge/_c/ibm.c +177 -0
- byteforge-0.0.5/src/byteforge/_c/milstd1750a.c +152 -0
- byteforge-0.0.5/src/byteforge/_c/ti.c +166 -0
- byteforge-0.0.5/src/byteforge/_c/ufunc.c +194 -0
- byteforge-0.0.5/src/byteforge/_c/utils.c +15 -0
- byteforge-0.0.5/src/byteforge/_registry.py +68 -0
- byteforge-0.0.5/src/byteforge/_validation.py +13 -0
- byteforge-0.0.5/src/byteforge/_version.py +34 -0
- byteforge-0.0.5/src/byteforge/bcd.py +203 -0
- byteforge-0.0.5/src/byteforge/boolean.py +46 -0
- byteforge-0.0.5/src/byteforge/dec_float.py +215 -0
- byteforge-0.0.5/src/byteforge/gray_code.py +88 -0
- byteforge-0.0.5/src/byteforge/ibm_float.py +179 -0
- byteforge-0.0.5/src/byteforge/ieee754.py +45 -0
- byteforge-0.0.5/src/byteforge/linear_scaled.py +116 -0
- byteforge-0.0.5/src/byteforge/mil_std_1750a.py +193 -0
- byteforge-0.0.5/src/byteforge/offset_binary.py +73 -0
- byteforge-0.0.5/src/byteforge/ones_complement.py +84 -0
- byteforge-0.0.5/src/byteforge/py.typed +0 -0
- byteforge-0.0.5/src/byteforge/ti_float.py +179 -0
- byteforge-0.0.5/src/byteforge/twos_complement.py +84 -0
- byteforge-0.0.5/src/byteforge/unsigned.py +53 -0
- byteforge-0.0.5/src/byteforge.egg-info/PKG-INFO +105 -0
- byteforge-0.0.5/src/byteforge.egg-info/SOURCES.txt +66 -0
- byteforge-0.0.5/src/byteforge.egg-info/dependency_links.txt +1 -0
- byteforge-0.0.5/src/byteforge.egg-info/requires.txt +1 -0
- byteforge-0.0.5/src/byteforge.egg-info/top_level.txt +1 -0
- byteforge-0.0.5/tests/__init__.py +0 -0
- byteforge-0.0.5/tests/conftest.py +0 -0
- byteforge-0.0.5/tests/test_bcd.py +241 -0
- byteforge-0.0.5/tests/test_benchmarks.py +88 -0
- byteforge-0.0.5/tests/test_boolean.py +78 -0
- byteforge-0.0.5/tests/test_bytes.py +143 -0
- byteforge-0.0.5/tests/test_c_parity.py +188 -0
- byteforge-0.0.5/tests/test_dec_float.py +189 -0
- byteforge-0.0.5/tests/test_empty_arrays.py +49 -0
- byteforge-0.0.5/tests/test_gray_code.py +100 -0
- byteforge-0.0.5/tests/test_ibm_float.py +101 -0
- byteforge-0.0.5/tests/test_ieee754.py +151 -0
- byteforge-0.0.5/tests/test_linear_scaled.py +100 -0
- byteforge-0.0.5/tests/test_mil_std_1750a.py +126 -0
- byteforge-0.0.5/tests/test_multidimensional.py +136 -0
- byteforge-0.0.5/tests/test_new_features.py +307 -0
- byteforge-0.0.5/tests/test_offset_binary.py +71 -0
- byteforge-0.0.5/tests/test_ones_complement.py +152 -0
- byteforge-0.0.5/tests/test_registry.py +126 -0
- byteforge-0.0.5/tests/test_roundtrip_fuzz.py +337 -0
- byteforge-0.0.5/tests/test_ti_float.py +146 -0
- byteforge-0.0.5/tests/test_twos_complement.py +140 -0
- byteforge-0.0.5/tests/test_unsigned.py +82 -0
- byteforge-0.0.5/uv.lock +716 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: "Publish"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build_sdist:
|
|
9
|
+
name: Build sdist
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v6
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v7
|
|
16
|
+
|
|
17
|
+
- name: Build sdist
|
|
18
|
+
run: uv build --sdist
|
|
19
|
+
|
|
20
|
+
- name: Upload sdist
|
|
21
|
+
uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: sdist
|
|
24
|
+
path: dist/*.tar.gz
|
|
25
|
+
|
|
26
|
+
build_wheels:
|
|
27
|
+
name: Build wheels (${{ matrix.os }})
|
|
28
|
+
runs-on: ${{ matrix.os }}
|
|
29
|
+
strategy:
|
|
30
|
+
fail-fast: false
|
|
31
|
+
matrix:
|
|
32
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v6
|
|
35
|
+
|
|
36
|
+
- name: Build wheels
|
|
37
|
+
uses: pypa/cibuildwheel@v3.4.0
|
|
38
|
+
env:
|
|
39
|
+
CIBW_ARCHS_MACOS: "x86_64 arm64"
|
|
40
|
+
|
|
41
|
+
- name: Upload wheels
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: wheels-${{ matrix.os }}
|
|
45
|
+
path: wheelhouse/*.whl
|
|
46
|
+
|
|
47
|
+
publish:
|
|
48
|
+
name: Publish to PyPI
|
|
49
|
+
needs: [build_sdist, build_wheels]
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
environment:
|
|
52
|
+
name: pypi
|
|
53
|
+
permissions:
|
|
54
|
+
id-token: write
|
|
55
|
+
contents: read
|
|
56
|
+
steps:
|
|
57
|
+
- name: Install uv
|
|
58
|
+
uses: astral-sh/setup-uv@v7
|
|
59
|
+
|
|
60
|
+
- name: Download all artifacts
|
|
61
|
+
uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
path: dist
|
|
64
|
+
merge-multiple: true
|
|
65
|
+
|
|
66
|
+
- name: Publish to PyPI
|
|
67
|
+
run: uv publish dist/*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
fail-fast: false
|
|
10
|
+
matrix:
|
|
11
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
|
|
16
|
+
- name: Install uv and set the Python version
|
|
17
|
+
uses: astral-sh/setup-uv@v7
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install the project
|
|
23
|
+
run: uv sync --locked --all-extras --dev
|
|
24
|
+
|
|
25
|
+
- name: Run pytest
|
|
26
|
+
run: uv run pytest tests/
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
__about__.py
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
.python-version
|
|
87
|
+
|
|
88
|
+
# pipenv
|
|
89
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
90
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
91
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
92
|
+
# install all needed dependencies.
|
|
93
|
+
#Pipfile.lock
|
|
94
|
+
|
|
95
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
96
|
+
__pypackages__/
|
|
97
|
+
|
|
98
|
+
# Celery stuff
|
|
99
|
+
celerybeat-schedule
|
|
100
|
+
celerybeat.pid
|
|
101
|
+
|
|
102
|
+
# SageMath parsed files
|
|
103
|
+
*.sage.py
|
|
104
|
+
|
|
105
|
+
# Environments
|
|
106
|
+
.env
|
|
107
|
+
.venv
|
|
108
|
+
env/
|
|
109
|
+
venv/
|
|
110
|
+
ENV/
|
|
111
|
+
env.bak/
|
|
112
|
+
venv.bak/
|
|
113
|
+
|
|
114
|
+
# Spyder project settings
|
|
115
|
+
.spyderproject
|
|
116
|
+
.spyproject
|
|
117
|
+
|
|
118
|
+
# Rope project settings
|
|
119
|
+
.ropeproject
|
|
120
|
+
|
|
121
|
+
# mkdocs documentation
|
|
122
|
+
/site
|
|
123
|
+
|
|
124
|
+
# mypy
|
|
125
|
+
.mypy_cache/
|
|
126
|
+
.dmypy.json
|
|
127
|
+
dmypy.json
|
|
128
|
+
|
|
129
|
+
# Pyre type checker
|
|
130
|
+
.pyre/
|
|
131
|
+
|
|
132
|
+
# hatch-vcs
|
|
133
|
+
_version.py
|
|
134
|
+
|
|
135
|
+
# ruff
|
|
136
|
+
.ruff_cache
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.0.4] - 2026-03-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Unified `encode_errors` parameter on all encodings (except IEEE754 and Boolean):
|
|
7
|
+
`"clamp"` (default), `"raise"`, `"nan"`, or numeric sentinel.
|
|
8
|
+
- `BCD` now accepts `decode_errors` for invalid-nibble handling during decode.
|
|
9
|
+
- Float encodings (MilStd1750A, TIFloat, IBMFloat, DECFloat, DECFloatG) now
|
|
10
|
+
support `encode_errors` for out-of-range detection.
|
|
11
|
+
- pytest-benchmark benchmarks for all encoding types.
|
|
12
|
+
- `__version__` attribute powered by setuptools-scm.
|
|
13
|
+
- Docstrings on all `_encode_py()`/`_decode_py()` private helpers.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **BREAKING**: `errors` parameter renamed to `encode_errors` on all encodings.
|
|
17
|
+
- **BREAKING**: `BCD(errors=...)` renamed to `BCD(decode_errors=...)`.
|
|
18
|
+
- Version management switched from hardcoded to setuptools-scm.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Fixed mypy type annotation errors in `_base.py`, `twos_complement.py`,
|
|
22
|
+
`ieee754.py`, and `bcd.py`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Byteforge
|
|
2
|
+
|
|
3
|
+
Encode and decode numbers as unsigned integer bit patterns (`np.ndarray`).
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `src/byteforge/` — flat package
|
|
8
|
+
- `_base.py` — abstract `Encoding` base class (encode/decode accept scalars or arrays)
|
|
9
|
+
- `_registry.py` — `@register` decorator and `create_encoding()` factory
|
|
10
|
+
- 13 encodings: Unsigned, TwosComplement, OnesComplement, IEEE754, LinearScaled, MilStd1750A, BCD, GrayCode, OffsetBinary, Boolean, TIFloat, IBMFloat, DECFloat/DECFloatG
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
- **Test**: `uv run pytest tests/ -v`
|
|
15
|
+
- **Lint**: `uv run ruff check src/ tests/`
|
|
16
|
+
- **Type check**: `uv run mypy`
|
|
17
|
+
|
|
18
|
+
## C Extension (`src/byteforge/_c/`)
|
|
19
|
+
|
|
20
|
+
Optional NumPy C ufuncs accelerate encodings where Python has per-element loops or multi-pass numpy operations. Falls back to pure Python when the C extension is unavailable (set `BYTEFORGE_NO_C=1` to force fallback).
|
|
21
|
+
|
|
22
|
+
**Has C ufuncs:** BCD (encode/decode), GrayCode (decode only), MilStd1750A, TIFloat, IBMFloat, DECFloat/DECFloatG (all encode+decode).
|
|
23
|
+
|
|
24
|
+
**No C ufuncs:** Unsigned, TwosComplement, OnesComplement, IEEE754, OffsetBinary, Boolean, LinearScaled — these are already single-pass vectorized numpy ops (`view`, `clip`, `where`, arithmetic) with no loops or multi-pass overhead to eliminate. GrayCode encode is also pure Python since it's a single expression (`n ^ (n >> 1)`).
|
|
25
|
+
|
|
26
|
+
Each encoding module checks `_HAS_C` and dispatches to either the C ufunc or `_encode_py`/`_decode_py` helpers.
|
|
27
|
+
|
|
28
|
+
## Conventions
|
|
29
|
+
|
|
30
|
+
- Python >=3.9, numpy >=2.0
|
|
31
|
+
- Don't use from __future__ import annotations
|
|
32
|
+
- Google style docstrings; use type annotations, not types in docstrings
|
|
33
|
+
- Ruff: rules E, F, W, I, UP, B, SIM; line-length 100
|
|
34
|
+
- Tests use pytest + hypothesis
|
|
35
|
+
- Keep `CHANGELOG.md` up to date under `## Unreleased` for user-facing changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include src/byteforge/_c *.c
|
byteforge-0.0.5/PKG-INFO
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: byteforge
|
|
3
|
+
Version: 0.0.5
|
|
4
|
+
Summary: Encode and decode numbers as unsigned integer bit patterns
|
|
5
|
+
Author-email: Jonathan Olsten <jolsten@gmail.com>
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy>=2.0
|
|
9
|
+
|
|
10
|
+
# Byteforge
|
|
11
|
+
|
|
12
|
+
Encode and decode numbers as unsigned integer bit patterns using NumPy arrays.
|
|
13
|
+
|
|
14
|
+
Requires Python >= 3.9 and NumPy >= 2.0.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
pip install byteforge
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Every encoding exposes the same interface: `encode()` converts physical values to bit patterns, `decode()` converts back.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import numpy as np
|
|
28
|
+
from byteforge import TwosComplement, IEEE754, Unsigned
|
|
29
|
+
|
|
30
|
+
# Integer encodings
|
|
31
|
+
tc = TwosComplement(16)
|
|
32
|
+
dns = tc.encode(np.array([-1, 0, 127])) # array([65535, 0, 127], dtype=uint16)
|
|
33
|
+
tc.decode(dns) # array([-1, 0, 127], dtype=int16)
|
|
34
|
+
|
|
35
|
+
# Floating-point encodings
|
|
36
|
+
ieee = IEEE754(32)
|
|
37
|
+
dns = ieee.encode(np.array([3.14])) # uint32 bit pattern
|
|
38
|
+
ieee.decode(dns) # array([3.14], dtype=float32)
|
|
39
|
+
|
|
40
|
+
# Unsigned with automatic bit width
|
|
41
|
+
u = Unsigned.from_range(max_value=1000) # Unsigned(10)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Registry factory
|
|
45
|
+
|
|
46
|
+
Create encodings by name using the registry:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from byteforge import create_encoding
|
|
50
|
+
|
|
51
|
+
enc = create_encoding("twos_complement", 8)
|
|
52
|
+
enc = create_encoding("ieee32") # aliases have default bit widths
|
|
53
|
+
enc = create_encoding("linear_scaled", 16, scale_factor=0.1, offset=10.0)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Byte serialization
|
|
57
|
+
|
|
58
|
+
Convert encoded bit patterns to/from raw bytes:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
enc = Unsigned(16)
|
|
62
|
+
dns = enc.encode(np.array([256, 512]))
|
|
63
|
+
raw = enc.to_bytes(dns, byteorder="big") # uint8 array, shape (2, 2)
|
|
64
|
+
enc.decode(enc.from_bytes(raw, byteorder="big")) # roundtrip
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For non-byte-aligned widths (e.g. 12-bit), values are zero-padded in the most
|
|
68
|
+
significant bits to fill whole bytes (2 bytes for 12-bit).
|
|
69
|
+
|
|
70
|
+
## Encodings
|
|
71
|
+
|
|
72
|
+
| Encoding | Registry names | Bit widths |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `Unsigned` | `unsigned`, `u` | 1-64 |
|
|
75
|
+
| `TwosComplement` | `twos_complement`, `2c` | 2-64 |
|
|
76
|
+
| `OnesComplement` | `ones_complement`, `1c` | 2-64 |
|
|
77
|
+
| `OffsetBinary` | `offset_binary` | 1-64 |
|
|
78
|
+
| `GrayCode` | `gray_code`, `gray` | 1-64 |
|
|
79
|
+
| `BCD` | `bcd` | 4-64 (multiples of 4) |
|
|
80
|
+
| `Boolean` | `boolean` | 1 |
|
|
81
|
+
| `IEEE754` | `ieee754`, `ieee16`, `ieee32`, `ieee64` | 16, 32, 64 |
|
|
82
|
+
| `LinearScaled` | `linear_scaled` | 1-64 |
|
|
83
|
+
| `MilStd1750A` | `mil_std_1750a`, `1750a32`, `1750a48` | 32, 48 |
|
|
84
|
+
| `TIFloat` | `ti_float`, `ti32`, `ti40` | 32, 40 |
|
|
85
|
+
| `IBMFloat` | `ibm_float`, `ibm32`, `ibm64` | 32, 64 |
|
|
86
|
+
| `DECFloat` | `dec_float`, `dec32`, `dec64` | 32, 64 |
|
|
87
|
+
| `DECFloatG` | `dec_float_g`, `dec64g` | 64 |
|
|
88
|
+
|
|
89
|
+
## C Extension
|
|
90
|
+
|
|
91
|
+
Optional NumPy C ufuncs accelerate encodings that would otherwise require per-element loops
|
|
92
|
+
or multi-pass numpy operations. The C extension is compiled automatically during install and
|
|
93
|
+
falls back to pure Python when unavailable. Set `BYTEFORGE_NO_C=1` to force the pure-Python
|
|
94
|
+
path.
|
|
95
|
+
|
|
96
|
+
Encodings with C ufuncs: BCD, GrayCode (decode), MilStd1750A, TIFloat, IBMFloat,
|
|
97
|
+
DECFloat, and DECFloatG.
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
uv run pytest tests/ -v # tests
|
|
103
|
+
uv run ruff check src/ tests/ # lint
|
|
104
|
+
uv run mypy # type check
|
|
105
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Byteforge
|
|
2
|
+
|
|
3
|
+
Encode and decode numbers as unsigned integer bit patterns using NumPy arrays.
|
|
4
|
+
|
|
5
|
+
Requires Python >= 3.9 and NumPy >= 2.0.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install byteforge
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Every encoding exposes the same interface: `encode()` converts physical values to bit patterns, `decode()` converts back.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import numpy as np
|
|
19
|
+
from byteforge import TwosComplement, IEEE754, Unsigned
|
|
20
|
+
|
|
21
|
+
# Integer encodings
|
|
22
|
+
tc = TwosComplement(16)
|
|
23
|
+
dns = tc.encode(np.array([-1, 0, 127])) # array([65535, 0, 127], dtype=uint16)
|
|
24
|
+
tc.decode(dns) # array([-1, 0, 127], dtype=int16)
|
|
25
|
+
|
|
26
|
+
# Floating-point encodings
|
|
27
|
+
ieee = IEEE754(32)
|
|
28
|
+
dns = ieee.encode(np.array([3.14])) # uint32 bit pattern
|
|
29
|
+
ieee.decode(dns) # array([3.14], dtype=float32)
|
|
30
|
+
|
|
31
|
+
# Unsigned with automatic bit width
|
|
32
|
+
u = Unsigned.from_range(max_value=1000) # Unsigned(10)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Registry factory
|
|
36
|
+
|
|
37
|
+
Create encodings by name using the registry:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from byteforge import create_encoding
|
|
41
|
+
|
|
42
|
+
enc = create_encoding("twos_complement", 8)
|
|
43
|
+
enc = create_encoding("ieee32") # aliases have default bit widths
|
|
44
|
+
enc = create_encoding("linear_scaled", 16, scale_factor=0.1, offset=10.0)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Byte serialization
|
|
48
|
+
|
|
49
|
+
Convert encoded bit patterns to/from raw bytes:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
enc = Unsigned(16)
|
|
53
|
+
dns = enc.encode(np.array([256, 512]))
|
|
54
|
+
raw = enc.to_bytes(dns, byteorder="big") # uint8 array, shape (2, 2)
|
|
55
|
+
enc.decode(enc.from_bytes(raw, byteorder="big")) # roundtrip
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For non-byte-aligned widths (e.g. 12-bit), values are zero-padded in the most
|
|
59
|
+
significant bits to fill whole bytes (2 bytes for 12-bit).
|
|
60
|
+
|
|
61
|
+
## Encodings
|
|
62
|
+
|
|
63
|
+
| Encoding | Registry names | Bit widths |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `Unsigned` | `unsigned`, `u` | 1-64 |
|
|
66
|
+
| `TwosComplement` | `twos_complement`, `2c` | 2-64 |
|
|
67
|
+
| `OnesComplement` | `ones_complement`, `1c` | 2-64 |
|
|
68
|
+
| `OffsetBinary` | `offset_binary` | 1-64 |
|
|
69
|
+
| `GrayCode` | `gray_code`, `gray` | 1-64 |
|
|
70
|
+
| `BCD` | `bcd` | 4-64 (multiples of 4) |
|
|
71
|
+
| `Boolean` | `boolean` | 1 |
|
|
72
|
+
| `IEEE754` | `ieee754`, `ieee16`, `ieee32`, `ieee64` | 16, 32, 64 |
|
|
73
|
+
| `LinearScaled` | `linear_scaled` | 1-64 |
|
|
74
|
+
| `MilStd1750A` | `mil_std_1750a`, `1750a32`, `1750a48` | 32, 48 |
|
|
75
|
+
| `TIFloat` | `ti_float`, `ti32`, `ti40` | 32, 40 |
|
|
76
|
+
| `IBMFloat` | `ibm_float`, `ibm32`, `ibm64` | 32, 64 |
|
|
77
|
+
| `DECFloat` | `dec_float`, `dec32`, `dec64` | 32, 64 |
|
|
78
|
+
| `DECFloatG` | `dec_float_g`, `dec64g` | 64 |
|
|
79
|
+
|
|
80
|
+
## C Extension
|
|
81
|
+
|
|
82
|
+
Optional NumPy C ufuncs accelerate encodings that would otherwise require per-element loops
|
|
83
|
+
or multi-pass numpy operations. The C extension is compiled automatically during install and
|
|
84
|
+
falls back to pure Python when unavailable. Set `BYTEFORGE_NO_C=1` to force the pure-Python
|
|
85
|
+
path.
|
|
86
|
+
|
|
87
|
+
Encodings with C ufuncs: BCD, GrayCode (decode), MilStd1750A, TIFloat, IBMFloat,
|
|
88
|
+
DECFloat, and DECFloatG.
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
uv run pytest tests/ -v # tests
|
|
94
|
+
uv run ruff check src/ tests/ # lint
|
|
95
|
+
uv run mypy # type check
|
|
96
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "byteforge"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Encode and decode numbers as unsigned integer bit patterns"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Jonathan Olsten", email = "jolsten@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"numpy>=2.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["setuptools>=64", "setuptools-scm>=8", "numpy>=2.0"]
|
|
16
|
+
build-backend = "setuptools.build_meta"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"hypothesis>=6.141",
|
|
24
|
+
"mypy>=1.19.1",
|
|
25
|
+
"pytest>=8.4",
|
|
26
|
+
"pytest-benchmark>=5.2.3",
|
|
27
|
+
"ruff>=0.15.4",
|
|
28
|
+
"setuptools>=64",
|
|
29
|
+
"setuptools-scm>=8",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
addopts = "--benchmark-disable"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools_scm]
|
|
37
|
+
version_file = "src/byteforge/_version.py"
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
target-version = "py39"
|
|
41
|
+
src = ["src"]
|
|
42
|
+
line-length = 100
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "W", "I", "UP", "B", "SIM"]
|
|
46
|
+
ignore = ["E501"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint.isort]
|
|
49
|
+
known-first-party = ["byteforge"]
|
|
50
|
+
|
|
51
|
+
[tool.mypy]
|
|
52
|
+
python_version = "3.9"
|
|
53
|
+
warn_return_any = false
|
|
54
|
+
warn_unused_configs = true
|
|
55
|
+
disallow_untyped_defs = true
|
|
56
|
+
check_untyped_defs = true
|
|
57
|
+
packages = ["byteforge"]
|
|
58
|
+
mypy_path = "src"
|
|
59
|
+
|
|
60
|
+
[[tool.mypy.overrides]]
|
|
61
|
+
module = "byteforge._c.*"
|
|
62
|
+
ignore_missing_imports = true
|
|
63
|
+
|
|
64
|
+
[tool.cibuildwheel]
|
|
65
|
+
build = "cp39-* cp310-* cp311-* cp312-* cp313-*"
|
|
66
|
+
skip = "*-win32 *-manylinux_i686"
|
|
67
|
+
test-requires = ["pytest", "pytest-benchmark", "hypothesis"]
|
|
68
|
+
test-command = "pytest {project}/tests/"
|
byteforge-0.0.5/setup.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import numpy
|
|
2
|
+
from setuptools import Extension, setup
|
|
3
|
+
from setuptools.command.build_ext import build_ext as _build_ext
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class optional_build_ext(_build_ext):
|
|
7
|
+
"""Allow C extension build to fail gracefully."""
|
|
8
|
+
|
|
9
|
+
def run(self):
|
|
10
|
+
try:
|
|
11
|
+
_build_ext.run(self)
|
|
12
|
+
except Exception:
|
|
13
|
+
self._warn_unavailable()
|
|
14
|
+
|
|
15
|
+
def build_extension(self, ext):
|
|
16
|
+
try:
|
|
17
|
+
_build_ext.build_extension(self, ext)
|
|
18
|
+
except Exception:
|
|
19
|
+
self._warn_unavailable()
|
|
20
|
+
|
|
21
|
+
def _warn_unavailable(self):
|
|
22
|
+
print("*" * 70)
|
|
23
|
+
print("WARNING: byteforge C extension could not be compiled.")
|
|
24
|
+
print("Falling back to pure Python (slower).")
|
|
25
|
+
print("*" * 70)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
setup(
|
|
29
|
+
cmdclass={"build_ext": optional_build_ext},
|
|
30
|
+
ext_modules=[
|
|
31
|
+
Extension(
|
|
32
|
+
"byteforge._c.ufunc",
|
|
33
|
+
sources=["src/byteforge/_c/ufunc.c"],
|
|
34
|
+
include_dirs=[numpy.get_include(), "src/byteforge/_c"],
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from ._version import __version__, __version_tuple__
|
|
3
|
+
except ImportError:
|
|
4
|
+
__version__ = "0.0.0"
|
|
5
|
+
__version_tuple__ = (0, 0, 0)
|
|
6
|
+
|
|
7
|
+
from ._base import Encoding
|
|
8
|
+
from ._registry import create_encoding, register
|
|
9
|
+
from .bcd import BCD
|
|
10
|
+
from .boolean import Boolean
|
|
11
|
+
from .dec_float import DECFloat, DECFloatG
|
|
12
|
+
from .gray_code import GrayCode
|
|
13
|
+
from .ibm_float import IBMFloat
|
|
14
|
+
from .ieee754 import IEEE754
|
|
15
|
+
from .linear_scaled import LinearScaled
|
|
16
|
+
from .mil_std_1750a import MilStd1750A
|
|
17
|
+
from .offset_binary import OffsetBinary
|
|
18
|
+
from .ones_complement import OnesComplement
|
|
19
|
+
from .ti_float import TIFloat
|
|
20
|
+
from .twos_complement import TwosComplement
|
|
21
|
+
from .unsigned import Unsigned
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"__version__",
|
|
25
|
+
"Encoding",
|
|
26
|
+
"register",
|
|
27
|
+
"create_encoding",
|
|
28
|
+
"BCD",
|
|
29
|
+
"Boolean",
|
|
30
|
+
"DECFloat",
|
|
31
|
+
"DECFloatG",
|
|
32
|
+
"GrayCode",
|
|
33
|
+
"IBMFloat",
|
|
34
|
+
"IEEE754",
|
|
35
|
+
"LinearScaled",
|
|
36
|
+
"MilStd1750A",
|
|
37
|
+
"OffsetBinary",
|
|
38
|
+
"OnesComplement",
|
|
39
|
+
"TIFloat",
|
|
40
|
+
"TwosComplement",
|
|
41
|
+
"Unsigned",
|
|
42
|
+
]
|