Release pipeline fixes. No functional changes to the math library.
- Fixed
tools/make_release.shfailing after squash-merge: local master diverges from origin (squash creates a new commit), sogit pull --ff-onlyfails. Now detects divergence and resets to origin/master. - Fixed on-master release path: script now pushes master to origin and waits for CI before tagging (previously skipped both, causing tags to point at commits not yet on the remote).
- Release pipeline auto-commits pipeline-generated changes (badge updates, version sync) instead of failing on a dirty working tree. Unexpected dirty files still block the release.
CI fix release. No functional changes to the math library.
- Fixed
release.ymlcoverage step failing due to stale gcov invocation - Removed conflicting auto-release job from
ci.yml(replaced by tag-triggeredrelease.yml) - Documentation updated:
release_management.md,docs/building.md, andpages/guide/building.htmlnow accurately describe the guided release pipeline
CI and release pipeline cleanup. No functional changes to the math library itself.
- New guided release script
tools/make_release.sh(ported from xelp): validates, opens PR, waits for CI, merges, tags, waits for GitHub Release, then publishes to PlatformIO and ESP-IDF registries - Removed old
scripts/make_release.sh(validation-only, manual merge) - All doc/page references updated to
tools/make_release.sh - Cleaned up stale branches
Embedded library publishing support. No functional changes to the math library itself — this release adds package manager integration and improves example organization.
- Added
library.propertiesfor Arduino Library Manager - Added
library.jsonfor PlatformIO Registry - Added
idf_component.yml+CMakeLists.txtfor ESP-IDF Component Registry - Added
keywords.txtfor Arduino IDE syntax highlighting - Added
.github/workflows/release.ymlfor tag-triggered release validation
- New focused Arduino sketches:
basic-math,trig-functions,wave-generators - Moved
FR_Math_Example1.cpptoexamples/posix-example/ - Removed symlinks from
examples/arduino_smoke/(Arduino forbids symlinks) - Added
examples/README.md
- Added
llms.txt— machine-readable API summary for AI coding agents - Added
agents.md— coding agent conventions and contribution guide - Added
dev/misc/publish checklists for Arduino, PlatformIO, and ESP-IDF - Updated
scripts/sync_version.shto sync version across all new metadata files - Added release step 11: verify
llms.txtandagents.mdare current
Precision and accuracy release. All changes are backward-compatible with v2.0.0 except where noted.
fr_cos_bam/fr_sin_bamnow return s15.16 (was s0.15). Exact values at cardinal angles:cos(0°) = 65536,cos(90°) = 0.FR_TRIG_ONE = 65536.FR_TanI/fr_tanreturn s15.16 (was s16.15). Saturation is now±INT32_MAX.- All wrappers updated:
FR_Cos,FR_Sin,FR_CosI,FR_SinI,FR_TanI,FR_Tan.
FR_acos,FR_asin,FR_atangain anout_radixparameter and return radians at that radix (was degrees ass16).FR_atan2(y, x, out_radix)also returns radians.FR_BAM2RADmacro corrected (was off by a factor of 1024).
FR_FixMuls/FR_FixMulSat: add 0.5 LSB (+0x8000) before the>>16shift. Both now round to nearest instead of truncating.FR_sqrt/FR_hypot: the internalfr_isqrt64now rounds to nearest (remainder > root → +1). Worst-case error drops from <1 LSB to ≤ 0.5 LSB.FR_DIVnow rounds to nearest (≤ 0.5 LSB error) instead of truncating.FR_DIV_TRUNCpreserves the old truncating behaviour.
FR_pow2table expanded from 17 entries (16 segments) to 65 entries (64 segments, 260 bytes). Interpolation error drops by ~16×.FR_log2table expanded from 33 entries to 65 entries (6-bit index / 24-bit interpolation). Worst-case error ≤ 4 LSB at Q16.16.FR_MULK28macro added: multiplies any fixed-point value by a radix-28 constant using a 64-bit intermediate with round-to-nearest. ~9 decimal digits of precision.FR_EXPandFR_POW10now useFR_MULK28for the base conversion instead of shift-only macros.FR_lnandFR_log10also useFR_MULK28internally.FR_EXP_FASTandFR_POW10_FASTadded as shift-only alternatives for 8-bit targets where 64-bit multiply is expensive.
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the oldFR_DIVbehaviour).FR_MOD(x, xr, y, yr)— fixed-point modulus.- Radix-28 constants:
FR_kLOG2E_28,FR_krLOG2E_28,FR_kLOG2_10_28,FR_krLOG2_10_28.
| Change | v2.0.0 | v2.0.1 |
|---|---|---|
| sin/cos return type | s16 (s0.15) |
s32 (s15.16) |
| sin/cos 1.0 value | 32767 | 65536 (exact) |
| tan return format | s16.15 (radix 15) | s15.16 (radix 16) |
| tan saturation | ±(32767 << 15) |
±INT32_MAX |
| FR_acos/asin signature | (input, radix) → s16 degrees |
(input, radix, out_radix) → s32 radians |
| FR_atan signature | (input, radix) → s16 degrees |
(input, radix, out_radix) → s32 radians |
| FR_atan2 signature | (y, x) → s16 degrees |
(y, x, out_radix) → s32 radians |
| FR_BAM2RAD | off by 1024× (bug) | correct |
| FR_DIV rounding | truncates toward zero | rounds to nearest (use FR_DIV_TRUNC for old behaviour) |
This is a significant bug-fix and precision-improvement release. Several
v1 functions produced wrong numerical results on every platform due to
arithmetic bugs; others produced wrong results specifically on 64-bit
hosts because of a typedef choice. Every changed function is covered by
the TDD characterization suite in tests/test_tdd.cpp.
See dev/fr_math_precision.md for the full per-symbol reference
(inputs, outputs, precision, saturation) and dev/fr_math2_impl_plan.md
for the implementation plan this release executed.
FR_defs.h: migrated typedefs to<stdint.h>(s8→int8_t,s32→int32_t, etc.). v1 defineds32assigned long, which is 64 bits on LP64 platforms (Linux and macOS on x64 / ARM64), so every fixed-point computation that relied ons32being exactly 32 bits silently produced wrong answers on desktop Linux and macOS. C99<stdint.h>is now mandatory in v2 (every modern toolchain — gcc, clang, MSVC, IAR, Keil C51, sdcc, MSP430-gcc, AVR-gcc, RISC-V/ARM — ships it). If you are stuck on a pre-C99 compiler, FR_Math 1.0.x remains the version for you.
FR_FixMulSat/FR_FixMuls: v1 used a split-multiply formula with an algebraic bug that returned wrong values for certain sign combinations. v2 uses anint64_tfast path with explicit saturation.FR_log2: v1 was missing the accumulator in the mantissa-table step and returned wrong values for non-power-of-2 inputs. v2 rewrites the algorithm: leading-bit-position → normalize to s1.30 → 33-entry mantissa lookup with linear interpolation.FR_ln/FR_log10: inherit theFR_log2fix automatically (they are multiplies ofFR_log2by a constant).FR_pow2: v1 used C truncation toward zero to extract the integer exponent, which gave wrong answers for negative non-integer inputs (e.g.FR_pow2(-0.5)returned ~2 instead of ~0.707). v2 uses mathematical floor (toward −∞) and a 17-entry fraction table with linear interp.FR_atan2: v1 was a placeholder that returned garbage. v2 is a correct octant-reduced arctan with a 33-entry table. Max error ≤ 1°. v2 also drops the vestigialradixparameter — new signature isFR_atan2(s32 y, s32 x).FR_atan: v1 was wired up incorrectly. v2 implements asFR_atan2(input, 1<<radix).FR_Tan: v1 useds16loop variables that overflowed for angles near 90°. v2 usess32.FR_TanI: removed dead unreachable code (if (270 == deg)).FR_printNumD: v1 invoked unary minus onINT_MIN, which is undefined behavior, and always returned 0. v2 works in unsigned magnitude and returns the real byte count (or −1 on nullf).FR_printNumF: same INT_MIN fix, plus v1's fraction extraction was mathematically wrong and printed bogus tail digits.FR_printNumH: v1 shifted a signed negative int right. v2 casts to unsigned first.
FR_DEG2RADandFR_RAD2DEG: v1 had the macro bodies swapped relative to their names, soFR_DEG2RAD(x)actually multiplied by 57.3 andFR_RAD2DEG(x)multiplied by 0.017. v2 swaps them back.FR_DEG2RADalso had missing parens aroundxin a subexpression.FR_NUM: v1 signature wasFR_NUM(i, f, r)and the body ignored thefargument entirely. v2 adds an explicitddigit-count argument — new signature isFR_NUM(i, f, d, r). Any caller of the v1 form must be updated.
- Radian-native trig (
fr_cos,fr_sin,fr_tan,fr_cos_bam,fr_sin_bam,fr_cos_deg,fr_sin_deg): takes radians (or BAM, or integer degrees) directly rather than forcing everything through integer degrees. Uses a new 129-entry s0.15 quadrant cosine table insrc/FR_trig_table.hwith linear interpolation and round-to-nearest. Max error ≤ 1 LSB of s0.15 (~3e−5). Mean error ~0. - BAM (Binary Angular Measure) macros:
FR_DEG2BAM,FR_BAM2DEG,FR_RAD2BAM,FR_BAM2RAD. BAM is the natural integer representation for trig: 16 bits per full circle, top 2 bits select the quadrant, next 7 bits index the table, bottom 7 bits drive interpolation. - Table size is a compile-time knob:
-DFR_TRIG_TABLE_BITS=8gives a 257-entry table for halved worst-case error (default is 7 / 129 entries). - Square root and hypot (
FR_sqrt,FR_hypot): radix-aware fixed point square root and 2D vector magnitude.FR_sqrtuses a digit-by-digit (shift-and-subtract) integer isqrt onint64_t. Negative input returnsFR_DOMAIN_ERROR(INT32_MIN).FR_hypotcomputessqrt(x^2 + y^2)with no intermediate overflow up to the full s32 range. Bit-exact for perfect squares; max error ~1 LSB at the requested radix. - Fast approximate magnitude (
FR_hypot_fast,FR_hypot_fast8): shift-only piecewise-linear approximation ofsqrt(x^2 + y^2)— no multiply, no divide, no 64-bit math, no ROM table, no iteration.FR_hypot_fastuses 4 segments (~0.4% peak error);FR_hypot_fast8uses 8 segments (~0.14% peak error). Based on the method of US Patent 6,567,777 B1 (Chatterjee, public domain). Noradixparameter needed — the algorithm is scale-invariant. - Wave function family for embedded audio / LFOs / control
signals. All take a
u16BAM phase and return s0.15 (s16, ±32767):fr_wave_sqr(phase)— symmetric square wave.fr_wave_pwm(phase, duty)— variable-duty pulse wave;dutyis au16threshold in BAM units.fr_wave_tri(phase)— symmetric triangle, peaks clamped to ±32767. Max error vs ideal triangle ~3e−5 (1 LSB s0.15).fr_wave_saw(phase)— sawtooth,(s16)(phase - 0x8000)with boundary clamp.fr_wave_tri_morph(phase, break_point)— variable-symmetry triangle that morphs into a sawtooth asbreak_pointapproaches0or0xffff. Returns unipolar [0, 32767]. Uses one division per sample.fr_wave_noise(state)— 32-bit Galois LFSR (poly0xD0000001, period 2^32 − 1) returning a full-range s16. Caller owns theu32state; seed with any non-zero value.
FR_HZ2BAM_INC(hz, sample_rate): phase increment helper. Computeshz * 65536 / sample_rateso a u16 phase accumulator driven by this increment produces the requested frequency. Example:FR_HZ2BAM_INC(440, 48000) = 600(≈ 439.45 Hz).- ADSR envelope generator (
fr_adsr_t,fr_adsr_init,fr_adsr_trigger,fr_adsr_release,fr_adsr_step): linear-segment attack/decay/sustain/release envelope. Internal levels are stored as s1.30 so very long durations (e.g. 48000-sample attack at 48 kHz) still get a non-zero per-sample increment;fr_adsr_stepreturns s0.15 for direct multiplication into a wave sample. State machine exposes constantsFR_ADSR_IDLE/_ATTACK/_DECAY/_SUSTAIN/_RELEASE.
dev/fr_math_precision.md— comprehensive per-symbol precision reference. Every public macro, constant, and function is documented with inputs, output format, worst-case error, saturation behavior, and side-effect notes.CONTRIBUTING.md— PR expectations, test discipline, portability rules, commit message format.tools/interp_analysis.html— interactive Chart.js analysis of trig interpolation methods. Compares nearest-neighbor, linear truncated, linear rounded, cosine interp, smoothstep, Hermite cubic, and Catmull-Rom on the same 129-entry table, over θ ∈ [−45°, +45°]. Includes pan/zoom on a second chart showing the actual interpolated values vsMath.cosreference. Use this to verify interpolation claims and to pick a default for the library.scripts/clean_build.sh— one-shot clean ofbuild/andcoverage/directories. Handy when switching branches or debugging stale object files.
FR_NUMsignature:FR_NUM(i, f, r)→FR_NUM(i, f, d, r). Any code that called the 3-argument form will fail to compile. The old form was broken (it ignoredfentirely), so any caller was already getting wrong results.FR_atan2signature:FR_atan2(y, x, radix)→FR_atan2(y, x). The radix parameter was vestigial (ignored by the v1 placeholder and unnecessary in v2).FR_RESULTandFR_E_*codes removed: v1's HRESULT-style return codes are gone. The matrixinv()methods now returnbool(trueon success,falseif singular).add(),sub(), andsetrotate()returnvoid. Math functions that can hit a domain error return the named sentinelFR_DOMAIN_ERROR; saturating arithmetic returnsFR_OVERFLOW_POS/FR_OVERFLOW_NEG.FR_SQUAREandFR_FIXMUL32uremoved: these s16.16-only macros were narrow specializations ofFR_FixMuls/FR_FixMulSat. Use the generic versions, which now have anint64_tfast path and work at any radix.FR_NO_INT64andFR_NO_STDINTbuild flags removed: every C99-or-newer toolchain on every architecture (including 8-bit targets like sdcc, AVR-gcc, MSP430-gcc) provides<stdint.h>and 64-bit integer arithmetic, so the conditional fallbacks were carrying their weight in maintenance and ROM for no benefit.- Wider intermediate types: if you had code that poked at
s32as if it werelong(e.g. printing with%ld), that will now warn. Use%dwiths32/int32_t, or cast.
- Increased overall test coverage from 4% to 72%
- FR_math.c: 60% coverage
- FR_math_2D.cpp: 98% coverage
- Added comprehensive test suite with multiple test files:
test_comprehensive.c- Comprehensive tests for core math functionstest_overflow_saturation.c- Edge case and overflow behavior teststest_full_coverage.c- Tests for previously uncovered functionstest_2d_complete.cpp- Complete 2D transformation matrix tests
- Fixed test failures in 2D transformation tests (XFormPtI now correctly expects integer inputs)
- Fixed FR_atan function - Was incorrectly calling FR_atan2 with wrong arguments, now properly calls FR_atanI
- Fixed constant declarations - Changed FR_PI, FR_2PI, FR_E from non-const to proper const declarations
- Fixed XFormPtI test assumptions - Tests were incorrectly passing fixed-point values instead of integers
- Added comprehensive Makefile targets:
make lib- Build static librarymake test- Run all testsmake coverage- Generate coverage reports with lcovmake examples- Build example programsmake clean- Clean build artifacts
- Added GitHub Actions CI/CD:
- Multi-platform testing (Ubuntu, macOS)
- Multi-compiler support (gcc, clang)
- Cross-compilation testing (ARM, RISC-V)
- 32-bit compatibility testing
- Automated coverage reporting to Codecov
- Overflow and saturation testing with sanitizers
- Cleaned up README.md:
- Fixed grammar and spelling throughout
- Added "Building and Testing" section with clear instructions
- Improved formatting with consistent markdown usage
- Added supported platforms list
- Better code examples with syntax highlighting
- Clearer mathematical notation and explanations
- Added CLAUDE.md - Assistant-friendly documentation with:
- Project structure overview
- Key concepts and math notation explained
- Testing and linting commands
- Common pitfalls and tips
- Added inline documentation for test files explaining coverage goals
- Removed unused/unimplemented functions:
- Removed FR_atan declaration (was declared but never implemented)
- Cleaned up test code to remove references to non-existent functions
- Fixed compiler warnings:
- Resolved deprecated C++ warnings
- Fixed format string warnings in tests
- Addressed unused variable warnings
- Improved code organization:
- Better separation of test types
- Clearer test naming conventions
- More maintainable test structure
- Verified compilation and testing on:
- x86/x64 (Linux, macOS)
- ARM (32-bit and 64-bit)
- RISC-V
- 32-bit x86 targets
- Added cross-compilation support in CI
- Updated all source file headers to version 1.0.3
- Added version section to README.md
- Initial public release
- Basic fixed-point math operations
- 2D transformation matrices
- Core trigonometric functions
- Internal development version
- Cleaned up naming conventions
- Initial test framework
Note: FR_Math has been in development since 2000, originally used in embedded systems for Palm Pilots and later ARM cores. This is the first version with comprehensive testing and CI/CD integration.