Releases: deftio/fr_math
FR_Math 2.0.8
FR_Math Release Notes
Version 2.0.8 (2026)
Tangent accuracy rewrite and trig rounding fix.
BAM-native tangent table
- New
fr_tan_bam(u16 bam)function with a dedicated 65-entry octant
lookup table (gFR_TAN_TAB_OinFR_trig_table.h, 130 bytes ROM).
First octant uses direct table + lerp; second octant uses the
reciprocal identitytan(x) = 1/tan(90-x)with one 32-bit division.
No 64-bit intermediates anywhere in the tan path. FR_TanI,FR_Tan,fr_tanare now thin wrappers over
fr_tan_bam. The old sin/cos division implementation is removed.- Peak error: 0.17% (BAM), 0.60% (deg r7), 0.17% (rad r16).
Round-to-nearest fix for radian/degree wrappers
fr_cos,fr_sin,fr_tan,FR_Cos,FR_Sin,FR_Tannow add
0.5 LSB (1 << (radix-1)) before the>> radixshift when converting
from radians/degrees to BAM. This rounds to the nearest BAM value
instead of truncating, eliminating a systematic 1-BAM rounding error
that caused ~1% peak error near zero crossings.- Radian-path sin/cos/tan now match BAM-native accuracy (0.16-0.17%
peak, was ~1.03%).
Conversion macro trimming
FR_DEG2BAM: 10 terms (~28 bits) reduced to 7 terms (~18 bits)FR_RAD2BAM: 9 terms (~27 bits) reduced to 7 terms (~21 bits)FR_DEG2RAD: 3 terms (~13 bits) extended to 5 terms (~17 bits)- 18 bits of precision gives 4 bits of headroom over the 14-bit
effective BAM resolution of the trig tables. Verified: reverting to
the old full-precision macros changes sin/cos peak error by <0.04%.
Other
FR_TRIG_MINVALfixed: was-FR_TRIG_MASK(-65535), now
-FR_TRIG_MAXVAL(-2147483647) to properly pair withFR_TRIG_MAXVAL
for tan saturation clamping.- Accuracy table in all docs now shows separate BAM/deg/rad rows for
sin/cos and tan, matching the TDD characterization report. fr_tan_bamadded to function listings across README, docs, HTML
pages, and llms.txt.
Version 2.0.7 (2026)
README restructure, accuracy table cleanup, and expanded cross-compile support.
New: FR_CORE_ONLY convenience define
A single #define FR_CORE_ONLY before including FR_math.h (or -DFR_CORE_ONLY
on the command line) now strips both print helpers and wave generators in one step.
It expands to FR_NO_PRINT + FR_NO_WAVES internally.
Accuracy table cleanup
The LSB column has been removed from the accuracy table output. Percent error
is the metric that matters to callers; LSB error is an implementation detail
that varies with the chosen radix.
Expanded cross-compile targets
- RP2040 (Cortex-M0+) and STM32 (Cortex-M4) added as named targets
in the Docker cross-build - 68HC11 and MIPS32 toolchains added to the Docker image
- Size table now shows three columns: Lean, Core, and Full
- Consolidated
scripts/crossbuild_sizes.sh— single script runs Docker,
builds all targets, writes CSV + markdown, and patches doc files
(replacescrossbuild-docker.sh,size_report.sh,update_sizes.sh) - Size table sorted by architecture width (8-bit → 64-bit)
README restructure
Sections reordered: accuracy table moved above the size table to lead with
the library's primary selling point. Badges cleaned up from Quikdown HTML to
standard markdown syntax. Build flavor descriptions made more concise.
Version 2.0.6 (2026)
Accuracy improvements, lean-build options, and library cleanup.
Accuracy & algorithms
- FR_acos boundary fix: deferred quantization computes
1-xat the
caller's radix instead of r15, giving 12x better accuracy near ±1.0
(max LSB error 512.6 → 42.3) - FR_atan2 rewrite: uses asin/acos + hypot_fast8 with octant
switching for well-conditioned results everywhere (0.41% peak vs
20% for libfixmath)
Lean build options
Two new compile-time #define guards strip optional subsystems for
ROM-constrained targets:
| Define | Removes | Savings |
|---|---|---|
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 |
With both guards the core math library compiles to ~3.5 KB on x86-64
(clang -Os), roughly 2.6 KB on Thumb-2.
Removed
- FR_hypot_fast (4-segment) deleted — FR_hypot_fast8 (8-segment)
is strictly better in both accuracy (0.10% vs 0.34%) and is used
internally by FR_atan2. The 4-segment variant was dead weight.
Other
- libfixmath comparison benchmark (
compare_lfm/) added to repo - Documentation updated across all markdown and HTML pages
Version 2.0.5 (2026)
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.
Version 2.0.4 (2026)
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
Version 2.0.3 (2026)
CI and release pipeline cleanup. No functional changes to the math
library itself.
Release pipeline
- 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
Version 2.0.2 (2026)
Embedded library publishing support. No functional changes to the
math library itself — this release adds package manager integration
and improves example organization.
Library publishing
- 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
Examples reorganized
- 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
Documentation
- 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
Version 2.0.1 (2026)
Precision and accuracy release. All changes are backward-compatible
with v2.0.0 except where noted.
Trig output precision
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.
Inverse trig now returns radians
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).
Rounding improvements
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.
Improved exp / log accuracy
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.
New symbols
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the old
FR_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.
Breaking changes from v2.0.0
| Change | v2.0.0 | v2.0.1 |
|----...
FR_Math 2.0.7
FR_Math Release Notes
Version 2.0.7 (2026)
README restructure, accuracy table cleanup, and expanded cross-compile support.
New: FR_CORE_ONLY convenience define
A single #define FR_CORE_ONLY before including FR_math.h (or -DFR_CORE_ONLY
on the command line) now strips both print helpers and wave generators in one step.
It expands to FR_NO_PRINT + FR_NO_WAVES internally.
Accuracy table cleanup
The LSB column has been removed from the accuracy table output. Percent error
is the metric that matters to callers; LSB error is an implementation detail
that varies with the chosen radix.
Expanded cross-compile targets
- RP2040 (Cortex-M0+) and STM32 (Cortex-M4) added as named targets
in the Docker cross-build - 68HC11 toolchain added to the Docker image
- Size table now shows two columns: Core (
-DFR_CORE_ONLY) and Full docker/build_sizes.shoutputsbuild/sizes.csvfor automated patching- New
scripts/update_sizes.shauto-patches size tables into README, docs,
and HTML pages
README restructure
Sections reordered: accuracy table moved above the size table to lead with
the library's primary selling point. Size table now shows Core vs Full columns.
Version 2.0.6 (2026)
Accuracy improvements, lean-build options, and library cleanup.
Accuracy & algorithms
- FR_acos boundary fix: deferred quantization computes
1-xat the
caller's radix instead of r15, giving 12x better accuracy near ±1.0
(max LSB error 512.6 → 42.3) - FR_atan2 rewrite: uses asin/acos + hypot_fast8 with octant
switching for well-conditioned results everywhere (0.41% peak vs
20% for libfixmath)
Lean build options
Two new compile-time #define guards strip optional subsystems for
ROM-constrained targets:
| Define | Removes | Savings |
|---|---|---|
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 |
With both guards the core math library compiles to ~3.5 KB on x86-64
(clang -Os), roughly 2.6 KB on Thumb-2.
Removed
- FR_hypot_fast (4-segment) deleted — FR_hypot_fast8 (8-segment)
is strictly better in both accuracy (0.10% vs 0.34%) and is used
internally by FR_atan2. The 4-segment variant was dead weight.
Other
- libfixmath comparison benchmark (
compare_lfm/) added to repo - Documentation updated across all markdown and HTML pages
Version 2.0.5 (2026)
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.
Version 2.0.4 (2026)
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
Version 2.0.3 (2026)
CI and release pipeline cleanup. No functional changes to the math
library itself.
Release pipeline
- 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
Version 2.0.2 (2026)
Embedded library publishing support. No functional changes to the
math library itself — this release adds package manager integration
and improves example organization.
Library publishing
- 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
Examples reorganized
- 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
Documentation
- 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
Version 2.0.1 (2026)
Precision and accuracy release. All changes are backward-compatible
with v2.0.0 except where noted.
Trig output precision
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.
Inverse trig now returns radians
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).
Rounding improvements
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.
Improved exp / log accuracy
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.
New symbols
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the old
FR_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.
Breaking changes from v2.0.0
| 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) |
Version 2.0.0 (2026)
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.
64-bit safety (portability)
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.
Numerical fixes
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 miss...
FR_Math 2.0.6
FR_Math Release Notes
Version 2.0.6 (2026)
Accuracy improvements, lean-build options, and library cleanup.
Accuracy & algorithms
- FR_acos boundary fix: deferred quantization computes
1-xat the
caller's radix instead of r15, giving 12x better accuracy near ±1.0
(max LSB error 512.6 → 42.3) - FR_atan2 rewrite: uses asin/acos + hypot_fast8 with octant
switching for well-conditioned results everywhere (0.41% peak vs
20% for libfixmath)
Lean build options
Two new compile-time #define guards strip optional subsystems for
ROM-constrained targets:
| Define | Removes | Savings |
|---|---|---|
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 |
With both guards the core math library compiles to ~3.5 KB on x86-64
(clang -Os), roughly 2.6 KB on Thumb-2.
Removed
- FR_hypot_fast (4-segment) deleted — FR_hypot_fast8 (8-segment)
is strictly better in both accuracy (0.10% vs 0.34%) and is used
internally by FR_atan2. The 4-segment variant was dead weight.
Other
- libfixmath comparison benchmark (
compare_lfm/) added to repo - Documentation updated across all markdown and HTML pages
Version 2.0.5 (2026)
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.
Version 2.0.4 (2026)
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
Version 2.0.3 (2026)
CI and release pipeline cleanup. No functional changes to the math
library itself.
Release pipeline
- 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
Version 2.0.2 (2026)
Embedded library publishing support. No functional changes to the
math library itself — this release adds package manager integration
and improves example organization.
Library publishing
- 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
Examples reorganized
- 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
Documentation
- 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
Version 2.0.1 (2026)
Precision and accuracy release. All changes are backward-compatible
with v2.0.0 except where noted.
Trig output precision
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.
Inverse trig now returns radians
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).
Rounding improvements
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.
Improved exp / log accuracy
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.
New symbols
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the old
FR_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.
Breaking changes from v2.0.0
| 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) |
Version 2.0.0 (2026)
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.
64-bit safety (portability)
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.
Numerical fixes
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 is
FR_atan2(s32 y, s32 x).FR_atan: v1 was wired up incorrectly. v2 implements as
FR_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 behavi...
FR_Math 2.0.5
FR_Math Release Notes
Version 2.0.5 (2026)
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.
Version 2.0.4 (2026)
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
Version 2.0.3 (2026)
CI and release pipeline cleanup. No functional changes to the math
library itself.
Release pipeline
- 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
Version 2.0.2 (2026)
Embedded library publishing support. No functional changes to the
math library itself — this release adds package manager integration
and improves example organization.
Library publishing
- 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
Examples reorganized
- 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
Documentation
- 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
Version 2.0.1 (2026)
Precision and accuracy release. All changes are backward-compatible
with v2.0.0 except where noted.
Trig output precision
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.
Inverse trig now returns radians
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).
Rounding improvements
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.
Improved exp / log accuracy
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.
New symbols
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the old
FR_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.
Breaking changes from v2.0.0
| 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) |
Version 2.0.0 (2026)
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.
64-bit safety (portability)
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.
Numerical fixes
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 is
FR_atan2(s32 y, s32 x).FR_atan: v1 was wired up incorrectly. v2 implements as
FR_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.
Macro fixes
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.
New functionality
- 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 in
src/FR_trig_table.hwith linear interpolation and...
FR_Math 2.0.2
FR_Math Release Notes
Version 2.0.2 (2026)
Embedded library publishing support. No functional changes to the
math library itself — this release adds package manager integration
and improves example organization.
Library publishing
- 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
Examples reorganized
- 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
Documentation
- 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
Version 2.0.1 (2026)
Precision and accuracy release. All changes are backward-compatible
with v2.0.0 except where noted.
Trig output precision
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.
Inverse trig now returns radians
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).
Rounding improvements
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.
Improved exp / log accuracy
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.
New symbols
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the old
FR_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.
Breaking changes from v2.0.0
| 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) |
Version 2.0.0 (2026)
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.
64-bit safety (portability)
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.
Numerical fixes
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 is
FR_atan2(s32 y, s32 x).FR_atan: v1 was wired up incorrectly. v2 implements as
FR_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.
Macro fixes
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.
New functionality
- 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 in
src/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_hypot
computessqrt(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). No
radixparameter needed — the algorithm is scale-invariant. - Wave function family for embedded audio / LFOs / control
signals. All take au16BAM phase and return s0.15 (s16, ±32767):fr_wave_sqr(phase)— symmetric square wave.fr_wave_pwm(phase, duty)— variabl...
FR_Math 2.0.1
FR_Math Release Notes
Version 2.0.1 (2026)
Precision and accuracy release. All changes are backward-compatible
with v2.0.0 except where noted.
Trig output precision
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.
Inverse trig now returns radians
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).
Rounding improvements
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.
Improved exp / log accuracy
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.
New symbols
FR_MIN,FR_MAX,FR_CLAMP— standard min/max/clamp.FR_DIV_TRUNC(x, xr, y, yr)— truncating division (the old
FR_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.
Breaking changes from v2.0.0
| 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) |
Version 2.0.0 (2026)
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.
64-bit safety (portability)
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.
Numerical fixes
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 is
FR_atan2(s32 y, s32 x).FR_atan: v1 was wired up incorrectly. v2 implements as
FR_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.
Macro fixes
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.
New functionality
- 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 in
src/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_hypot
computessqrt(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). No
radixparameter needed — the algorithm is scale-invariant. - Wave function family for embedded audio / LFOs / control
signals. All take au16BAM 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_pointapproaches
0or0xffff. 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 the
u32state; 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 p...
FR_Math 2.0.0
FR_Math Release Notes
Version 2.0.0 (2026)
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.
64-bit safety (portability)
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.
Numerical fixes
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 is
FR_atan2(s32 y, s32 x).FR_atan: v1 was wired up incorrectly. v2 implements as
FR_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.
Macro fixes
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.
New functionality
- 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 in
src/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_hypot
computessqrt(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). No
radixparameter needed — the algorithm is scale-invariant. - Wave function family for embedded audio / LFOs / control
signals. All take au16BAM 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_pointapproaches
0or0xffff. 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 the
u32state; 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.
New documentation
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/and
coverage/directories. Handy when switching branches or debugging
stale object files.
Breaking changes
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(), and
setrotate()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.
Version 1.0.3 (2025)
Test Coverage Improvements
- 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 an...
FR_Math v1.0.3
FR_Math Release Notes
Version 1.0.3 (2025)
Test Coverage Improvements
- 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)
Bug Fixes
- 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
Build System Enhancements
- 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
Documentation Improvements
- 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
Code Quality
- 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
Platform Support
- 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
Version Updates
- Updated all source file headers to version 1.0.3
- Added version section to README.md
Previous Versions
Version 1.02
- Initial public release
- Basic fixed-point math operations
- 2D transformation matrices
- Core trigonometric functions
Version 1.01
- 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.