Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,27 @@ jobs:
body_path: /tmp/release_notes.md
draft: false
prerelease: false

publish-pio:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install PlatformIO
run: pip install platformio
- name: Publish to PlatformIO Registry
env:
PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }}
run: pio pkg publish . --no-interactive

publish-espressif:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install compote (ESP Component Manager)
run: pip install idf-component-manager
- name: Publish to Espressif Component Registry
env:
IDF_COMPONENT_API_TOKEN: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
run: compote component upload --name fr_math --namespace deftio
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ htmlcov/
.idea/
*.sublime-*

# Claude Code local files
CLAUDE.local.md
# Claude (Anthropic) project-local files — not part of the library
CLAUDE*.md
.claude/

# OS files
Expand Down
190 changes: 102 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,98 +1,104 @@
[![License](https://img.shields.io/badge/License-BSD%202--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause)
[![CI](https://github.com/deftio/fr_math/actions/workflows/ci.yml/badge.svg)](https://github.com/deftio/fr_math/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg)](#building-and-testing)
[![Coverage](https://img.shields.io/badge/coverage-96%25-brightgreen.svg)](#building-and-testing)
[![Docs](https://img.shields.io/badge/docs-online-blue.svg)](https://deftio.github.io/fr_math/)
[![Version](https://img.shields.io/badge/version-2.0.7-blue.svg)](release_notes.md)
[![Version](https://img.shields.io/badge/version-2.0.8-blue.svg)](release_notes.md)

[![PlatformIO](https://img.shields.io/badge/PlatformIO-library-teal.svg)](https://registry.platformio.org/libraries/deftio/fr_math)
[![Arduino](https://img.shields.io/badge/Arduino-library-teal.svg)](https://github.com/deftio/fr_math)
[![ESP Component](https://img.shields.io/badge/ESP--IDF-component-teal.svg)](https://components.espressif.com/components/deftio/fr_math)


# FR_Math: A C Language Fixed-Point Math Library for Embedded Systems

FR_Math is a compact, integer-only fixed-point math library built for
systems where floating point is too slow, too big, or unavailable. Designed for embedded targets ranging from
legacy 16 MHz 68k processors to modern Cortex-M and RISC-V cores, it
provides a full suite of math primitives — trigonometry, logarithms,
roots, transforms, and signal generators — while remaining
deterministic, portable, and small. Unlike traditional fixed-point
libraries, FR_Math lets the caller choose the binary point per
operation, trading precision and range explicitly instead of locking
into a single format. Pure C (C99/C11/C17) with an optional C++
2D-transform wrapper. Compiles under Arduino. Zero dependencies
beyond `<stdint.h>`.
See: **[Documentation & Guide](https://deftio.github.io/fr_math/)** — for API reference, examples, fixed-point primer, build instructions.


**FR_Math** is a compact, integer-only fixed-point math library built for systems where floating point is too slow, too big, or unavailable. Designed for embedded targets ranging from legacy 16 MHz 68k processors to modern Cortex-M and RISC-V cores, it provides a full suite of math primitives — trigonometry, logarithms, roots, transforms, and signal generators — while remaining deterministic, portable, and small.

Unlike most fixed-point libraries, FR_Math lets the caller choose the binary point (radix) per operation, trading precision and range explicitly instead of locking into a single format. Pure C with C++ wrappers — compiles cleanly under C99, C11, C17, C++11 and later.
Compiles under Arduino, PlatformIO, Espressif, many older embedded targets.
Zero dependencies beyond `<stdint.h>`.


### Measured accuracy

Errors below are measured at Q16.16 (s15.16). All functions accept any
radix — Q16.16 is just the reference point for the table.
Percent errors skip expected values near zero (|expected| < 0.01).

At other radixes (3-bit, 24-bit, etc.) accuracy will differ due to the
number of fractional bits available. All functions support radix 0 to 30.

<!-- ACCURACY_TABLE_START -->
| Function | Max err (%) | Avg err (%) | Note |
|---|---:|---:|---|
| sin / cos | 0.7169 | 0.0100 | 65536-pt sweep + specials |
| tan | 0.7118 | 0.0162 | 65536-pt sweep (skip poles) |
| asin / acos | 0.7025 | 0.0105 | 65536-pt; sqrt approx near boundary |
| atan2 | 0.4953 | 0.0268 | 65536x5 radii; asin/acos+hypot_fast8 |
| atan | 0.2985 | 0.0159 | 20001-pt sweep [-10,10]; via FR_atan2 |
| sqrt | 0.0003 | 0.0000 | Round-to-nearest |
| log2 | 0.2479 | 0.0045 | 65-entry mantissa table |
| pow2 | 0.1373 | 0.0057 | 65-entry fraction table |
| ln, log10 | 0.0015 | 0.0004 | Via FR_MULK28 from log2 |
| exp | 0.0719 | 0.0051 | FR_MULK28 + FR_pow2 |
| exp_fast | 0.0719 | 0.0064 | Shift-only scaling |
| pow10 | 0.1163 | 0.0075 | FR_MULK28 + FR_pow2 |
| pow10_fast | 0.1163 | 0.0100 | Shift-only scaling |
| hypot (exact) | 0.0001 | 0.0000 | 64-bit intermediate |
| hypot_fast8 (8-seg) | 0.0977 | 0.0508 | Shift-only, no multiply |
number of fractional bits available.

<!-- ACCURACY_TABLE_START -->
| Function | Max err (%)*| Avg err (%) | Note |
|---|---:|---:|---|
| sin/cos (BAM) | 0.1526 | 0.0030 | very fast binary angle trig |
| sin/cos (deg) | 0.1526 | 0.0029 | degree input trig fns |
| sin/cos (rad) | 0.1828 | 0.0033 | radian (traditional) trig |
| tan (BAM) | 0.5823 | 0.0008 | binary angle tangent; ±maxint at poles |
| tan (deg) | 0.5311 | 0.0008 | degree input tangent; saturated at poles |
| tan (rad) | 0.0386 | 0.0001 | radian (traditional) tangent |
| asin / acos | 0.7771 | 0.0280 | reverse trig, radian output |
| atan2 | 0.2564 | 0.0237 | reverse tangent, always safe |
| atan | 0.2425 | 0.0155 | reverse tangent, accepts up to maxint |
| sqrt | 0.0000 | 0.0000 | Round-to-nearest |
| log2 | 0.0116 | 0.0016 | shift/add only for speed |
| pow2 | 0.0018 | 0.0004 | shift/add only for speed |
| ln, log10 | 0.0004 | 0.0000 | shift/add only for speed |
| exp | 0.0003 | 0.0000 | shift/add only for speed |
| exp_fast | 0.0009 | 0.0001 | Shift-only scaling |
| pow10 | 0.0005 | 0.0000 | shift/add only for speed |
| pow10_fast | 0.0022 | 0.0002 | Shift-only scaling |
| hypot (exact) | 0.0000 | 0.0000 | Uses 64-bit intermediate |
| hypot_fast8 (8-seg) | 0.0915 | 0.0320 | Shift-only, no multiply |

*Relative error; reference clamped to 1% of full-scale output.
<!-- ACCURACY_TABLE_END -->

### What's in the box

| Area | Functions |
|---|---|
| Arithmetic | `FR_ADD`, `FR_SUB`, `FR_DIV`, `FR_DIV32`, `FR_MOD`, `FR_FixMuls`, `FR_FixMulSat`, `FR_CHRDX` |
| Utility | `FR_MIN`, `FR_MAX`, `FR_CLAMP`, `FR_ABS`, `FR_SGN` |
| Trig (integer deg) | `FR_Sin`, `FR_Cos`, `FR_Tan`, `FR_SinI`, `FR_CosI`, `FR_TanI` |
| Trig (radian/BAM) | `fr_sin`, `fr_cos`, `fr_tan`, `fr_sin_bam`, `fr_cos_bam`, `fr_sin_deg`, `fr_cos_deg` |
| Inverse trig | `FR_atan`, `FR_atan2`, `FR_asin`, `FR_acos` |
| Log / exp | `FR_log2`, `FR_ln`, `FR_log10`, `FR_pow2`, `FR_EXP`, `FR_POW10`, `FR_EXP_FAST`, `FR_POW10_FAST`, `FR_MULK28` |
| Roots | `FR_sqrt`, `FR_hypot`, `FR_hypot_fast8` |
| Wave generators | `fr_wave_sqr`, `fr_wave_pwm`, `fr_wave_tri`, `fr_wave_saw`, `fr_wave_tri_morph`, `fr_wave_noise` |
| Envelope | `fr_adsr_init`, `fr_adsr_trigger`, `fr_adsr_release`, `fr_adsr_step` |
| 2D transforms | `FR_Matrix2D_CPT` (mul, add, sub, det, inv, setrotate, XFormPtI, XFormPtI16) |
| Formatted output | `FR_printNumD`, `FR_printNumF`, `FR_printNumH`, `FR_numstr` |

### Library size (FR_math.c only, `-Os`)

Compiled object code sizes on select platforms (static test build). Your
sizes may vary depending on optimization and linker settings. Sizes
include all code and internal tables; everything is ROMable.
| --- | --- |
| Arithmetic | FR_ADD, FR_SUB, FR_DIV, FR_DIV32, FR_MOD, FR_FixMuls, FR_FixMulSat, FR_CHRDX |
| Utility | FR_MIN, FR_MAX, FR_CLAMP, FR_ABS, FR_SGN |
| Trig (degree) | fr_sin_deg, fr_cos_deg, fr_tan_deg, FR_SinI, FR_CosI, FR_TanI |
| Trig (radian/BAM) | fr_sin, fr_cos, fr_tan, fr_sin_bam, fr_cos_bam, fr_tan_bam |
| Inverse trig | FR_atan, FR_atan2, FR_asin, FR_acos |
| Log / exp | FR_log2, FR_ln, FR_log10, FR_pow2, FR_EXP, FR_POW10, FR_EXP_FAST, FR_POW10_FAST, FR_MULK28 |
| Roots | FR_sqrt, FR_hypot, FR_hypot_fast8 |
| Wave generators | fr_wave_sqr, fr_wave_pwm, fr_wave_tri, fr_wave_saw, fr_wave_tri_morph, fr_wave_noise |
| Envelope | fr_adsr_init, fr_adsr_trigger, fr_adsr_release, fr_adsr_step |
| 2D transforms | FR_Matrix2D_CPT (mul, add, sub, det, inv, setrotate, XFormPtI, XFormPtI16) |
| Formatted output | FR_printNumD, FR_printNumF, FR_printNumH, FR_numstr |

### Compiled library size (FR_math.c only, `-Os`)

.text section sizes, all code + internal tables, ROMable. Sorted 8-bit → 64-bit.

<!-- SIZE_TABLE_START -->
| Target | Core | Full |
|--------|-----:|-----:|
| RP2040 (Cortex-M0+) | 2.6 KB | 4.2 KB |
| STM32 (Cortex-M4) | 2.6 KB | 4.2 KB |
| RISC-V 32 (rv32imac) | 3.0 KB | 4.7 KB |
| ESP32 (Xtensa) | 3.5 KB | 5.2 KB |
| 68k | 3.5 KB | 5.3 KB |
| x86-64 (GCC) | 3.5 KB | 5.7 KB |
| x86-32 | 4.5 KB | 6.8 KB |
| MSP430 (16-bit) | 5.9 KB | 8.9 KB |
| 68HC11 | 10.8 KB | 16.0 KB |
| AVR (ATmega328P) | 7.0 KB | 10.6 KB |
| Target | Lean | Core | Full |
| --- | ---:| ---:| ---:|
| AVR ATmega328P (8-bit) | 9.2 KB | 12.8 KB | 15.4 KB |
| 68HC11 (8-bit) | 13.3 KB | 18.4 KB | 22.6 KB |
| MSP430 (16-bit) | 7.8 KB | 10.7 KB | 12.8 KB |
| Xtensa LX7 (ESP32-S3) | 2.9 KB | 4.2 KB | 5.3 KB |
| Cortex-M4 (32-bit) | 3.3 KB | 4.4 KB | 5.5 KB |
| Cortex-M0 (32-bit) | 3.4 KB | 4.5 KB | 5.7 KB |
| RISC-V rv32 | 4.1 KB | 5.5 KB | 6.8 KB |
| Xtensa LX106 (ESP8266) | 4.2 KB | 5.8 KB | 7.3 KB |
| m68k (32-bit) | 4.4 KB | 6.2 KB | 7.8 KB |
| MIPS32 | 4.7 KB | 6.6 KB | 8.7 KB |
| x86-32 | 5.3 KB | 7.2 KB | 9.2 KB |
| RISC-V rv64 | 4.0 KB | 5.5 KB | 6.8 KB |
| x86-64 (GCC) | 4.6 KB | 6.1 KB | 8.0 KB |
| AArch64 (ARM64) | 4.8 KB | 6.6 KB | 8.7 KB |
<!-- SIZE_TABLE_END -->

Core = compiled with `-DFR_CORE_ONLY` (math only, no print, no waves).
The optional 2D module adds ~1 KB.
\* MSP430, 68HC11, and AVR are 8/16-bit — every 32-bit operation expands to multiple instructions.
See [`docker/`](docker/) for the cross-compile setup.
**Lean** (`-DFR_LEAN -DFR_NO_PRINT`): radian trig, inv trig, log/exp, sqrt.
**Core** (`-DFR_CORE_ONLY`): Lean + degree/BAM trig, log10, hypot.
**Full** (default): Core + formatted print, wave generators, ADSR envelope.
Optional C++ 2D module adds ~1 KB.
8/16-bit targets expand some 32-bit op to multiple instructions — hence the larger sizes.
See [Building & Testing](docs/building.md) for the full cross-compile setup.

### Lean build options

Expand All @@ -101,10 +107,10 @@ for ROM-constrained targets. Define them before including `FR_math.h`
(or pass `-D` on the compiler command line):

| Define | What it removes | Typical savings |
|---|---|---|
| `FR_CORE_ONLY` | Everything below (print + waves) | ~1.9 KB |
| `FR_NO_PRINT` | `FR_printNumF`, `FR_printNumD`, `FR_printNumH`, `FR_numstr` | ~1.3 KB |
| `FR_NO_WAVES` | `fr_wave_*` (6 shapes), `fr_adsr_*` (ADSR envelope), `FR_HZ2BAM_INC` | ~0.6 KB |
| --- | --- | --- |
| FR_CORE_ONLY | Everything below (print + waves) | ~1.9 KB |
| FR_NO_PRINT | FR_printNumF, FR_printNumD, FR_printNumH, FR_numstr | ~1.3 KB |
| FR_NO_WAVES | fr_wave_* (6 shapes), fr_adsr_* (ADSR envelope), FR_HZ2BAM_INC | ~0.6 KB |

`FR_CORE_ONLY` is a convenience shorthand that defines both
`FR_NO_PRINT` and `FR_NO_WAVES` in one step.
Expand All @@ -129,7 +135,7 @@ make lib # build static library
make test # run all tests (unit, TDD characterization, 2D)
```

## Quick taste
## Example

```c
#include "FR_math.h"
Expand Down Expand Up @@ -162,18 +168,23 @@ s32 two = I2FR(2, R); /* 2.0 → raw 131072 */
*
* MixedCase FR_ names are functions — they contain loops, tables, or
* multi-step algorithms where inlining would waste ROM:
* FR_Cos, FR_sqrt, FR_atan2, FR_log2, FR_pow2, FR_printNumF ...
* FR_sqrt, FR_atan2, FR_log2, FR_pow2, FR_printNumF ...
*
* lowercase fr_ names are v2 functions (radian trig, wave generators,
* ADSR envelopes):
* fr_sin, fr_cos, fr_tan, fr_wave_tri, fr_adsr_step ...
* lowercase fr_ names are v2 functions (degree/radian/BAM trig, wave
* generators, ADSR envelopes):
* fr_sin_deg, fr_cos_deg, fr_tan_deg, fr_sin, fr_cos, fr_tan,
* fr_wave_tri, fr_adsr_step ...
*
* Legacy aliases: FR_Cos, FR_Sin, FR_Tan still work — they are
* macros that map to fr_cos_deg, fr_sin_deg, fr_tan_deg. New code
* should use the fr_ names directly.
*
* Some macros wrap functions: FR_EXP(x,r) scales x then calls
* FR_pow2 — one-liner convenience, heavy lifting in the function.
*/

/* ---- Math functions ---- */
s32 c45 = FR_Cos(45, 0); /* cos(45°) = 0.7071 */
s32 c45 = fr_cos_deg(45, 0); /* cos(45°) = 0.7071 */
s32 s30 = fr_sin(FR_numstr("0.5236", R), R); /* sin(0.5236 rad) */
s32 root2 = FR_sqrt(two, R); /* sqrt(2) = 1.4142 */
s32 angle = FR_atan2(I2FR(1,R), I2FR(1,R), R); /* atan2(1,1) rad */
Expand Down Expand Up @@ -206,29 +217,32 @@ The full docs ship in two forms — pick whichever fits how you read.
**Terminal / editor (plain markdown):**

- [docs/README.md](docs/README.md) — same content as plain markdown.
- [getting-started.md](docs/getting-started.md) | [fixed-point-primer.md](docs/fixed-point-primer.md) | [api-reference.md](docs/api-reference.md)
- [examples.md](docs/examples.md) | [building.md](docs/building.md) | [releases.md](docs/releases.md)
- [getting-started.md](docs/getting-started.md) | [fixed-point-primer.md](docs/fixed-point-primer.md) | [api-reference.md](docs/api-reference.md)
- [examples.md](docs/examples.md) | [building.md](docs/building.md) | [releases.md](docs/releases.md)

## History

FR_Math has been in service since 2000, originally built for graphics
transforms on 16 MHz 68k Palm Pilots. It shipped inside Trumpetsoft's
*Inkstorm* on PalmOS, then moved forward through ARM, x86, MIPS,
RISC-V, and various 8/16-bit embedded targets. v2.0.7 is the current
release with a full test suite, bit-exact numerical specification, and
CI on every push.

RISC-V, and various 8/16-bit embedded targets.
The current release now has a full test suite, numerical specification, and
CI on every push and better documentation.
## License

BSD-2-Clause — see [LICENSE.txt](LICENSE.txt).
(c) 2000-2026 M. Chatterjee

PRs and suggestions are welcome.  Please be detailed as embedded systems can involve many tradeoffs.

## For AI coding agents

- [llms.txt](llms.txt) — machine-readable API summary
- [agents.md](agents.md) — conventions, build commands, and contribution guide for coding agents

## Version

2.0.7 — see [release_notes.md](release_notes.md) for the v1 → v2
See [release_notes.md](release_notes.md) for the v1 → v2
migration guide, numerical fixes, and new functionality.

2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.7
2.0.8
22 changes: 13 additions & 9 deletions agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ Integer-only, zero dependencies, caller-selectable radix (binary point).
```
src/ Core library (this is what ships)
FR_math.h Public API — all macros, function declarations, constants
FR_math.c All function implementations
FR_math.c All function implementations (trig tables inlined)
FR_defs.h Type aliases (s8, s16, s32, u8, u16, u32)
FR_trig_table.h Precomputed sine table
FR_math_2D.h/.cpp Optional C++ 2D transform class

tests/ Test suite (7 programs, run via `make test`)
examples/ Arduino .ino sketches + POSIX example
docs/ Markdown documentation
pages/ HTML documentation (mirrors docs/)
scripts/ Build, release, version sync helpers
scripts/ Build, release, version sync, size report helpers
tools/ Coefficient generators (Python, C++)
dev/ Development notes and planning (not shipped)
```
Expand All @@ -32,8 +31,10 @@ dev/ Development notes and planning (not shipped)

```bash
make lib # compile library objects
make test # run all 7 test suites (27+ tests)
make examples # build POSIX example
make test # run full test suite (99% line coverage)
make examples # build example programs
make size-report # cross-compile size report (Docker)
make size-update # size report + patch doc files
make clean # remove build artifacts
```

Expand Down Expand Up @@ -86,23 +87,26 @@ Versioned files (all synced automatically):

1. Bump `FR_MATH_VERSION_HEX` in `src/FR_math.h`
2. Run `./scripts/sync_version.sh`
3. Run `./tools/make_release.sh` (full validation gate)
4. Verify `llms.txt` and `agents.md` are current with any API changes
5. Commit, tag, push
3. Run `./scripts/crossbuild_sizes.sh --update` (rebuild size tables)
4. Run `./scripts/accuracy_report.sh --update` (rebuild accuracy tables)
5. Run `./tools/make_release.sh` (full validation gate)
6. Verify `llms.txt` and `agents.md` are current with any API changes
7. Commit, tag, push

## Lean build options

Define before including `FR_math.h` to exclude optional subsystems:

| Define | Removes | Savings |
|---|---|---|
| `FR_CORE_ONLY` | Print + waves (shorthand for both below) | ~1.9 KB |
| `FR_NO_PRINT` | `FR_printNumF/D/H`, `FR_numstr` | ~1.3 KB |
| `FR_NO_WAVES` | `fr_wave_*`, `fr_adsr_*`, `FR_HZ2BAM_INC` | ~0.6 KB |

## Platform targets

The library compiles on: AVR (Arduino), ARM Cortex-M0/M4, ESP32,
RISC-V, x86/x64, MSP430, 68k, 8051. Code is 4–8 KB at `-Os` on
RISC-V, x86/x64, MSP430, m68k, PowerPC, MIPS32, 68HC11. Code is 3–9 KB at `-Os` on
32-bit targets.

## Library publishing
Expand Down
Loading
Loading