diff --git a/README.md b/README.md
index 952eaac..f82861b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
[](https://opensource.org/licenses/BSD-2-Clause)
[](https://github.com/deftio/fr_math/actions/workflows/ci.yml)
-[](#building-and-testing)
+[](#building-and-testing)
[](https://deftio.github.io/fr_math/)
-[](release_notes.md)
+[](release_notes.md)
[](https://github.com/deftio/fr_math)
[](https://registry.platformio.org/libraries/deftio/fr_math)
@@ -30,30 +30,30 @@ radix — Q16.16 is just the reference point for the table.
At other radixes (3-bit, 24-bit, etc.) accuracy will differ due to the
number of fractional bits available.
-
-| 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.
+
+| 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.
### What's in the box
diff --git a/VERSION b/VERSION
index 815e68d..09843e3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.8
+2.0.9
diff --git a/agents.md b/agents.md
index 30128db..32339ba 100644
--- a/agents.md
+++ b/agents.md
@@ -18,7 +18,7 @@ src/ Core library (this is what ships)
FR_defs.h Type aliases (s8, s16, s32, u8, u16, u32)
FR_math_2D.h/.cpp Optional C++ 2D transform class
-tests/ Test suite (7 programs, run via `make test`)
+tests/ Test suite (8 programs, run via `make test`)
examples/ Arduino .ino sketches + POSIX example
docs/ Markdown documentation
pages/ HTML documentation (mirrors docs/)
@@ -31,7 +31,7 @@ dev/ Development notes and planning (not shipped)
```bash
make lib # compile library objects
-make test # run full test suite (99% line coverage)
+make test # run full test suite (98% line coverage, 100% of reachable branches)
make examples # build example programs
make size-report # cross-compile size report (Docker)
make size-update # size report + patch doc files
@@ -126,3 +126,9 @@ FR_Math is published to multiple package registries:
- Don't change the `extern "C"` wrapping in `FR_math.h`
- Don't hardcode a radix inside library functions — always parameterize
- Don't add `#include` dependencies beyond ``
+- Don't change the Arduino `u8`/`u16` typedef block in `FR_defs.h` to use
+ `uint8_t`/`uint16_t` — in Arduino C++ builds it must use
+ `unsigned char`/`unsigned short` verbatim so it stays typedef-identical
+ to USBAPI.h on AVR/SAM/SAMD cores while still defining the types on
+ cores without USBAPI.h (ESP32, RP2040, ...). See GitHub issue #11 and
+ `make test-arduino-compat`.
diff --git a/docs/building.md b/docs/building.md
index db8d192..20c435f 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -91,7 +91,7 @@ See `release_management.md` for the full step-by-step reference.
## The test suite
-Tests live under `tests/` and are split into seven
+Tests live under `tests/` and are split into eight
binaries to keep compile times low:
| Binary | What it checks |
@@ -103,9 +103,13 @@ binaries to keep compile times low:
| `test_full` | Full-coverage dark-corner cases and round-trips. |
| `test_2d_complete` | Extended 2D: matrix composition, inverse, point transforms. |
| `test_tdd` | Characterization tests pinned to bit-exact reference values. |
+| `test_arduino_compat` | Arduino build compatibility: compiles the headers as ESP32-style and AVR/USBAPI-style cores would (both include orders), plus type-size checks (issue #11). |
-The suite covers **99%** of the library source.
-Every public symbol is exercised at least once.
+The suite covers **98%** of the library source lines and every
+mathematically reachable branch. The remainder is defensive code
+(clamps and guards) that no input can trigger, kept in place as
+insurance against future edits. Every public symbol is exercised
+at least once.
### Running a single binary
@@ -258,8 +262,17 @@ arduino-cli compile --fqbn arduino:avr:uno examples/arduino_smoke
arduino-cli compile --fqbn arduino:avr:uno examples/basic-math
arduino-cli compile --fqbn arduino:avr:uno examples/trig-functions
arduino-cli compile --fqbn arduino:avr:uno examples/wave-generators
+
+# Non-AVR cores work the same way, e.g. ESP32:
+arduino-cli compile --fqbn esp32:esp32:esp32 examples/arduino_smoke
```
+`make test-arduino-compat` (part of `make test`) simulates the
+Arduino build environments on the host — ESP32-style cores with no
+USBAPI.h typedefs and AVR-style cores that predefine `u8`/`u16`,
+in both include orders — so core-specific type clashes like
+issue #11 are caught without any board toolchain installed.
+
See the [code size table](#code-size-text-section-compiled-with--os) above
for exact numbers. With linker dead-code elimination, only the
functions you call are linked.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 2fefb6d..f2c1b99 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -8,8 +8,26 @@ back.
## Install
-FR_Math is a handful of C and C++ files. There is no package
-manager integration and no install step. Either:
+FR_Math has no dependencies to install — it needs nothing beyond
+``, which every C99-or-newer toolchain ships. "Installing"
+here only means getting the sources into the project, either from a
+package registry or by copying them in directly.
+
+### From a package manager
+
+- **Arduino (IDE or CLI)** — open the Library Manager and search for
+ **FR_Math**, or run `arduino-cli lib install FR_Math`. Then
+ `#include ` in the sketch. Works on all cores (AVR,
+ ESP32, RP2040, SAMD, STM32, ...).
+- **PlatformIO** — add `lib_deps = deftio/fr_math` to
+ `platformio.ini`, or run `pio pkg install --library "deftio/fr_math"`.
+- **ESP-IDF** — run `idf.py add-dependency "deftio/fr_math"` to pull
+ it from the ESP Component Registry.
+
+### Vendoring the sources
+
+FR_Math is a handful of C and C++ files, so dropping it directly into
+a project works just as well. Either:
- Copy `src/FR_math.c`, `src/FR_math.h`,
`src/FR_defs.h` (and optionally
@@ -18,8 +36,8 @@ manager integration and no install step. Either:
- Add FR_Math as a git submodule and point the build system at
`src/`.
-There are no external dependencies beyond ``,
-which every C99-or-newer toolchain ships.
+Nothing else needs to be added to the build — no libraries to link
+(not even `libm`), no configuration step.
```bash
git clone https://github.com/deftio/fr_math.git
@@ -29,7 +47,7 @@ cd fr_math
`build.sh` wipes `build/`, rebuilds the
library, examples, and tests, and runs the full test suite. On success
-the output shows all tests passing (99% line coverage).
+the output shows all tests passing (98% line coverage, 100% of reachable branches).
## A first program
@@ -275,7 +293,7 @@ make coverage # coverage report (requires gcov)
```
Run `make test` for a full pass. With `make coverage`, line coverage of
-the library sources is about **99%**. See [Building & Testing](building.md)
+the library sources is about **98%**, with every mathematically reachable branch exercised. See [Building & Testing](building.md)
for targets, cross-compilation, and CI.
## Next steps
diff --git a/docs/releases.md b/docs/releases.md
index 734eb1b..92688ad 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -4,6 +4,17 @@ Release highlights. For the full per-symbol change log, see
[release_notes.md](https://github.com/deftio/fr_math/blob/master/release_notes.md)
in the repo.
+## v2.0.9 — 2026
+
+Arduino/ESP32 build fix and a saturating-add correctness fix. No API changes.
+
+- **Arduino `u8`/`u16` type error fixed** ([issue #11](https://github.com/deftio/fr_math/issues/11)): sketches failed to compile on ESP32 — and on any core other than AVR/SAM/SAMD — with `'u16' does not name a type`. `FR_defs.h` had skipped its typedefs in Arduino C++ builds, assuming `USBAPI.h` always provides them; it now emits them using `USBAPI.h`'s exact underlying types, which works whether or not that header is present. Thanks to [@beaka](https://github.com/beaka) for the report.
+- **`FR_FixAddSat` correctness**: the overflow check relied on signed-overflow UB and was optimized away at `-Os`, so negative overflow returned a wrapped positive value instead of `FR_OVERFLOW_NEG`. Also, `FR_FixAddSat(0, 0)` wrongly returned `FR_OVERFLOW_POS`. Both fixed.
+- **New `test_arduino_compat` suite**: compiles the headers as each Arduino core family does (with and without `USBAPI.h` typedefs, both include orders, C and C++), catching core-specific type clashes without a board toolchain.
+- **Coverage**: 98% of library source lines, every mathematically reachable branch exercised.
+
+---
+
## v2.0.8 — 2026
Tangent accuracy rewrite and trig rounding fix.
diff --git a/idf_component.yml b/idf_component.yml
index 8097972..09fdefd 100644
--- a/idf_component.yml
+++ b/idf_component.yml
@@ -1,4 +1,4 @@
-version: "2.0.8"
+version: "2.0.9"
description: "Compact fixed-point math library for embedded systems. Integer-only with caller-selectable radix. Trig, log/exp, sqrt, hypot, wave generators, ADSR, and 2D transforms. Zero dependencies."
url: "https://github.com/deftio/fr_math"
repository: "https://github.com/deftio/fr_math.git"
diff --git a/library.json b/library.json
index 17f9649..ab607a9 100644
--- a/library.json
+++ b/library.json
@@ -1,6 +1,6 @@
{
"name": "FR_Math",
- "version": "2.0.8",
+ "version": "2.0.9",
"description": "Compact fixed-point math library for embedded systems. Integer-only with caller-selectable radix. Trig, log/exp, sqrt, hypot, wave generators, ADSR, and 2D transforms in 4KB of flash. Zero dependencies.",
"keywords": [
"fixed-point",
diff --git a/library.properties b/library.properties
index 47dde32..601fc4a 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=FR_Math
-version=2.0.8
+version=2.0.9
author=M. A. Chatterjee
maintainer=M. A. Chatterjee
sentence=Compact fixed-point math library for embedded systems. 4KB flash, zero dependencies, any radix.
diff --git a/llms.txt b/llms.txt
index 5930e4d..0723213 100644
--- a/llms.txt
+++ b/llms.txt
@@ -9,7 +9,16 @@ or libraries. Pure C99, zero dependencies beyond ``.
- Repository: https://github.com/deftio/fr_math
- Documentation: https://deftio.github.io/fr_math/
- License: BSD-2-Clause
-- Version: 2.0.8
+- Version: 2.0.9
+
+## Installation
+
+- Arduino: Library Manager, search "FR_Math" (or `arduino-cli lib install FR_Math`),
+ then `#include `. All cores supported (AVR, ESP32, RP2040, SAMD, ...).
+- PlatformIO: `lib_deps = deftio/fr_math` in platformio.ini
+- ESP-IDF: `idf.py add-dependency "deftio/fr_math"`
+- Vendoring: copy `src/FR_math.c`, `src/FR_math.h`, `src/FR_defs.h`
+ (plus `src/FR_math_2D.h/.cpp` if 2D transforms are needed) into the project.
## Key concept: radix parameter
diff --git a/makefile b/makefile
index 196e127..995d380 100644
--- a/makefile
+++ b/makefile
@@ -48,6 +48,7 @@ help:
@echo " test-full Run full coverage tests"
@echo " test-2d-complete Run 2D complete coverage tests"
@echo " test-tdd Run TDD characterization tests"
+ @echo " test-arduino-compat Run Arduino environment compatibility tests"
@echo ""
@echo "Analysis targets:"
@echo " accuracy Show accuracy summary table"
@@ -127,7 +128,7 @@ run-examples: examples
# Build and run tests
.PHONY: test
-test: dirs examples test-basic test-comprehensive test-2d test-overflow test-full test-2d-complete test-tdd
+test: dirs examples test-basic test-comprehensive test-2d test-overflow test-full test-2d-complete test-tdd test-arduino-compat
.PHONY: test-tdd
test-tdd: $(BUILD_DIR)/test_tdd
@@ -140,6 +141,28 @@ $(BUILD_DIR)/test_tdd: $(TEST_DIR)/test_tdd.cpp $(SRC_DIR)/FR_math.c $(SRC_DIR)/
$(CXX) -I$(SRC_DIR) $(LIB_WARN) -Os $(TEST_FLAGS) -c $(SRC_DIR)/FR_math_2D.cpp -o $(BUILD_DIR)/test_tdd_FR_math_2D.o
$(CXX) $(CXXFLAGS) $(TEST_FLAGS) $(TEST_DIR)/test_tdd.cpp $(BUILD_DIR)/test_tdd_FR_math.o $(BUILD_DIR)/test_tdd_FR_math_2D.o $(LDFLAGS) -o $@
+# Arduino environment compatibility (issue #11): compile-matrix simulating
+# Arduino cores with and without USBAPI.h's u8/u16 typedefs, plus a runtime
+# type-size check. ARDUINO=10813 mimics the IDE-defined version macro.
+ARDUINO_SIM = -DARDUINO=10813
+.PHONY: test-arduino-compat
+test-arduino-compat: dirs
+ @echo "Running Arduino compatibility tests (issue #11)..."
+ @$(CXX) -x c++ $(CFLAGS) $(ARDUINO_SIM) -DARDUINO_ARCH_ESP32 -fsyntax-only $(TEST_DIR)/test_arduino_compat.c
+ @echo " compile C++ ESP32-style core (no USBAPI typedefs): PASS"
+ @$(CXX) -x c++ $(CFLAGS) $(ARDUINO_SIM) -DFR_TEST_USBAPI_FIRST -fsyntax-only $(TEST_DIR)/test_arduino_compat.c
+ @echo " compile C++ AVR-style core (USBAPI typedefs before): PASS"
+ @$(CXX) -x c++ $(CFLAGS) $(ARDUINO_SIM) -DFR_TEST_USBAPI_AFTER -fsyntax-only $(TEST_DIR)/test_arduino_compat.c
+ @echo " compile C++ (USBAPI typedefs after FR_math.h): PASS"
+ @$(CC) $(CFLAGS) $(ARDUINO_SIM) -fsyntax-only $(TEST_DIR)/test_arduino_compat.c
+ @echo " compile C translation unit under Arduino: PASS"
+ @$(CC) -I$(SRC_DIR) $(LIB_WARN) -Os $(ARDUINO_SIM) -fsyntax-only $(SRC_DIR)/FR_math.c
+ @$(CXX) -I$(SRC_DIR) $(LIB_WARN) -Os $(ARDUINO_SIM) -fsyntax-only $(SRC_DIR)/FR_math_2D.cpp
+ @echo " compile library sources under Arduino: PASS"
+ @$(CC) $(CFLAGS) -c $(SRC_DIR)/FR_math.c -o $(BUILD_DIR)/test_arduino_compat_FR_math.o
+ @$(CXX) -x c++ $(CXXFLAGS) $(ARDUINO_SIM) $(TEST_DIR)/test_arduino_compat.c -x none $(BUILD_DIR)/test_arduino_compat_FR_math.o $(LDFLAGS) -o $(BUILD_DIR)/test_arduino_compat
+ @./$(BUILD_DIR)/test_arduino_compat
+
.PHONY: test-basic
test-basic: $(BUILD_DIR)/fr_test
@echo "Running basic tests..."
diff --git a/pages/guide/building.html b/pages/guide/building.html
index 5ab570f..a305bcb 100644
--- a/pages/guide/building.html
+++ b/pages/guide/building.html
@@ -107,7 +107,7 @@ tools/make_release.sh
The test suite
-Tests live under tests/ and are split into seven
+
Tests live under tests/ and are split into eight
binaries to keep compile times low:
@@ -120,10 +120,14 @@ The test suite
test_full | Full-coverage dark-corner cases and round-trips. |
test_2d_complete | Extended 2D: matrix composition, inverse, point transforms. |
test_tdd | Characterization tests pinned to bit-exact reference values. |
+test_arduino_compat | Arduino build compatibility: compiles the headers as ESP32-style and AVR/USBAPI-style cores would (both include orders), plus type-size checks (issue #11). |
-The suite covers 99% of the library source.
+
The suite covers 98% of the library source lines
+and every mathematically reachable branch; the remainder is defensive
+code (clamps and guards) that no input can trigger, kept in place as
+insurance against future edits.
Every public symbol is exercised at least once.
Running a single binary
@@ -275,7 +279,17 @@ Example: Arduino
# Or try the focused examples:
arduino-cli compile --fqbn arduino:avr:uno examples/basic-math
arduino-cli compile --fqbn arduino:avr:uno examples/trig-functions
-arduino-cli compile --fqbn arduino:avr:uno examples/wave-generators
+arduino-cli compile --fqbn arduino:avr:uno examples/wave-generators
+
+# Non-AVR cores work the same way, e.g. ESP32:
+arduino-cli compile --fqbn esp32:esp32:esp32 examples/arduino_smoke
+
+make test-arduino-compat (part of make test)
+simulates the Arduino build environments on the host — ESP32-style
+cores with no USBAPI.h typedefs and AVR-style cores that predefine
+u8/u16, in both include orders — so
+core-specific type clashes like issue #11 are caught without any
+board toolchain installed.
See the code size table above for exact numbers. With linker
dead-code elimination, only the functions you call are linked.
diff --git a/pages/guide/getting-started.html b/pages/guide/getting-started.html
index e5076bf..2d58081 100644
--- a/pages/guide/getting-started.html
+++ b/pages/guide/getting-started.html
@@ -25,8 +25,35 @@ Getting Started
Install
-FR_Math is a handful of C and C++ files. There is no package
-manager integration and no install step. Either:
+FR_Math has no dependencies to install — it needs nothing
+beyond <stdint.h>, which every C99-or-newer toolchain
+ships. “Installing” here only means getting the sources into
+the project, either from a package registry or by copying them in
+directly.
+
+From a package manager
+
+FR_Math is published to the major embedded package registries:
+
+
+ - Arduino (IDE or CLI) — open the Library Manager
+ and search for FR_Math, or run
+
arduino-cli lib install FR_Math. Then
+ #include <FR_math.h> in the sketch. Works on
+ all cores (AVR, ESP32, RP2040, SAMD, STM32, ...).
+ - PlatformIO — add
+
lib_deps = deftio/fr_math to
+ platformio.ini, or run
+ pio pkg install --library "deftio/fr_math".
+ - ESP-IDF — run
+
idf.py add-dependency "deftio/fr_math" to pull it
+ from the ESP Component Registry.
+
+
+Vendoring the sources
+
+FR_Math is a handful of C and C++ files, so dropping it directly
+into a project works just as well. Either:
- Copy
src/FR_math.c, src/FR_math.h,
@@ -37,8 +64,8 @@ Install
src/.
-There are no external dependencies beyond <stdint.h>,
-which every C99-or-newer toolchain ships.
+Nothing else needs to be added to the build — no libraries to
+link (not even libm), no configuration step.
git clone https://github.com/deftio/fr_math.git
cd fr_math
@@ -46,7 +73,7 @@ Install
build.sh wipes build/, rebuilds the
library, examples, and tests, and runs the full test suite. On success
-the output shows all tests passing (99% line coverage).
+the output shows all tests passing (98% line coverage, 100% of reachable branches).
A first program
@@ -287,7 +314,7 @@ Running the test suite
make coverage # coverage report (requires gcov)
Run make test for a full pass. With make coverage,
-line coverage of the library sources is about 99%.
+line coverage of the library sources is about 98%, with every mathematically reachable branch exercised.
See Building & Testing for targets,
cross-compilation, and CI.
diff --git a/pages/index.html b/pages/index.html
index 9713405..b449193 100644
--- a/pages/index.html
+++ b/pages/index.html
@@ -49,32 +49,32 @@ Measured accuracy
Run make test-tdd to generate the TDD report
(build/test_tdd_report.md) with sweeps at radixes 8, 12, 16, and 24.
-
-
-| 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.
+
+
+| 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.
What’s in the box
diff --git a/pages/releases.html b/pages/releases.html
index 96e18bb..d96d1ee 100644
--- a/pages/releases.html
+++ b/pages/releases.html
@@ -21,6 +21,19 @@ Releases
release_notes.md
in the repo.
+v2.0.9 — 2026
+
+Arduino/ESP32 build fix and a saturating-add correctness fix. No API changes.
+
+
+ - Arduino
u8/u16 type error fixed (issue #11): sketches failed to compile on ESP32 — and on any core other than AVR/SAM/SAMD — with 'u16' does not name a type. FR_defs.h had skipped its typedefs in Arduino C++ builds, assuming USBAPI.h always provides them; it now emits them using USBAPI.h's exact underlying types, which works whether or not that header is present. Thanks to @beaka for the report.
+ FR_FixAddSat correctness: the overflow check relied on signed-overflow UB and was optimized away at -Os, so negative overflow returned a wrapped positive value instead of FR_OVERFLOW_NEG. Also, FR_FixAddSat(0, 0) wrongly returned FR_OVERFLOW_POS. Both fixed.
+ - New
test_arduino_compat suite: compiles the headers as each Arduino core family does (with and without USBAPI.h typedefs, both include orders, C and C++), catching core-specific type clashes without a board toolchain.
+ - Coverage: 98% of library source lines, every mathematically reachable branch exercised.
+
+
+
+
v2.0.8 — 2026
Tangent accuracy rewrite and trig rounding fix.
diff --git a/pages/version.json b/pages/version.json
index f81a375..a26fd82 100644
--- a/pages/version.json
+++ b/pages/version.json
@@ -1 +1 @@
-{"version":"2.0.8","hex":"0x020008"}
+{"version":"2.0.9","hex":"0x020009"}
diff --git a/release_notes.md b/release_notes.md
index 5ff9659..375e678 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -1,5 +1,82 @@
# FR_Math Release Notes
+## Version 2.0.9 (2026)
+
+Arduino/ESP32 build fix and a saturating-add correctness fix. No API
+changes — `FR_math.h` declarations are identical to 2.0.8.
+
+### Arduino: `u8`/`u16` did not name a type on non-AVR cores
+
+- **Fixed** ([issue #11](https://github.com/deftio/fr_math/issues/11)):
+ sketches failed to compile on ESP32, and on any Arduino core other
+ than AVR/SAM/SAMD, with `'u16' does not name a type`. Thanks to
+ [@beaka](https://github.com/beaka) for reporting this, on ESP32 dev
+ and ESP32-S3 boards with Arduino IDE 2.3.10.
+- Cause: `FR_defs.h` skipped its `u8`/`u16` typedefs entirely in Arduino
+ C++ builds, on the assumption that `USBAPI.h` always supplies them.
+ That holds only for the AVR/SAM/SAMD cores; ESP32, RP2040, STM32 and
+ others have no such typedefs, so the types were left undefined.
+- Fix: in Arduino C++ builds the typedefs are now emitted using
+ `USBAPI.h`'s exact underlying types (`unsigned char` /
+ `unsigned short`). Where `USBAPI.h` is present the duplicate typedef
+ is identical and legal C++; where it is absent the types are defined.
+ Both include orders work.
+
+### `FR_FixAddSat` returned wrong results
+
+- **Fixed**: `FR_FixAddSat(x, y)` computed `x + y` in signed arithmetic
+ and then tested the result for wraparound. Signed overflow is
+ undefined behavior, so optimizers were free to delete the check — at
+ `-Os`, clang did, and negative overflow returned a wrapped positive
+ value instead of `FR_OVERFLOW_NEG`. The sum is now formed in unsigned
+ arithmetic, which is well-defined.
+- **Fixed**: `FR_FixAddSat(0, 0)` returned `FR_OVERFLOW_POS` instead of
+ `0`. The positive-overflow test was `sum <= 0`; it is now `sum < 0`,
+ since a zero sum from two non-negative operands is legitimate.
+
+### Testing
+
+- New `tests/test_arduino_compat.c` and `make test-arduino-compat`
+ target (now part of `make test`, bringing the suite to eight
+ binaries). It compiles the headers the way each Arduino core family
+ does — ESP32-style with no `USBAPI.h` typedefs, AVR-style with them
+ predefined, in both include orders, as both C and C++ — plus runtime
+ type-width checks. Catches core-specific type clashes without a board
+ toolchain installed.
+- New branch-coverage tests in `tests/test_full_coverage.c` covering
+ previously unexercised paths: radian range reduction beyond 2π and
+ 4π, degree reduction beyond ±360°, `radix == 0` fast paths in
+ `fr_cos_deg`/`fr_sin_deg`, BAM pole handling in `fr_tan_deg`,
+ radix-conversion arms in `FR_acos`/`FR_asin`/`FR_atan2` at output
+ radix 14 and 20, `FR_pow2(x, 0)`, `FR_printNumF` with `radix == 0`
+ and `prec == 0`, `FR_numstr` leading `+` and >9 fractional digits,
+ `FR_hypot_fast8` at `INT32_MIN`, and the `fr_wave_noise` /
+ `fr_adsr_step` clamps.
+- Library coverage is now 98% of source lines, with every
+ mathematically reachable branch exercised. The remainder is defensive
+ code (clamps and guards) that no input can reach; it is kept in place
+ deliberately.
+
+### Documentation
+
+- **Install instructions corrected**: `docs/getting-started.md`,
+ `pages/guide/getting-started.html`, and `llms.txt` now show how to
+ obtain the library from the package registries it is published to
+ (Arduino Library Manager, PlatformIO, ESP-IDF), alongside copying the
+ sources in directly. The guide previously said FR_Math had "no
+ package manager integration", which conflated two separate things:
+ the library indeed has no *dependencies* to install, but it is and
+ was distributed through those registries — which is how the reporter
+ in issue #11 obtained it.
+- `docs/building.md` and `pages/guide/building.html`: test-suite table
+ now lists eight binaries including `test_arduino_compat`, with an
+ ESP32 `arduino-cli compile` example and a note on what the
+ compatibility matrix covers.
+- `agents.md` records the `FR_defs.h` Arduino typedef invariant in
+ "What NOT to do", so the issue #11 fix is not undone by a future
+ cleanup, and the test count is corrected to eight.
+- Coverage figures refreshed across README, the guides, and `agents.md`.
+
## Version 2.0.8 (2026)
Tangent accuracy rewrite and trig rounding fix.
diff --git a/scripts/coverage_report.sh b/scripts/coverage_report.sh
index 143d792..17ff8df 100755
--- a/scripts/coverage_report.sh
+++ b/scripts/coverage_report.sh
@@ -117,7 +117,8 @@ parse_coverage() {
local percent=0
if [ "$total_lines" -gt 0 ]; then
- percent=$((executed_lines * 100 / total_lines))
+ # round to nearest, not truncate: 97.99% must not report as 97%
+ percent=$(((executed_lines * 100 + total_lines / 2) / total_lines))
fi
echo "$executed_lines $total_lines $percent $not_executed"
@@ -171,7 +172,7 @@ total_lines=$((fr_math_total + fr_2d_total))
total_exec=$((fr_math_exec + fr_2d_exec))
if [ "$total_lines" -gt 0 ] && [ "$total_lines" != "0" ]; then
- overall_percent=$((total_exec * 100 / total_lines))
+ overall_percent=$(((total_exec * 100 + total_lines / 2) / total_lines))
else
overall_percent=0
fi
diff --git a/src/FR_defs.h b/src/FR_defs.h
index 631126e..fc12694 100644
--- a/src/FR_defs.h
+++ b/src/FR_defs.h
@@ -64,19 +64,21 @@ typedef signed long long int64_t;
#endif /* FR_NO_STDINT */
/*
- * Arduino's USBAPI.h typedefs u8 and u16 as unsigned char / unsigned short.
- * On AVR, uint8_t/uint16_t resolve to unsigned int types, which are the same
- * width but different C++ types — causing a redefinition error. Skip those
- * two typedefs when building in an Arduino environment; the Arduino-provided
- * types are the same width and work identically.
+ * Arduino's AVR/SAM/SAMD cores (USBAPI.h, pulled in by Arduino.h) typedef
+ * u8 as unsigned char and u16 as unsigned short. On AVR, uint16_t is
+ * unsigned int — the same width as unsigned short but a distinct C++
+ * type — so "typedef uint16_t u16;" would clash with USBAPI.h's typedef.
+ * Other Arduino cores (ESP32, RP2040, STM32, ...) have no USBAPI.h
+ * typedefs at all and need ours (see GitHub issue #11).
*
- * The guard checks __cplusplus too because Arduino.h (which pulls in
- * USBAPI.h) is only auto-included in .ino/.cpp translation units.
- * Plain-C files (.c) compiled by the Arduino build system need our
- * typedefs even though the ARDUINO macro is defined for them.
+ * In Arduino C++ builds we therefore typedef u8/u16 with USBAPI.h's exact
+ * underlying types: where USBAPI.h is present the duplicate typedef is
+ * identical and legal C++; where it is absent these provide the types.
+ * unsigned char / unsigned short are 8/16-bit on all Arduino targets.
*/
#if defined(ARDUINO) && defined(__cplusplus)
- /* Arduino C++ TU — USBAPI.h already provides u8 and u16 */
+typedef unsigned char u8;
+typedef unsigned short u16;
#else
typedef uint8_t u8;
typedef uint16_t u16;
diff --git a/src/FR_math.c b/src/FR_math.c
index 95809f8..1de4ae6 100644
--- a/src/FR_math.c
+++ b/src/FR_math.c
@@ -708,16 +708,20 @@ s32 FR_FixMulSat(s32 x, s32 y)
*/
s32 FR_FixAddSat(s32 x, s32 y)
{
- s32 sum = x + y;
+ /* Sum in unsigned space: signed overflow is undefined behavior, and
+ * optimizers legally delete wrap-detection checks written as x + y. */
+ s32 sum = (s32)((u32)x + (u32)y);
if (x < 0)
{
- if (y < 0)
- return (sum >= 0) ? FR_OVERFLOW_NEG : sum;
+ if (y < 0 && sum >= 0)
+ return FR_OVERFLOW_NEG;
}
else
{
- if (y >= 0)
- return (sum <= 0) ? FR_OVERFLOW_POS : sum;
+ /* nonneg + nonneg wraps into [INT32_MIN, -2]; a zero sum (0+0)
+ * is legitimate, so only sum < 0 signals overflow */
+ if (y >= 0 && sum < 0)
+ return FR_OVERFLOW_POS;
}
return sum;
}
diff --git a/src/FR_math.h b/src/FR_math.h
index a2db262..1937295 100644
--- a/src/FR_math.h
+++ b/src/FR_math.h
@@ -32,8 +32,8 @@
#ifndef __FR_Math_h__
#define __FR_Math_h__
-#define FR_MATH_VERSION "2.0.8"
-#define FR_MATH_VERSION_HEX 0x020008 /* major << 16 | minor << 8 | patch */
+#define FR_MATH_VERSION "2.0.9"
+#define FR_MATH_VERSION_HEX 0x020009 /* major << 16 | minor << 8 | patch */
#ifdef FR_CORE_ONLY
#define FR_NO_PRINT
diff --git a/src/FR_math_2D.cpp b/src/FR_math_2D.cpp
index b45ca75..1437e57 100644
--- a/src/FR_math_2D.cpp
+++ b/src/FR_math_2D.cpp
@@ -5,7 +5,7 @@
*
* @copy Copyright (C) <2001-2026>
* @author M A Chatterjee
- * @version 2.0.8 M. A. Chatterjee, cleaned up naming
+ * @version 2.0.9 M. A. Chatterjee, cleaned up naming
*
* This file contains integer math settable fixed point radix math routines for
* use on systems in which floating point is not desired or unavailable.
diff --git a/src/FR_math_2D.h b/src/FR_math_2D.h
index 3eaf7d3..2aca6c0 100644
--- a/src/FR_math_2D.h
+++ b/src/FR_math_2D.h
@@ -3,7 +3,7 @@
*
* @copy Copyright (C) <2001-2026>
* @author M A Chatterjee
- * @version 2.0.8 M. A. Chatterjee, cleaned up naming
+ * @version 2.0.9 M. A. Chatterjee, cleaned up naming
*
* This file contains integer math settable fixed point radix math routines for
* use on systems in which floating point is not desired or unavailable.
diff --git a/tests/test_arduino_compat.c b/tests/test_arduino_compat.c
new file mode 100644
index 0000000..cf39d96
--- /dev/null
+++ b/tests/test_arduino_compat.c
@@ -0,0 +1,81 @@
+/**
+ * test_arduino_compat.c - Arduino environment compatibility tests (issue #11)
+ *
+ * Verifies that FR_defs.h provides u8/u16/u32 (and friends) in every
+ * Arduino build flavor. The Makefile target test-arduino-compat compiles
+ * this file several ways to simulate the environments that matter:
+ *
+ * 1. -DARDUINO, C++ : ESP32/RP2040/STM32-style core — no
+ * USBAPI.h typedefs exist; FR_defs.h must
+ * supply u8/u16 (the issue #11 failure).
+ * 2. -DARDUINO, C++,
+ * -DFR_TEST_USBAPI_FIRST : AVR/SAM/SAMD-style core — USBAPI.h has
+ * already typedef'd u8/u16 before our
+ * header is included; must not clash.
+ * 3. -DARDUINO, C++,
+ * -DFR_TEST_USBAPI_AFTER : FR_math.h included before Arduino.h;
+ * USBAPI.h typedefs come second.
+ * 4. -DARDUINO, plain C : Arduino builds of FR_math.c; USBAPI.h
+ * never appears in C translation units.
+ * 5. no -DARDUINO, C and C++ : host builds, unchanged behavior.
+ *
+ * This file is written in the common subset of C89 and C++ so the same
+ * source compiles in every mode above.
+ */
+
+/* Simulated ArduinoCore-avr USBAPI.h typedefs (verbatim underlying types) */
+#if defined(FR_TEST_USBAPI_FIRST) && defined(__cplusplus)
+typedef unsigned char u8;
+typedef unsigned short u16;
+#endif
+
+#include "FR_math.h"
+
+#if defined(FR_TEST_USBAPI_AFTER) && defined(__cplusplus)
+typedef unsigned char u8;
+typedef unsigned short u16;
+#endif
+
+#include
+
+/* Compile-time checks (C89-compatible negative-array-size trick) */
+typedef char fr_ct_u8_is_1_byte[(sizeof(u8) == 1) ? 1 : -1];
+typedef char fr_ct_u16_is_2_bytes[(sizeof(u16) == 2) ? 1 : -1];
+typedef char fr_ct_u32_is_4_bytes[(sizeof(u32) == 4) ? 1 : -1];
+typedef char fr_ct_u64_is_8_bytes[(sizeof(u64) == 8) ? 1 : -1];
+typedef char fr_ct_u8_unsigned[((u8)-1 > 0) ? 1 : -1];
+typedef char fr_ct_u16_unsigned[((u16)-1 > 0) ? 1 : -1];
+typedef char fr_ct_u32_unsigned[((u32)-1 > 0) ? 1 : -1];
+typedef char fr_ct_s16_signed[((s16)-1 < 0) ? 1 : -1];
+typedef char fr_ct_s32_signed[((s32)-1 < 0) ? 1 : -1];
+
+/* The u16-typed public API must be declared and callable (issue #11 was a
+ failure to even parse these prototypes on ESP32). */
+static s32 use_u16_api(void)
+{
+ u16 bam = FR_DEG2BAM_I(45);
+ return fr_sin_bam(bam) + fr_cos_bam(bam);
+}
+
+int main(void)
+{
+ int fails = 0;
+
+ if (sizeof(u8) != 1 || sizeof(u16) != 2 || sizeof(u32) != 4) {
+ printf(" type sizes: FAIL\n");
+ fails++;
+ } else {
+ printf(" type sizes (u8=1, u16=2, u32=4): PASS\n");
+ }
+
+ if (use_u16_api() == 0) {
+ /* sin(45)+cos(45) in s0.15 is decidedly nonzero */
+ printf(" u16 BAM API callable: FAIL\n");
+ fails++;
+ } else {
+ printf(" u16 BAM API callable: PASS\n");
+ }
+
+ printf("Arduino compat: %s\n", fails ? "FAIL" : "ALL PASS");
+ return fails;
+}
diff --git a/tests/test_full_coverage.c b/tests/test_full_coverage.c
index 36c00f0..e1f6666 100644
--- a/tests/test_full_coverage.c
+++ b/tests/test_full_coverage.c
@@ -1079,6 +1079,144 @@ int test_constants_complete() {
return TEST_PASS;
}
+/*=======================================================
+ * Branch-coverage completion tests
+ * Each test targets a specific previously never-taken
+ * branch in FR_math.c, identified by gcov -b analysis.
+ *=======================================================*/
+
+static int near_s32(s32 a, s32 b, s32 tol) {
+ s32 d = a - b;
+ if (d < 0) d = -d;
+ return d <= tol;
+}
+
+static int cov_sink(char c) { (void)c; return 0; }
+
+/* normalize_to_r16 radix>16 arm; reduce_to_2pi both reduction arms */
+int test_cov_radian_reduction() {
+ /* radix 18 input: sin(pi/6) = 0.5 */
+ if (!near_s32(fr_sin(137258, 18), 32768, 80)) return TEST_FAIL;
+ /* r > 4*pi: sin(5*pi) = 0 */
+ if (!near_s32(fr_sin(1029435, 16), 0, 16)) return TEST_FAIL;
+ /* 2*pi < r <= 4*pi: sin(3*pi) = 0 */
+ if (!near_s32(fr_sin(617661, 16), 0, 16)) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* fr_deg_to_bam |d| >= 360-degree reduction, both signs */
+int test_cov_deg_to_bam_reduction() {
+ if (fr_deg_to_bam(23625728, 16) != fr_deg_to_bam(32768, 16))
+ return TEST_FAIL; /* 360.5 deg == 0.5 deg */
+ if (fr_deg_to_bam(-23625728, 16) != fr_deg_to_bam(-32768, 16))
+ return TEST_FAIL; /* -360.5 deg == -0.5 deg */
+ return TEST_PASS;
+}
+
+/* fr_cos_deg/fr_sin_deg radix==0 fast path; >=360deg reductions */
+int test_cov_deg_trig_paths() {
+ if (!near_s32(fr_cos_deg(45, 0), 46341, 60)) return TEST_FAIL;
+ if (!near_s32(fr_sin_deg(45, 0), 46341, 60)) return TEST_FAIL;
+ /* 400.5 deg reduces to 40.5 deg */
+ if (fr_cos_deg(26247168, 16) != fr_cos_deg(2654208, 16)) return TEST_FAIL;
+ if (fr_tan_deg(26247168, 16) != fr_tan_deg(2654208, 16)) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* fr_tan_deg non-cardinal inputs that land exactly on BAM poles */
+int test_cov_tan_deg_pole_bam() {
+ /* 90 + 2^-16 deg -> bam 0x4000, past the pole: large negative */
+ if (fr_tan_deg(5898241, 16) != -FR_TRIG_MAXVAL) return TEST_FAIL;
+ /* 270 + 2^-16 deg -> bam 0xC000 */
+ if (fr_tan_deg(17694721, 16) != -FR_TRIG_MAXVAL) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* FR_FixAddSat overflow wraps. The negative case regression-tests the
+ * signed-overflow UB bug (optimizer deleted the wrap check at -Os), and
+ * the 0+0 case regression-tests the old `sum <= 0` false positive. */
+int test_cov_addsat_neg_overflow() {
+ if (FR_FixAddSat(-2000000000, -2000000000) != (s32)0x80000000)
+ return TEST_FAIL;
+ if (FR_FixAddSat(2000000000, 2000000000) != (s32)0x7fffffff)
+ return TEST_FAIL;
+ if (FR_FixAddSat(0, 0) != 0) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* FR_acos/FR_asin radix-conversion arms not hit elsewhere */
+int test_cov_invtrig_radix_arms() {
+ s32 v;
+ /* clamp path with sign set, out_radix > 16 */
+ if (FR_acos(-70000, 16, 20) != (FR_kPI << 4)) return TEST_FAIL;
+ /* input radix < 15: shift-left normalization; acos(0.5) = pi/3 */
+ if (!near_s32(FR_acos(128, 8, 16), 68629, 400)) return TEST_FAIL;
+ /* small-angle fast path with radix >= out_radix */
+ v = FR_acos(65500, 16, 16);
+ if (v < 1500 || v > 2600) return TEST_FAIL;
+ /* fast path, negative input, out_radix > 16 */
+ v = FR_acos(-65500, 16, 20);
+ if (!near_s32(v, (FR_kPI << 4) - 2172 * 16, 9000)) return TEST_FAIL;
+ /* table path with out_radix <= 14: acos(0) = pi/2 at r14 */
+ if (!near_s32(FR_acos(0, 16, 14), 25736, 40)) return TEST_FAIL;
+ /* asin with out_radix > 16: asin(0.5) = pi/6 at r20 */
+ if (!near_s32(FR_asin(32768, 16, 20), 549066, 4000)) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* FR_atan2 axis cases and fast paths at out_radix 14 and 20 */
+int test_cov_atan2_radix_arms() {
+ if (FR_atan2(5, 0, 20) != (FR_kQ2RAD << 4)) return TEST_FAIL;
+ if (FR_atan2(-5, 0, 20) != -(FR_kQ2RAD << 4)) return TEST_FAIL;
+ if (FR_atan2(0, -5, 20) != (FR_kPI << 4)) return TEST_FAIL;
+ if (!near_s32(FR_atan2(1, 100, 14), 164, 30)) return TEST_FAIL;
+ if (!near_s32(FR_atan2(100, 1, 20), 1636640, 6000)) return TEST_FAIL;
+ if (!near_s32(FR_atan2(100, 1, 14), 25572, 80)) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* radix==0 / prec==0 arms in FR_pow2, FR_printNumF; FR_numstr edges */
+int test_cov_pow2_print_numstr() {
+ if (FR_pow2(3, 0) != 8) return TEST_FAIL;
+ /* radix 0, prec 0: prints "42", returns char count */
+ if (FR_printNumF(cov_sink, 42, 0, 0, 0) != 2) return TEST_FAIL;
+ /* leading '+' sign */
+ if (FR_numstr("+2.5", 16) != 163840) return TEST_FAIL;
+ /* more than 9 fractional digits: extras ignored */
+ if (FR_numstr("0.1234567890123", 16) != FR_numstr("0.123456789", 16))
+ return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* FR_hypot_fast8 INT32_MIN mirror clamps */
+int test_cov_hypot_intmin() {
+ if (FR_hypot_fast8((s32)0x80000000, 100) < 2140000000) return TEST_FAIL;
+ if (FR_hypot_fast8(100, (s32)0x80000000) < 2140000000) return TEST_FAIL;
+ return TEST_PASS;
+}
+
+/* fr_wave_noise -32768 clamp; fr_adsr_step defensive arms */
+int test_cov_noise_adsr_edges() {
+ fr_adsr_t env;
+ u32 st = 2; /* lsb 0 -> state 1 -> top bits 0 -> v = -32768 -> clamp */
+ if (fr_wave_noise(&st) != -32767) return TEST_FAIL;
+
+ /* idle envelope with no trigger returns 0 */
+ fr_adsr_init(&env, 10, 10, 16384, 10);
+ if (fr_adsr_step(&env) != 0) return TEST_FAIL;
+
+ /* defensive default: unknown state value (struct is caller-owned) */
+ env.state = 99;
+ if (fr_adsr_step(&env) != 0) return TEST_FAIL;
+
+ /* defensive out<0 clamp: force a negative level mid-attack */
+ fr_adsr_init(&env, 65535, 0, 16384, 0);
+ env.state = FR_ADSR_ATTACK;
+ env.level = -(1 << 28);
+ if (fr_adsr_step(&env) != 0) return TEST_FAIL;
+ return TEST_PASS;
+}
+
/* Main test runner */
int main() {
printf("\n=== FR_Math Full Coverage Test Suite ===\n\n");
@@ -1130,6 +1268,18 @@ int main() {
printf("\nDark-Corner Edge Branches:\n");
RUN_TEST(test_edge_branches);
+ printf("\nBranch-Coverage Completion:\n");
+ RUN_TEST(test_cov_radian_reduction);
+ RUN_TEST(test_cov_deg_to_bam_reduction);
+ RUN_TEST(test_cov_deg_trig_paths);
+ RUN_TEST(test_cov_tan_deg_pole_bam);
+ RUN_TEST(test_cov_addsat_neg_overflow);
+ RUN_TEST(test_cov_invtrig_radix_arms);
+ RUN_TEST(test_cov_atan2_radix_arms);
+ RUN_TEST(test_cov_pow2_print_numstr);
+ RUN_TEST(test_cov_hypot_intmin);
+ RUN_TEST(test_cov_noise_adsr_edges);
+
printf("\n=== Test Summary ===\n");
printf("Total: %d, Passed: %d, Failed: %d\n",
test_count, test_count - fail_count, fail_count);