Releases: deftio/fr_math
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.