qf_math is a lightweight library for fast approximate math on IEEE-754 float — built for ARM Cortex-M, RISC-V, Xtensa/ESP32, and any 32-bit target. One translation unit, no heap, no dependencies, no FPU required (but benefits from one). Drop it into firmware for predictable-cost trig, log, exp, sqrt, hypot, waveforms, and ADSR — without pulling in full libm.
Need fixed point?: fr_math is the fixed-point (pure integer) cousin with the same API.
Docs: qf_math docs html pages for this library with algorithms, example code, metrics.
Depending on whether the target has hardware floating point support different chocies may be appropriate.
| qf_math (this library) | fr_math | |
|---|---|---|
| Representation | IEEE-754 float (32-bit) |
Caller-selectable fixed-radix int32_t (e.g. s15.16, s19.12, s23.8) |
| Best on | Cortex-M4F/M7, ESP32, RP2040/RP2350 with FPU | Cortex-M0/M3, 8/16-bit MCUs, no-FPU targets |
| Soft-float cost | High (compiler inserts sw multiply/add) | Zero (integer ALU only) |
| Dynamic range | ~1e-38 to ~3e+38 | Determined by radix (e.g. R=16: ±32K; R=12: ±512K; R=8: ±8M) |
| Typical accuracy | < 0.002% FS (trig), < 0.001% rel (log/exp) | < 0.008% FS (trig), < 0.4% rel (log/exp) |
| Code size | ~10 KB (full), ~9 KB (lean) | ~10 KB (full), ~5 KB (lean) |
Peak error from identical benchmark test suites. Both libraries use the approximation strategies (BAM phase, sub-step interpolation, poly interpolation, etc) with differences from quantization and dynamic range tradeoffs.
| Function | Metric | libm | qf_math | fr_math |
|---|---|---|---|---|
| sin (rad) | %FS | 0 | 0.0019 | 0.0078 |
| cos (rad) | %FS | 0 | 0.0019 | 0.0087 |
| tan (rad) | abs | 0 | 0.30 | 0.051 |
| asin | abs rad | 0 | 0.00047 | 0.00036 |
| acos | abs rad | 0 | 0.00047 | 0.00036 |
| atan2 | abs rad | 0 | 0.0013 | 0.00094 |
| sqrt | rel % | 0 | 0.00047 | 1.19 |
| log2 | rel % | 0 | 0.0016 | 0.32 |
| exp | rel % | 0 | 0.00034 | 0.57 |
| pow | rel % | 0 | 0.00038 | 0.087 |
| hypot | rel % | 0 | 0.00047 | 0.000007 |
%FS = % of full-scale (±1 output for trig). rel % = relative error vs
doublereference. abs rad = absolute radians. Bold = closer to libm.libmcolumn is the*freference (effectively 0 error at this precision).
Ratio of libm wall-clock time to library time for the same loop. > 1.0 = faster than sinf/sqrtf/etc. on that platform. ESP32-S3 has a single-precision FPU, so both qf_math and libm use hardware float — the advantage comes from simpler approximations with fewer branches.
| Function | qf_math | fr_math | Notes |
|---|---|---|---|
| sin (rad) | 4.30× | 1.32× | qf_math: table + lerp; fr_math: integer table via float bridge |
| cos (rad) | 4.39× | 0.90× | |
| tan (rad) | 3.56× | 1.31× | |
| asin | 1.10× | 0.77× | |
| acos | 1.11× | 0.77× | |
| atan2 | 1.41× | 0.70× | |
| sqrt | 1.07× | 0.08× | libm sqrtf is hard to beat (HW instruction on many cores) |
| exp | 2.63× | 1.88× | |
| ln | 2.20× | 0.92× | |
| hypot | 2.51× | 0.21× | fr_math hypot goes through float bridge overhead |
fr_math numbers include float↔fixed bridge overhead — native
s32/fix16_tcalls in a pure-integer pipeline are faster. Seecompare/BENCHMARK_CROSSPLATFORM.mdfor full matrices including libfixmath, FastTrig, and ESP-DSP.
#include "qf_math.h"
qf y = qf_sin(1.0f); /* radians */
qf len = qf_hypot(dx, dy);
qf dB = 20.0f * qf_log10(v / ref);| Language | C99 (float API, qf typedef), plus optional C++ header wrapper |
| Dependencies | None at compile time for the library itself |
| License | BSD-2-Clause — see LICENSE.txt |
Version macros live in qf_math.h: QF_MATH_VERSION / QF_MATH_VERSION_HEX. When you bump the version string, update the Version badge at the top of this README to match.
For releases, prefer the version/release helpers so manifests, docs, badges, pages, and generated reports stay in sync:
python3 tools/qf_version.py show --format markdown
python3 tools/qf_version.py update 1.0.2
make make-release VERSION=1.0.2
make make-release RELEASE_ARGS="--dry-run --skip-docker --skip-peer-tests"- Deterministic building blocks for motor control, graphics-lite, sensor fusion, LED/audio synthesis, and telemetry pipelines.
- Tight code size: one translation unit with shared lookup tables (see
make size). - Explicit trade-offs: worst-case errors are documented in headers (e.g. trig ~3e-5 class behavior vs libm doubles).
- No heap, minimal stack depth in normal use, integer-friendly helpers (
QF_*macros, BAM phase, fixed-radix bridges).
Public surface is declared in src/qf_math.h:
- Macros: clamps, interpolation, deg/rad/BAM, radix bridges (
QF_TO_FR, …). - Trig:
qf_sin,qf_cos,qf_tan, BAM-native variants, inverse trig. - Log/exp/pow:
qf_log2,qf_ln,qf_log10,qf_pow2,qf_exp,qf_pow10,qf_pow. - Length:
qf_sqrt,qf_hypot,qf_hypot_fast2,qf_hypot_fast8(piecewise-linear magnitude). - Audio-ish: LFSR noise, PWM/square/saw/triangle waves,
qf_adsr_*envelope.
Domain violations (sqrt of negative, log of non-positive) return QF_DOMAIN_ERROR.
Full reference: docs/API.md — Algorithms: docs/ALGORITHMS.md — Float math tradeoffs (libm vs qf_math vs fixed-point vs DSP)
src/ qf_math.c, qf_math.h/.hpp — drop-in C library + C++ wrapper
test/ qf_math_test.c — correctness vs libm on host
docs/ Markdown documentation (algorithms, API, fr_math, integration)
pages/ GitHub Pages site (compact HTML + CSS; deploy via Actions)
examples/ optional demos — ESP-IDF, LilyGO T-Display-S3, Raspberry Pi Pico 2
docker/ multi-arch toolchain image + cross size report
compare/ matrices vs other math libs + benchmark harness (see below)
tools/ extra shell helpers & qf_math vs libm micro-benchmark
build/ compiled binaries, CMake outputs, cloned deps (gitignored)
Requires a normal UNIX toolchain (gcc or clang) and -lm for tests and benchmarks only (references use sinf, logf, …).
make coverage-check (also run in CI) rebuilds tests with gcov and fails unless every executable line in src/qf_math.c is covered. Use make coverage for the same build plus a human-readable gcov summary without enforcing 100%.
make help # targets
make lib # build/qf_math.o
make test # build & run unit tests
make bench # qf_math vs libm speed/accuracy (single TU bench)
make size # object size report
make coverage # gcov summary (optional gcov install; non-failing)
make coverage-check # CI-style gate: 100% lines covered in src/qf_math.c
make cleanLike fr_math, this repo ships a docker/ image (Ubuntu + common 32/64-bit cross-compilers + Espressif Xtensa) to compile src/qf_math.c for several CPUs and emit the source-of-truth size matrix (build/docker_sizes.csv).
make docker-sizes # CSV + compact compare/README.md summary
make docker-sizes-rebuild # rebuild image, then CSV + summaryDetails: docker/README.md.
Documentation tables live under compare/ (LIBRARIES.md for platforms/representation/footprint guidance, FUNCTION_MATRIX.md for symbol coverage vs libfixmath, fr_math, CMSIS-DSP, libm, …).
| Target | What it does |
|---|---|
make compare-deps |
Shallow-clones libfixmath (MIT) and fr_math into build/compare/third_party/. |
make compare |
Builds build/compare/benchmark_suite — accuracy + wall-clock for qf_math, libm, libfixmath (float bridge), fr_math (fixed-point bridge), and a Taylor poly baseline. |
make compare-github-report |
Regenerates compare/BENCHMARK_REPORT.md for GitHub. |
make mcu-benchmark-snapshot |
Flash MCU bench and rewrite compare/MCU_BENCHMARK_SNAPSHOT*.md via UART. |
make benchmark-crossplatform |
Rewrite compare/BENCHMARK_CROSSPLATFORM.md — Host + MCU ratios from committed snapshots. |
On silicon: examples/lilygo_t_display_s3_bench (PlatformIO), examples/esp32s3_benchmark (ESP-IDF), or examples/pico2_benchmark (Arduino, Pico 2 ARM/RISC-V) run the same benchmark_core.c loops on real hardware.
Use library.json at the repo root: add this folder as a local library or publish following PlatformIO library format. Headers resolve from src/ (#include "qf_math.h" for C, #include "qf_math.hpp" for C++).
idf_component.yml registers the component for the ESP-IDF Component Manager. CMakeLists.txt wraps idf_component_register for src/qf_math.c with include path src/.
Full guide: docs/INTEGRATION.md
| File | Purpose |
|---|---|
docs/ |
Markdown documentation — algorithms, API reference, fr_math relationship, integration guide |
pages/ |
GitHub Pages site (live at https://deftio.github.io/qf_math/) |
compare/ |
Compare harness docs, benchmark reports, cross-platform matrices |
llms.txt |
Compact index for LLM tooling / crawling |
agents.md |
Conventions for coding agents working in this repo |
- Prefer extending tests in
test/qf_math_test.cwhen adjusting numerical behavior. - Keep binaries and fetched trees under
build/only. - Match existing comment and license style in touched files.
Author: M. A. Chatterjee (deftio at deftio dot com). See LICENSE.txt for full BSD-2-Clause text.
Third-party comparison tooling pulls libfixmath (MIT) and fr_math (BSD-2-Clause); neither is bundled in git—they populate only under build/compare/third_party/ when you run comparison targets.