diff --git a/README.md b/README.md
index b3bce93..3090660 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](https://github.com/deftio/fr_math/actions/workflows/ci.yml)
[](#building-and-testing)
[](https://deftio.github.io/fr_math/)
-[](release_notes.md)
+[](release_notes.md)
# FR_Math: A C Language Fixed-Point Math Library for Embedded Systems
@@ -18,51 +18,95 @@ into a single format. Pure C (C99/C11/C17) with an optional C++
2D-transform wrapper. Compiles under Arduino. Zero dependencies
beyond ``.
+### Measured accuracy
+
+Errors below are measured at Q16.16 (s15.16). All functions accept any
+radix — Q16.16 is just the reference point for the table.
+Percent errors skip expected values near zero (|expected| < 0.01).
+
+At other radixes (3-bit, 24-bit, etc.) accuracy will differ due to the
+number of fractional bits available. All functions support radix 0 to 30.
+
+
+| Function | Max err (%) | Avg err (%) | Note |
+|---|---:|---:|---|
+| sin / cos | 0.7169 | 0.0100 | 65536-pt sweep + specials |
+| tan | 0.7118 | 0.0162 | 65536-pt sweep (skip poles) |
+| asin / acos | 0.7025 | 0.0105 | 65536-pt; sqrt approx near boundary |
+| atan2 | 0.4953 | 0.0268 | 65536x5 radii; asin/acos+hypot_fast8 |
+| atan | 0.2985 | 0.0159 | 20001-pt sweep [-10,10]; via FR_atan2 |
+| sqrt | 0.0003 | 0.0000 | Round-to-nearest |
+| log2 | 0.2479 | 0.0045 | 65-entry mantissa table |
+| pow2 | 0.1373 | 0.0057 | 65-entry fraction table |
+| ln, log10 | 0.0015 | 0.0004 | Via FR_MULK28 from log2 |
+| exp | 0.0719 | 0.0051 | FR_MULK28 + FR_pow2 |
+| exp_fast | 0.0719 | 0.0064 | Shift-only scaling |
+| pow10 | 0.1163 | 0.0075 | FR_MULK28 + FR_pow2 |
+| pow10_fast | 0.1163 | 0.0100 | Shift-only scaling |
+| hypot (exact) | 0.0001 | 0.0000 | 64-bit intermediate |
+| hypot_fast8 (8-seg) | 0.0977 | 0.0508 | Shift-only, no multiply |
+
+
+### What's in the box
+
+| Area | Functions |
+|---|---|
+| Arithmetic | `FR_ADD`, `FR_SUB`, `FR_DIV`, `FR_DIV32`, `FR_MOD`, `FR_FixMuls`, `FR_FixMulSat`, `FR_CHRDX` |
+| Utility | `FR_MIN`, `FR_MAX`, `FR_CLAMP`, `FR_ABS`, `FR_SGN` |
+| Trig (integer deg) | `FR_Sin`, `FR_Cos`, `FR_Tan`, `FR_SinI`, `FR_CosI`, `FR_TanI` |
+| Trig (radian/BAM) | `fr_sin`, `fr_cos`, `fr_tan`, `fr_sin_bam`, `fr_cos_bam`, `fr_sin_deg`, `fr_cos_deg` |
+| Inverse trig | `FR_atan`, `FR_atan2`, `FR_asin`, `FR_acos` |
+| Log / exp | `FR_log2`, `FR_ln`, `FR_log10`, `FR_pow2`, `FR_EXP`, `FR_POW10`, `FR_EXP_FAST`, `FR_POW10_FAST`, `FR_MULK28` |
+| Roots | `FR_sqrt`, `FR_hypot`, `FR_hypot_fast8` |
+| Wave generators | `fr_wave_sqr`, `fr_wave_pwm`, `fr_wave_tri`, `fr_wave_saw`, `fr_wave_tri_morph`, `fr_wave_noise` |
+| Envelope | `fr_adsr_init`, `fr_adsr_trigger`, `fr_adsr_release`, `fr_adsr_step` |
+| 2D transforms | `FR_Matrix2D_CPT` (mul, add, sub, det, inv, setrotate, XFormPtI, XFormPtI16) |
+| Formatted output | `FR_printNumD`, `FR_printNumF`, `FR_printNumH`, `FR_numstr` |
+
### Library size (FR_math.c only, `-Os`)
Compiled object code sizes on select platforms (static test build). Your
sizes may vary depending on optimization and linker settings. Sizes
include all code and internal tables; everything is ROMable.
-| Target | Code (text) |
-|--------|-------------|
-| ARM Thumb (Cortex-M0/M4) | 4.2 KB |
-| RISC-V 32 (rv32imac) | 4.7 KB |
-| RISC-V 64 | 4.5 KB |
-| Xtensa LX106 (ESP8266) | 5.2 KB |
-| 68k | 5.3 KB |
-| ARM32 | 5.4 KB |
-| x86-64 (GCC) | 5.7 KB |
-| AArch64 (ARM64) | 6.0 KB |
-| x86-64 (Clang) | 6.4 KB |
-| x86-32 | 6.8 KB |
-| PowerPC | 7.4 KB |
-| MSP430 (16-bit) | 8.9 KB * |
-| AVR (ATmega328P) | 10.6 KB * |
-
+
+| Target | Core | Full |
+|--------|-----:|-----:|
+| RP2040 (Cortex-M0+) | 2.6 KB | 4.2 KB |
+| STM32 (Cortex-M4) | 2.6 KB | 4.2 KB |
+| RISC-V 32 (rv32imac) | 3.0 KB | 4.7 KB |
+| ESP32 (Xtensa) | 3.5 KB | 5.2 KB |
+| 68k | 3.5 KB | 5.3 KB |
+| x86-64 (GCC) | 3.5 KB | 5.7 KB |
+| x86-32 | 4.5 KB | 6.8 KB |
+| MSP430 (16-bit) | 5.9 KB | 8.9 KB |
+| 68HC11 | 10.8 KB | 16.0 KB |
+| AVR (ATmega328P) | 7.0 KB | 10.6 KB |
+
+
+Core = compiled with `-DFR_CORE_ONLY` (math only, no print, no waves).
The optional 2D module adds ~1 KB.
-\* MSP430 and AVR are 8/16-bit — every 32-bit operation expands to multiple instructions.
+\* MSP430, 68HC11, and AVR are 8/16-bit — every 32-bit operation expands to multiple instructions.
See [`docker/`](docker/) for the cross-compile setup.
### Lean build options
-Two compile-time `#define` guards let you strip optional subsystems
+Three compile-time `#define` guards let you strip optional subsystems
for ROM-constrained targets. Define them before including `FR_math.h`
(or pass `-D` on the compiler command line):
| Define | What it removes | Typical savings |
|---|---|---|
+| `FR_CORE_ONLY` | Everything below (print + waves) | ~1.9 KB |
| `FR_NO_PRINT` | `FR_printNumF`, `FR_printNumD`, `FR_printNumH`, `FR_numstr` | ~1.3 KB |
| `FR_NO_WAVES` | `fr_wave_*` (6 shapes), `fr_adsr_*` (ADSR envelope), `FR_HZ2BAM_INC` | ~0.6 KB |
-With both guards enabled the core math library (trig, inverse trig, log/exp,
-sqrt, hypot) compiles to ~3.5 KB on x86-64 / clang -Os. On Thumb-2 this
-would be roughly 2.6 KB.
+`FR_CORE_ONLY` is a convenience shorthand that defines both
+`FR_NO_PRINT` and `FR_NO_WAVES` in one step.
```c
/* Example: headless sensor node — math only, no print, no audio */
-#define FR_NO_PRINT
-#define FR_NO_WAVES
+#define FR_CORE_ONLY
#include "FR_math.h"
```
@@ -71,51 +115,6 @@ also strip any unused functions automatically, so these guards are most
useful when you include the library as a single `.c` file or static
archive without section-level dead-code elimination.
-### Measured accuracy
-
-Errors below are measured at Q16.16 (s15.16). All functions accept any
-radix — Q16.16 is just the reference point for the table.
-Percent errors skip expected values near zero (|expected| < 0.01).
-
-At other radixes (3-bit, 24-bit, etc.) accuracy will differ due to the
-number of fractional bits available. All functions support radix 0 to 30.
-
-
-| Function | Max err (LSB) | Max err (%) | Avg err (%) | Note |
-|---|---:|---:|---:|---|
-| sin / cos | 7.5 | 0.7169 | 0.0100 | 65536-pt sweep + specials |
-| tan | 38020.4 | 0.7118 | 0.0162 | 65536-pt sweep (skip poles) |
-| asin / acos | 42.3 | 0.7025 | 0.0105 | 65536-pt; sqrt approx near boundary |
-| atan2 | 63.3 | 0.4953 | 0.0268 | 65536x5 radii; asin/acos+hypot_fast8 |
-| atan | 61.9 | 0.2985 | 0.0159 | 20001-pt sweep [-10,10]; via FR_atan2 |
-| sqrt | 28.4 | 0.0003 | 0.0000 | Round-to-nearest |
-| log2 | 10.5 | 0.2479 | 0.0045 | 65-entry mantissa table |
-| pow2 | 220.4 | 0.1373 | 0.0057 | 65-entry fraction table |
-| ln, log10 | 0.7 | 0.0015 | 0.0004 | Via FR_MULK28 from log2 |
-| exp | 65.7 | 0.0719 | 0.0051 | FR_MULK28 + FR_pow2 |
-| exp_fast | 195.5 | 0.0719 | 0.0064 | Shift-only scaling |
-| pow10 | 143.4 | 0.1163 | 0.0075 | FR_MULK28 + FR_pow2 |
-| pow10_fast | 581.9 | 0.1163 | 0.0100 | Shift-only scaling |
-| hypot (exact) | 0.2 | 0.0001 | 0.0000 | 64-bit intermediate |
-| hypot_fast8 (8-seg) | 59968.8 | 0.0977 | 0.0508 | Shift-only, no multiply |
-
-
-### What's in the box
-
-| Area | Functions |
-|---|---|
-| Arithmetic | `FR_ADD`, `FR_SUB`, `FR_DIV`, `FR_DIV32`, `FR_MOD`, `FR_FixMuls`, `FR_FixMulSat`, `FR_CHRDX` |
-| Utility | `FR_MIN`, `FR_MAX`, `FR_CLAMP`, `FR_ABS`, `FR_SGN` |
-| Trig (integer deg) | `FR_Sin`, `FR_Cos`, `FR_Tan`, `FR_SinI`, `FR_CosI`, `FR_TanI` |
-| Trig (radian/BAM) | `fr_sin`, `fr_cos`, `fr_tan`, `fr_sin_bam`, `fr_cos_bam`, `fr_sin_deg`, `fr_cos_deg` |
-| Inverse trig | `FR_atan`, `FR_atan2`, `FR_asin`, `FR_acos` |
-| Log / exp | `FR_log2`, `FR_ln`, `FR_log10`, `FR_pow2`, `FR_EXP`, `FR_POW10`, `FR_EXP_FAST`, `FR_POW10_FAST`, `FR_MULK28` |
-| Roots | `FR_sqrt`, `FR_hypot`, `FR_hypot_fast8` |
-| Wave generators | `fr_wave_sqr`, `fr_wave_pwm`, `fr_wave_tri`, `fr_wave_saw`, `fr_wave_tri_morph`, `fr_wave_noise` |
-| Envelope | `fr_adsr_init`, `fr_adsr_trigger`, `fr_adsr_release`, `fr_adsr_step` |
-| 2D transforms | `FR_Matrix2D_CPT` (mul, add, sub, det, inv, setrotate, XFormPtI, XFormPtI16) |
-| Formatted output | `FR_printNumD`, `FR_printNumF`, `FR_printNumH`, `FR_numstr` |
-
## Quick start
```bash
@@ -210,7 +209,7 @@ The full docs ship in two forms — pick whichever fits how you read.
FR_Math has been in service since 2000, originally built for graphics
transforms on 16 MHz 68k Palm Pilots. It shipped inside Trumpetsoft's
*Inkstorm* on PalmOS, then moved forward through ARM, x86, MIPS,
-RISC-V, and various 8/16-bit embedded targets. v2.0.6 is the current
+RISC-V, and various 8/16-bit embedded targets. v2.0.7 is the current
release with a full test suite, bit-exact numerical specification, and
CI on every push.
@@ -226,5 +225,5 @@ BSD-2-Clause — see [LICENSE.txt](LICENSE.txt).
## Version
-2.0.6 — see [release_notes.md](release_notes.md) for the v1 → v2
+2.0.7 — see [release_notes.md](release_notes.md) for the v1 → v2
migration guide, numerical fixes, and new functionality.
diff --git a/VERSION b/VERSION
index 157e54f..f1547e6 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.6
+2.0.7
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 6344151..4392617 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc-arm-none-eabi libnewlib-arm-none-eabi \
gcc-riscv64-unknown-elf \
gcc-12-m68k-linux-gnu \
+ gcc-m68hc1x \
sdcc \
wget ca-certificates xz-utils bzip2 \
&& rm -rf /var/lib/apt/lists/*
diff --git a/docker/build_sizes.sh b/docker/build_sizes.sh
index 33cd328..f52ae88 100755
--- a/docker/build_sizes.sh
+++ b/docker/build_sizes.sh
@@ -1,12 +1,14 @@
#!/usr/bin/env bash
#
# build_sizes.sh — cross-compile FR_math.c for every supported target
-# and report code (text section) sizes.
+# and report code (text section) sizes in two configurations:
+# Core = compiled with -DFR_CORE_ONLY (math only, no print, no waves)
+# Full = compiled without FR_CORE_ONLY (everything included)
#
# Run inside the Docker container:
# docker run --rm -v $(pwd):/src fr-math-sizes bash /src/docker/build_sizes.sh
#
-# Output: markdown table to stdout + build/size_table.md
+# Output: build/sizes.csv + markdown table to stdout + build/size_table.md
set -euo pipefail
@@ -14,6 +16,7 @@ SRC="/src/src/FR_math.c"
INC="-I/src/src"
OUT="/src/build/size_report_docker"
TABLE="/src/build/size_table.md"
+CSV="/src/build/sizes.csv"
mkdir -p "${OUT}"
@@ -21,6 +24,7 @@ mkdir -p "${OUT}"
# compile_gcc
# Compiles FR_math.c → .o, extracts text size via `size`.
+# Pass extra flags (including -DFR_CORE_ONLY) via the flags argument.
compile_gcc() {
local label="$1"; shift
local cc="$1"; shift
@@ -52,7 +56,9 @@ compile_gcc() {
# compile_sdcc — SDCC uses different flags and output format.
compile_sdcc() {
- local obj="${OUT}/FR_math_8051"
+ local suffix="$1" # "" or "_core"
+ local extra="$2" # "" or "-DFR_CORE_ONLY"
+ local obj="${OUT}/FR_math_8051${suffix}"
if ! command -v sdcc >/dev/null 2>&1; then
echo "n/a"
@@ -65,7 +71,7 @@ compile_sdcc() {
void main(void) {}
CEOF
- if ! sdcc -mmcs51 --std-c99 --opt-code-size ${INC} \
+ if ! sdcc -mmcs51 --std-c99 --opt-code-size ${extra} ${INC} \
-c "${SRC}" -o "${obj}.rel" 2>/dev/null; then
echo "fail"
return
@@ -88,47 +94,87 @@ CEOF
fi
}
-# ── compile all targets ───────────────────────────────────────────────
-
-declare -A RESULTS
-declare -a ORDER
-
-add_result() {
- local label="$1"
- local value="$2"
- RESULTS["${label}"]="${value}"
- ORDER+=("${label}")
+# ── compile all targets (Core + Full) ────────────────────────────────
+
+# Each entry: target_name, word_width, compile_func args...
+# We store results in parallel arrays.
+declare -a TARGET_NAMES TARGET_WIDTHS CORE_SIZES FULL_SIZES
+
+add_target() {
+ local name="$1"
+ local width="$2"
+ local core="$3"
+ local full="$4"
+ TARGET_NAMES+=("${name}")
+ TARGET_WIDTHS+=("${width}")
+ CORE_SIZES+=("${core}")
+ FULL_SIZES+=("${full}")
}
-echo "Compiling FR_math.c for all targets..."
+echo "Compiling FR_math.c for all targets (Core + Full)..."
echo ""
-add_result "Cortex-M0 (Thumb-1)" \
- "$(compile_gcc cm0 arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb)"
-
-add_result "Cortex-M4 (Thumb-2)" \
- "$(compile_gcc cm4 arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=soft)"
-
-add_result "MSP430" \
- "$(compile_gcc msp430 msp430-elf-gcc -mmcu=msp430f5529)"
-
-add_result "RISC-V 32 (rv32im)" \
- "$(compile_gcc rv32 riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32)"
-
-add_result "ESP32 (Xtensa)" \
- "$(compile_gcc esp32 xtensa-esp-elf-gcc)"
-
-add_result "68k" \
- "$(compile_gcc m68k m68k-linux-gnu-gcc-12)"
-
-add_result "x86-32" \
- "$(compile_gcc x86_32 gcc -m32)"
-
-add_result "x86-64" \
- "$(compile_gcc x86_64 gcc -m64)"
+# --- ARM targets ---
+add_target "RP2040 (Cortex-M0+)" 32 \
+ "$(compile_gcc rp2040_core arm-none-eabi-gcc -DFR_CORE_ONLY -mcpu=cortex-m0plus -mthumb)" \
+ "$(compile_gcc rp2040_full arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb)"
+
+add_target "STM32 (Cortex-M4)" 32 \
+ "$(compile_gcc stm32_core arm-none-eabi-gcc -DFR_CORE_ONLY -mcpu=cortex-m4 -mthumb -mfloat-abi=soft)" \
+ "$(compile_gcc stm32_full arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=soft)"
+
+add_target "Cortex-M0 (Thumb-1)" 32 \
+ "$(compile_gcc cm0_core arm-none-eabi-gcc -DFR_CORE_ONLY -mcpu=cortex-m0 -mthumb)" \
+ "$(compile_gcc cm0_full arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb)"
+
+# --- RISC-V ---
+add_target "RISC-V 32 (rv32im)" 32 \
+ "$(compile_gcc rv32_core riscv64-unknown-elf-gcc -DFR_CORE_ONLY -march=rv32im -mabi=ilp32)" \
+ "$(compile_gcc rv32_full riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32)"
+
+# --- Xtensa ---
+add_target "ESP32 (Xtensa)" 32 \
+ "$(compile_gcc esp32_core xtensa-esp-elf-gcc -DFR_CORE_ONLY)" \
+ "$(compile_gcc esp32_full xtensa-esp-elf-gcc)"
+
+# --- 68k ---
+add_target "68k" 32 \
+ "$(compile_gcc m68k_core m68k-linux-gnu-gcc-12 -DFR_CORE_ONLY)" \
+ "$(compile_gcc m68k_full m68k-linux-gnu-gcc-12)"
+
+# --- x86 ---
+add_target "x86-32" 32 \
+ "$(compile_gcc x86_32_core gcc -DFR_CORE_ONLY -m32)" \
+ "$(compile_gcc x86_32_full gcc -m32)"
+
+add_target "x86-64" 64 \
+ "$(compile_gcc x86_64_core gcc -DFR_CORE_ONLY -m64)" \
+ "$(compile_gcc x86_64_full gcc -m64)"
+
+# --- 16-bit ---
+add_target "MSP430" 16 \
+ "$(compile_gcc msp430_core msp430-elf-gcc -DFR_CORE_ONLY -mmcu=msp430f5529)" \
+ "$(compile_gcc msp430_full msp430-elf-gcc -mmcu=msp430f5529)"
+
+# --- 68HC11 (8-bit) ---
+add_target "68HC11" 8 \
+ "$(compile_gcc hc11_core m68hc11-gcc -DFR_CORE_ONLY)" \
+ "$(compile_gcc hc11_full m68hc11-gcc)"
+
+# --- 8051 ---
+add_target "8051 (SDCC)" 8 \
+ "$(compile_sdcc _core -DFR_CORE_ONLY)" \
+ "$(compile_sdcc _full "")"
+
+# ── write CSV ────────────────────────────────────────────────────────
+
+echo "target,width,core_bytes,full_bytes" > "${CSV}"
+for i in "${!TARGET_NAMES[@]}"; do
+ echo "${TARGET_NAMES[$i]},${TARGET_WIDTHS[$i]},${CORE_SIZES[$i]},${FULL_SIZES[$i]}" >> "${CSV}"
+done
-add_result "8051 (SDCC)" \
- "$(compile_sdcc)"
+echo "CSV saved to ${CSV}"
+echo ""
# ── format output ─────────────────────────────────────────────────────
@@ -140,22 +186,40 @@ fmt_size() {
# Format as X.X KB
local kb
kb=$(awk "BEGIN { printf \"%.1f\", ${val}/1024.0 }")
- echo "${val} B (${kb} KB)"
+ echo "${kb} KB"
fi
}
-# Print to stdout and file
+# Print markdown table sorted by width then full size ascending
{
echo "## FR_Math library size (FR_math.c only, \`-Os\`)"
echo ""
- echo "| Target | Code (text) |"
- echo "|--------|-------------|"
- for label in "${ORDER[@]}"; do
- val="${RESULTS[${label}]}"
- echo "| ${label} | $(fmt_size "${val}") |"
+ echo "| Target | Core | Full |"
+ echo "|--------|-----:|-----:|"
+
+ # Build sortable lines: width,full_bytes,index
+ declare -a SORT_LINES
+ for i in "${!TARGET_NAMES[@]}"; do
+ local_full="${FULL_SIZES[$i]}"
+ # Use 999999 for non-numeric values so they sort last
+ if [[ "${local_full}" =~ ^[0-9]+$ ]]; then
+ sort_key="${local_full}"
+ else
+ sort_key="999999"
+ fi
+ SORT_LINES+=("${TARGET_WIDTHS[$i]} ${sort_key} ${i}")
done
+
+ # Sort by width ascending then by full size ascending
+ sorted=$(printf '%s\n' "${SORT_LINES[@]}" | sort -k1,1n -k2,2n)
+
+ while read -r _width _fsize idx; do
+ echo "| ${TARGET_NAMES[$idx]} | $(fmt_size "${CORE_SIZES[$idx]}") | $(fmt_size "${FULL_SIZES[$idx]}") |"
+ done <<< "${sorted}"
+
echo ""
echo "All sizes are text-section bytes compiled with \`-Os -ffreestanding\`."
+ echo "Core = \`-DFR_CORE_ONLY\` (math only, no print, no waves)."
echo "The optional 2D module (\`FR_math_2D.cpp\`) adds ~1 KB."
} | tee "${TABLE}"
@@ -219,3 +283,4 @@ fi
echo ""
echo "Size table saved to build/size_table.md"
+echo "CSV saved to build/sizes.csv"
diff --git a/docs/README.md b/docs/README.md
index 9401590..0e1dd1a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -46,23 +46,23 @@ radix — Q16.16 is just the reference point for the table. See the
16, and 24. Percent errors skip expected values near zero (|expected| < 0.01).
-| Function | Max err (LSB) | Max err (%) | Avg err (%) | Note |
-|---|---:|---:|---:|---|
-| sin / cos | 7.5 | 0.7169 | 0.0100 | 65536-pt sweep + specials |
-| tan | 38020.4 | 0.7118 | 0.0162 | 65536-pt sweep (skip poles) |
-| asin / acos | 42.3 | 0.7025 | 0.0105 | 65536-pt; sqrt approx near boundary |
-| atan2 | 63.3 | 0.4953 | 0.0268 | 65536x5 radii; asin/acos+hypot_fast8 |
-| atan | 61.9 | 0.2985 | 0.0159 | 20001-pt sweep [-10,10]; via FR_atan2 |
-| sqrt | 28.4 | 0.0003 | 0.0000 | Round-to-nearest |
-| log2 | 10.5 | 0.2479 | 0.0045 | 65-entry mantissa table |
-| pow2 | 220.4 | 0.1373 | 0.0057 | 65-entry fraction table |
-| ln, log10 | 0.7 | 0.0015 | 0.0004 | Via FR_MULK28 from log2 |
-| exp | 65.7 | 0.0719 | 0.0051 | FR_MULK28 + FR_pow2 |
-| exp_fast | 195.5 | 0.0719 | 0.0064 | Shift-only scaling |
-| pow10 | 143.4 | 0.1163 | 0.0075 | FR_MULK28 + FR_pow2 |
-| pow10_fast | 581.9 | 0.1163 | 0.0100 | Shift-only scaling |
-| hypot (exact) | 0.2 | 0.0001 | 0.0000 | 64-bit intermediate |
-| hypot_fast8 (8-seg) | 59968.8 | 0.0977 | 0.0508 | Shift-only, no multiply |
+| Function | Max err (%) | Avg err (%) | Note |
+|---|---:|---:|---|
+| sin / cos | 0.7169 | 0.0100 | 65536-pt sweep + specials |
+| tan | 0.7118 | 0.0162 | 65536-pt sweep (skip poles) |
+| asin / acos | 0.7025 | 0.0105 | 65536-pt; sqrt approx near boundary |
+| atan2 | 0.4953 | 0.0268 | 65536x5 radii; asin/acos+hypot_fast8 |
+| atan | 0.2985 | 0.0159 | 20001-pt sweep [-10,10]; via FR_atan2 |
+| sqrt | 0.0003 | 0.0000 | Round-to-nearest |
+| log2 | 0.2479 | 0.0045 | 65-entry mantissa table |
+| pow2 | 0.1373 | 0.0057 | 65-entry fraction table |
+| ln, log10 | 0.0015 | 0.0004 | Via FR_MULK28 from log2 |
+| exp | 0.0719 | 0.0051 | FR_MULK28 + FR_pow2 |
+| exp_fast | 0.0719 | 0.0064 | Shift-only scaling |
+| pow10 | 0.1163 | 0.0075 | FR_MULK28 + FR_pow2 |
+| pow10_fast | 0.1163 | 0.0100 | Shift-only scaling |
+| hypot (exact) | 0.0001 | 0.0000 | 64-bit intermediate |
+| hypot_fast8 (8-seg) | 0.0977 | 0.0508 | Shift-only, no multiply |
## What's in the box
@@ -86,23 +86,22 @@ Every function is covered by the TDD characterization suite in
## Lean build options
-Two compile-time `#define` guards let you strip optional subsystems
+Three compile-time `#define` guards let you strip optional subsystems
for ROM-constrained targets. Define them before including `FR_math.h`
(or pass `-D` on the compiler command line):
| Define | What it removes | Typical savings |
|---|---|---|
+| `FR_CORE_ONLY` | Everything below (print + waves) | ~1.9 KB |
| `FR_NO_PRINT` | `FR_printNumF`, `FR_printNumD`, `FR_printNumH`, `FR_numstr` | ~1.3 KB |
| `FR_NO_WAVES` | `fr_wave_*` (6 shapes), `fr_adsr_*` (ADSR envelope), `FR_HZ2BAM_INC` | ~0.6 KB |
-With both guards enabled the core math library (trig, inverse trig, log/exp,
-sqrt, hypot) compiles to ~3.5 KB on x86-64 / clang -Os. On Thumb-2 this
-would be roughly 2.6 KB.
+`FR_CORE_ONLY` is a convenience shorthand that defines both
+`FR_NO_PRINT` and `FR_NO_WAVES` in one step.
```c
/* Example: headless sensor node — math only, no print, no audio */
-#define FR_NO_PRINT
-#define FR_NO_WAVES
+#define FR_CORE_ONLY
#include "FR_math.h"
```
@@ -225,7 +224,7 @@ script.
FR_Math has been in service since **2000**, originally built for
graphics transforms on 16 MHz 68k Palm Pilots (it shipped inside
Trumpetsoft's *Inkstorm*), then ported forward to ARM, x86, MIPS,
-RISC-V, and various 8/16-bit embedded targets. v2.0.6 is the current
+RISC-V, and various 8/16-bit embedded targets. v2.0.7 is the current
release with a full test suite, bit-exact numerical
specification, and CI on every push.
diff --git a/docs/building.md b/docs/building.md
index db13bb7..c9f5f21 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -146,6 +146,8 @@ you do *not* need `libm`.
| Windows x86_64 | MSVC, `clang-cl`, MinGW | Manual. |
| AArch64 (ARM64) | `aarch64-linux-gnu-gcc` | Docker. |
| ARM32 / Thumb | `arm-none-eabi-gcc`, IAR, Keil | Docker. |
+| RP2040 (Cortex-M0+) | `arm-none-eabi-gcc` | Docker. |
+| STM32 (Cortex-M4) | `arm-none-eabi-gcc` | Docker. |
| RISC-V rv64 / rv32 | `riscv64-linux-gnu-gcc`, `riscv64-unknown-elf-gcc` | Docker. |
| AVR (ATmega328P, ATtiny85) | `avr-gcc` | Docker. |
| Arduino (AVR, SAMD, etc.) | `arduino-cli` | Manual. |
@@ -158,50 +160,45 @@ you do *not* need `libm`.
### Code size (.text section, compiled with `-Os`)
-All sizes are for the complete `FR_math.c` — every function
-included, nothing stripped. With `-ffunction-sections` and
-linker `--gc-sections`, only the functions your application
-references are linked, so real flash usage will be smaller.
+Sizes are for `FR_math.c` compiled with `-Os -ffreestanding`.
+Core = compiled with `-DFR_CORE_ONLY` (math only, no print, no waves).
+With `-ffunction-sections` and linker `--gc-sections`, only the
+functions your application references are linked, so real flash
+usage will be smaller.
-| Target | .text (bytes) |
-|---|---:|
-| GCC ARM32 Thumb | 4,278 |
-| GCC RISC-V (rv64) | 4,574 |
-| GCC RISC-V (rv32) | 4,820 |
-| GCC Xtensa LX106 (ESP8266) | 5,317 |
-| GCC m68k | 5,410 |
-| GCC ARM32 | 5,504 |
-| GCC x86-64 | 5,857 |
-| GCC AArch64 (ARM64) | 6,112 |
-| Clang x86-64 | 6,555 |
-| GCC x86-32 | 6,947 |
-| GCC PowerPC | 7,540 |
-| GCC MSP430 | 9,146 |
-| TCC x86 | 9,887 |
-| GCC AVR5 (ATmega328P) | 10,806 |
-| GCC AVR ATtiny85 | 11,382 |
-| GCC 68HC11 | 16,392 |
+| Target | Core | Full |
+|--------|-----:|-----:|
+| RP2040 (Cortex-M0+) | 2.6 KB | 4.2 KB |
+| STM32 (Cortex-M4) | 2.6 KB | 4.2 KB |
+| RISC-V 32 (rv32imac) | 3.0 KB | 4.7 KB |
+| ESP32 (Xtensa) | 3.5 KB | 5.2 KB |
+| 68k | 3.5 KB | 5.3 KB |
+| x86-64 (GCC) | 3.5 KB | 5.7 KB |
+| x86-32 | 4.5 KB | 6.8 KB |
+| MSP430 (16-bit) | 5.9 KB | 8.9 KB |
+| 68HC11 | 10.8 KB | 16.0 KB |
+| AVR (ATmega328P) | 7.0 KB | 10.6 KB |
### Lean build options
-Two compile-time `#define` guards let you strip optional subsystems
+Three compile-time `#define` guards let you strip optional subsystems
for ROM-constrained targets. Define them before including `FR_math.h`
(or pass `-D` on the compiler command line):
| Define | What it removes | Typical savings |
|---|---|---|
+| `FR_CORE_ONLY` | Everything below (print + waves) | ~1.9 KB |
| `FR_NO_PRINT` | `FR_printNumF`, `FR_printNumD`, `FR_printNumH`, `FR_numstr` | ~1.3 KB |
| `FR_NO_WAVES` | `fr_wave_*` (6 shapes), `fr_adsr_*` (ADSR envelope), `FR_HZ2BAM_INC` | ~0.6 KB |
-With both guards enabled the core math library (trig, inverse trig, log/exp,
-sqrt, hypot) compiles to ~3.5 KB on x86-64 / clang -Os.
+`FR_CORE_ONLY` is a convenience shorthand that defines both
+`FR_NO_PRINT` and `FR_NO_WAVES` in one step.
```c
/* Example: headless sensor node — math only, no print, no audio */
-#define FR_NO_PRINT
-#define FR_NO_WAVES
+#define FR_CORE_ONLY
#include "FR_math.h"
```
diff --git a/docs/releases.md b/docs/releases.md
index 2568ae7..277b811 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -4,6 +4,19 @@ Release highlights. For the full per-symbol change log, see
[release_notes.md](https://github.com/deftio/fr_math/blob/master/release_notes.md)
in the repo.
+## v2.0.7 — 2026
+
+README restructure, accuracy table cleanup, expanded cross-compile support.
+
+- **`FR_CORE_ONLY` convenience define** — single `#define` strips both print helpers and wave generators
+- **Accuracy table cleanup** — removed LSB column (percent error is the user-facing metric)
+- **New cross-compile targets** — RP2040 (Cortex-M0+), STM32 (Cortex-M4), 68HC11 added to Docker build
+- **Two-column size table** — Core (`-DFR_CORE_ONLY`) vs Full for every target
+- **`scripts/update_sizes.sh`** — auto-patches size tables from `build/sizes.csv`
+- README reordered: accuracy table first, then function list, then size table
+
+---
+
## v2.0.6 — 2026
Accuracy improvements, lean-build options, library cleanup.
@@ -285,6 +298,6 @@ FR_Math has been in continuous service since **2000**,
when it was written to run 2D graphics transforms on 16 MHz 68k
Palm Pilots for Trumpetsoft's *Inkstorm*. It has since
been ported to ARM, x86, MIPS, RISC-V, and a menagerie of 8- and
-16-bit embedded targets. v2.0.0 is the first major revision with a
+16-bit embedded targets. v2.0.7 is the current release with a
full test suite, a bit-exact numerical specification, and CI on
every push.
diff --git a/idf_component.yml b/idf_component.yml
index d5c5d11..6a0d030 100644
--- a/idf_component.yml
+++ b/idf_component.yml
@@ -1,4 +1,4 @@
-version: "2.0.6"
+version: "2.0.7"
description: "Compact fixed-point math library for embedded systems. Integer-only with caller-selectable radix. Trig, log/exp, sqrt, hypot, wave generators, ADSR, and 2D transforms. Zero dependencies."
url: "https://github.com/deftio/fr_math"
repository: "https://github.com/deftio/fr_math.git"
diff --git a/library.json b/library.json
index daa7684..495a89f 100644
--- a/library.json
+++ b/library.json
@@ -1,6 +1,6 @@
{
"name": "FR_Math",
- "version": "2.0.6",
+ "version": "2.0.7",
"description": "Compact fixed-point math library for embedded systems. Integer-only with caller-selectable radix. Trig, log/exp, sqrt, hypot, wave generators, ADSR, and 2D transforms in 4KB of flash. Zero dependencies.",
"keywords": [
"fixed-point",
diff --git a/library.properties b/library.properties
index 47d834a..cd2d953 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=FR_Math
-version=2.0.6
+version=2.0.7
author=M. A. Chatterjee
maintainer=M. A. Chatterjee
sentence=Compact fixed-point math library for embedded systems. 4KB flash, zero dependencies, any radix.
diff --git a/llms.txt b/llms.txt
index fe5ee3b..a6d254f 100644
--- a/llms.txt
+++ b/llms.txt
@@ -9,7 +9,7 @@ or libraries. Pure C99, zero dependencies beyond ``.
- Repository: https://github.com/deftio/fr_math
- Documentation: https://deftio.github.io/fr_math/
- License: BSD-2-Clause
-- Version: 2.0.6
+- Version: 2.0.7
## Key concept: radix parameter
diff --git a/pages/assets/site.js b/pages/assets/site.js
index 8f5df7c..a686d8e 100644
--- a/pages/assets/site.js
+++ b/pages/assets/site.js
@@ -16,7 +16,7 @@
════════════════════════════════════════════════════════════════════ */
(function () {
- var FR_VERSION = 'v2.0.6';
+ var FR_VERSION = 'v2.0.7';
// Detect whether we're a top-level page or inside guide/.
// Works for both file:// and http(s):// because we look for the
diff --git a/pages/guide/building.html b/pages/guide/building.html
index f870b20..10b3739 100644
--- a/pages/guide/building.html
+++ b/pages/guide/building.html
@@ -166,6 +166,8 @@ Cross-compilation
Windows x86_64 MSVC, clang-cl, MinGW Manual.
AArch64 (ARM64) aarch64-linux-gnu-gccDocker.
ARM32 / Thumb arm-none-eabi-gcc, IAR, KeilDocker.
+RP2040 (Cortex-M0+) arm-none-eabi-gccDocker.
+STM32 (Cortex-M4) arm-none-eabi-gccDocker.
RISC-V rv64 / rv32 riscv64-linux-gnu-gcc, riscv64-unknown-elf-gccDocker.
AVR (ATmega328P, ATtiny85) avr-gccDocker.
Arduino (AVR, SAMD, etc.) arduino-cliManual.
@@ -180,55 +182,50 @@ Cross-compilation
Code size (.text section, compiled with -Os)
-All sizes are for the complete FR_math.c — every function
-included, nothing stripped. With -ffunction-sections and
-linker --gc-sections, only the functions your application
-references are linked, so real flash usage will be smaller.
+Sizes are for FR_math.c compiled with -Os -ffreestanding.
+Core = compiled with -DFR_CORE_ONLY (math only, no print, no waves).
+With -ffunction-sections and linker --gc-sections,
+only the functions your application references are linked, so real flash
+usage will be smaller.
-Target .text (bytes)
+Target Core Full
-GCC ARM32 Thumb 4,278
-GCC RISC-V (rv64) 4,574
-GCC RISC-V (rv32) 4,820
-GCC Xtensa LX106 (ESP8266) 5,317
-GCC m68k 5,410
-GCC ARM32 5,504
-GCC x86-64 5,857
-GCC AArch64 (ARM64) 6,112
-Clang x86-64 6,555
-GCC x86-32 6,947
-GCC PowerPC 7,540
-GCC MSP430 9,146
-TCC x86 9,887
-GCC AVR5 (ATmega328P) 10,806
-GCC AVR ATtiny85 11,382
-GCC 68HC11 16,392
+RP2040 (Cortex-M0+) 2.6 KB 4.2 KB
+STM32 (Cortex-M4) 2.6 KB 4.2 KB
+RISC-V 32 (rv32imac) 3.0 KB 4.7 KB
+ESP32 (Xtensa) 3.5 KB 5.2 KB
+68k 3.5 KB 5.3 KB
+x86-64 (GCC) 3.5 KB 5.7 KB
+x86-32 4.5 KB 6.8 KB
+MSP430 (16-bit) 5.9 KB 8.9 KB
+68HC11 10.8 KB 16.0 KB
+AVR (ATmega328P) 7.0 KB 10.6 KB
Lean build options
-Two compile-time #define guards let you strip optional subsystems
+
Three compile-time #define guards let you strip optional subsystems
for ROM-constrained targets. Define them before including
FR_math.h (or pass -D on the compiler command line):
Define What it removes Typical savings
+FR_CORE_ONLYEverything below (print + waves) ~1.9 KB
FR_NO_PRINTFR_printNumF, FR_printNumD, FR_printNumH, FR_numstr~1.3 KB
FR_NO_WAVESfr_wave_* (6 shapes), fr_adsr_* (ADSR envelope), FR_HZ2BAM_INC~0.6 KB
-With both guards enabled the core math library (trig, inverse trig, log/exp,
-sqrt, hypot) compiles to ~3.5 KB on x86-64 / clang -Os.
+FR_CORE_ONLY is a convenience shorthand that defines both
+FR_NO_PRINT and FR_NO_WAVES in one step.
/* Example: headless sensor node — math only, no print, no audio */
-#define FR_NO_PRINT
-#define FR_NO_WAVES
+#define FR_CORE_ONLY
#include "FR_math.h"
With -ffunction-sections and linker --gc-sections,
diff --git a/pages/index.html b/pages/index.html
index 9746d46..ac84759 100644
--- a/pages/index.html
+++ b/pages/index.html
@@ -52,23 +52,23 @@
Measured accuracy
-Function Max err (LSB) Max err (%) Avg err (%) Note
+Function Max err (%) Avg err (%) Note
-sin / cos 7.5 0.7169 0.0100 65536-pt sweep + specials
-tan 38020.4 0.7118 0.0162 65536-pt sweep (skip poles)
-asin / acos 42.3 0.7025 0.0105 65536-pt; sqrt approx near boundary
-atan2 63.3 0.4953 0.0268 65536x5 radii; asin/acos+hypot_fast8
-atan 61.9 0.2985 0.0159 20001-pt sweep [-10,10]; via FR_atan2
-sqrt 28.4 0.0003 0.0000 Round-to-nearest
-log2 10.5 0.2479 0.0045 65-entry mantissa table
-pow2 220.4 0.1373 0.0057 65-entry fraction table
-ln, log10 0.7 0.0015 0.0004 Via FR_MULK28 from log2
-exp 65.7 0.0719 0.0051 FR_MULK28 + FR_pow2
-exp_fast 195.5 0.0719 0.0064 Shift-only scaling
-pow10 143.4 0.1163 0.0075 FR_MULK28 + FR_pow2
-pow10_fast 581.9 0.1163 0.0100 Shift-only scaling
-hypot (exact) 0.2 0.0001 0.0000 64-bit intermediate
-hypot_fast8 (8-seg) 59968.8 0.0977 0.0508 Shift-only, no multiply
+sin / cos 0.7169 0.0100 65536-pt sweep + specials
+tan 0.7118 0.0162 65536-pt sweep (skip poles)
+asin / acos 0.7025 0.0105 65536-pt; sqrt approx near boundary
+atan2 0.4953 0.0268 65536x5 radii; asin/acos+hypot_fast8
+atan 0.2985 0.0159 20001-pt sweep [-10,10]; via FR_atan2
+sqrt 0.0003 0.0000 Round-to-nearest
+log2 0.2479 0.0045 65-entry mantissa table
+pow2 0.1373 0.0057 65-entry fraction table
+ln, log10 0.0015 0.0004 Via FR_MULK28 from log2
+exp 0.0719 0.0051 FR_MULK28 + FR_pow2
+exp_fast 0.0719 0.0064 Shift-only scaling
+pow10 0.1163 0.0075 FR_MULK28 + FR_pow2
+pow10_fast 0.1163 0.0100 Shift-only scaling
+hypot (exact) 0.0001 0.0000 64-bit intermediate
+hypot_fast8 (8-seg) 0.0977 0.0508 Shift-only, no multiply
@@ -246,7 +246,7 @@ History
built for graphics transforms on 16 MHz 68k Palm Pilots (it
shipped inside Trumpetsoft’s Inkstorm ), then ported
forward to ARM, x86, MIPS, RISC-V, and various 8/16-bit embedded
-targets. v2.0.6 is the current release with a full test suite,
+targets. v2.0.7 is the current release with a full test suite,
bit-exact numerical specification, and CI on every push.
License
diff --git a/pages/releases.html b/pages/releases.html
index 9c15d09..337035a 100644
--- a/pages/releases.html
+++ b/pages/releases.html
@@ -5,7 +5,7 @@
Releases — FR_Math
-
+
@@ -21,6 +21,21 @@ Releases
release_notes.md
in the repo.
+v2.0.7 — 2026
+
+README restructure, accuracy table cleanup, expanded cross-compile support.
+
+
+ FR_CORE_ONLY convenience define — single #define strips both print helpers and wave generators
+ Accuracy table cleanup — removed LSB column (percent error is the user-facing metric)
+ New cross-compile targets — RP2040 (Cortex-M0+), STM32 (Cortex-M4), 68HC11 added to Docker build
+ Two-column size table — Core (-DFR_CORE_ONLY) vs Full for every target
+ scripts/update_sizes.sh — auto-patches size tables from build/sizes.csv
+ README reordered: accuracy table first, then function list, then size table
+
+
+
+
v2.0.6 — 2026
Accuracy improvements, lean-build options, library cleanup.
@@ -336,7 +351,7 @@ Timeline
when it was written to run 2D graphics transforms on 16 MHz 68k
Palm Pilots for Trumpetsoft’s Inkstorm . It has since
been ported to ARM, x86, MIPS, RISC-V, and a menagerie of 8- and
-16-bit embedded targets. v2.0.0 is the first major revision with a
+16-bit embedded targets. v2.0.7 is the current release with a
full test suite, a bit-exact numerical specification, and CI on
every push.
diff --git a/release_notes.md b/release_notes.md
index 1de55e2..8a5f3bb 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -1,5 +1,38 @@
# 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.sh` outputs `build/sizes.csv` for automated patching
+- New `scripts/update_sizes.sh` auto-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.
diff --git a/scripts/accuracy_report.sh b/scripts/accuracy_report.sh
index a8426ee..1bd5745 100755
--- a/scripts/accuracy_report.sh
+++ b/scripts/accuracy_report.sh
@@ -89,8 +89,8 @@ patch_markdown() {
# Build replacement block: sentinel + header + separator + data + sentinel
local replacement
replacement=""$'\n'
- replacement+="| Function | Max err (LSB) | Max err (%) | Avg err (%) | Note |"$'\n'
- replacement+="|---|---:|---:|---:|---|"$'\n'
+ replacement+="| Function | Max err (%) | Avg err (%) | Note |"$'\n'
+ replacement+="|---|---:|---:|---|"$'\n'
replacement+="$DATA_ROWS"$'\n'
replacement+=""
@@ -123,8 +123,8 @@ patch_html() {
local cells
cells=$(echo "$line" | sed 's/^| //;s/ |$//' | sed 's/ | /\t/g')
local tr=""
- while IFS=$'\t' read -r c1 c2 c3 c4 c5; do
- tr+="${c1} ${c2} ${c3} ${c4} ${c5} "
+ while IFS=$'\t' read -r c1 c2 c3 c4; do
+ tr+="${c1} ${c2} ${c3} ${c4} "
done <<< "$cells"
tr+=" "
if [ -n "$html_rows" ]; then
@@ -137,7 +137,7 @@ patch_html() {
local replacement
replacement=""$'\n'
replacement+=""$'\n'
- replacement+="Function Max err (LSB) Max err (%) Avg err (%) Note "$'\n'
+ replacement+="Function Max err (%) Avg err (%) Note "$'\n'
replacement+=""$'\n'
replacement+="$html_rows"$'\n'
replacement+=" "$'\n'
diff --git a/scripts/update_sizes.sh b/scripts/update_sizes.sh
new file mode 100755
index 0000000..b696edd
--- /dev/null
+++ b/scripts/update_sizes.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env bash
+#
+# update_sizes.sh — read build/sizes.csv and patch the size table into
+# README.md, docs/building.md, and pages/guide/building.html.
+#
+# Usage:
+# scripts/update_sizes.sh # print table to stdout
+# scripts/update_sizes.sh --update # also patch the three doc files
+#
+# The table is delimited by sentinel comments:
+#
+# ...
+#
+#
+# Exit status: 0 on success, non-zero on missing CSV or extraction failure.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+cd "${PROJECT_ROOT}"
+
+CSV="build/sizes.csv"
+MODE="print"
+
+for arg in "$@"; do
+ case "$arg" in
+ --update) MODE="update" ;;
+ -h|--help)
+ echo "Usage: scripts/update_sizes.sh [--update]"
+ echo " (no args) Read build/sizes.csv, print size table"
+ echo " --update Also patch README.md, docs/building.md, pages/guide/building.html"
+ exit 0
+ ;;
+ *) echo "Unknown option: $arg" >&2; exit 1 ;;
+ esac
+done
+
+if [ ! -f "${CSV}" ]; then
+ echo "ERROR: ${CSV} not found. Run docker/build_sizes.sh first." >&2
+ exit 1
+fi
+
+# -----------------------------------------------------------------------
+# 1. Read CSV and sort by width then full_bytes ascending
+# -----------------------------------------------------------------------
+
+# Skip header, sort numerically by field 2 (width) then field 4 (full_bytes)
+SORTED=$(tail -n +2 "${CSV}" | sort -t',' -k2,2n -k4,4n)
+
+if [ -z "${SORTED}" ]; then
+ echo "ERROR: No data rows in ${CSV}" >&2
+ exit 1
+fi
+
+# Build markdown data rows
+MD_ROWS=""
+while IFS=',' read -r target width core full; do
+ # Format bytes as X.X KB
+ fmt_kb() {
+ local val="$1"
+ if [[ "${val}" =~ ^[0-9]+$ ]]; then
+ awk "BEGIN { printf \"%.1f KB\", ${val}/1024.0 }"
+ else
+ echo "${val}"
+ fi
+ }
+ row="| ${target} | $(fmt_kb "${core}") | $(fmt_kb "${full}") |"
+ if [ -n "${MD_ROWS}" ]; then
+ MD_ROWS+=$'\n'
+ fi
+ MD_ROWS+="${row}"
+done <<< "${SORTED}"
+
+# Build full markdown table
+MD_TABLE=""$'\n'
+MD_TABLE+="| Target | Core | Full |"$'\n'
+MD_TABLE+="|--------|-----:|-----:|"$'\n'
+MD_TABLE+="${MD_ROWS}"$'\n'
+MD_TABLE+=""
+
+echo "${MD_TABLE}"
+
+if [ "${MODE}" != "update" ]; then
+ exit 0
+fi
+
+# -----------------------------------------------------------------------
+# 2. Patch markdown files
+# -----------------------------------------------------------------------
+patch_markdown() {
+ local file="$1"
+ if [ ! -f "$file" ]; then
+ echo " skip: $file not found" >&2
+ return
+ fi
+
+ perl -0777 -i -pe "
+ s{.*?}
+ {${MD_TABLE}}s
+ " "$file"
+
+ echo " patched: $file" >&2
+}
+
+patch_markdown "README.md"
+patch_markdown "docs/building.md"
+
+# -----------------------------------------------------------------------
+# 3. Patch HTML file (pages/guide/building.html)
+# -----------------------------------------------------------------------
+patch_html() {
+ local file="$1"
+ if [ ! -f "$file" ]; then
+ echo " skip: $file not found" >&2
+ return
+ fi
+
+ # Convert sorted CSV rows to HTML rows
+ local html_rows=""
+ while IFS=',' read -r target width core full; do
+ fmt_kb() {
+ local val="$1"
+ if [[ "${val}" =~ ^[0-9]+$ ]]; then
+ awk "BEGIN { printf \"%.1f KB\", ${val}/1024.0 }"
+ else
+ echo "${val}"
+ fi
+ }
+ local tr=" ${target} $(fmt_kb "${core}") $(fmt_kb "${full}") "
+ if [ -n "$html_rows" ]; then
+ html_rows+=$'\n'
+ fi
+ html_rows+="${tr}"
+ done <<< "${SORTED}"
+
+ # Build the replacement block
+ local replacement
+ replacement=""$'\n'
+ replacement+=""$'\n'
+ replacement+="Target Core Full "$'\n'
+ replacement+=""$'\n'
+ replacement+="${html_rows}"$'\n'
+ replacement+=" "$'\n'
+ replacement+="
"$'\n'
+ replacement+=""
+
+ perl -0777 -i -pe "
+ s{.*?}
+ {${replacement}}s
+ " "$file"
+
+ echo " patched: $file" >&2
+}
+
+patch_html "pages/guide/building.html"
+
+echo "Size table updated in all doc files." >&2
diff --git a/src/FR_math.h b/src/FR_math.h
index 562a5d3..6eff284 100644
--- a/src/FR_math.h
+++ b/src/FR_math.h
@@ -32,8 +32,13 @@
#ifndef __FR_Math_h__
#define __FR_Math_h__
-#define FR_MATH_VERSION "2.0.6"
-#define FR_MATH_VERSION_HEX 0x020006 /* major << 16 | minor << 8 | patch */
+#define FR_MATH_VERSION "2.0.7"
+#define FR_MATH_VERSION_HEX 0x020007 /* major << 16 | minor << 8 | patch */
+
+#ifdef FR_CORE_ONLY
+#define FR_NO_PRINT
+#define FR_NO_WAVES
+#endif
#ifdef __cplusplus
extern "C"
diff --git a/src/FR_math_2D.cpp b/src/FR_math_2D.cpp
index 573a09f..c9025b3 100644
--- a/src/FR_math_2D.cpp
+++ b/src/FR_math_2D.cpp
@@ -5,7 +5,7 @@
*
* @copy Copyright (C) <2001-2026>
* @author M A Chatterjee
- * @version 2.0.6 M. A. Chatterjee, cleaned up naming
+ * @version 2.0.7 M. A. Chatterjee, cleaned up naming
*
* This file contains integer math settable fixed point radix math routines for
* use on systems in which floating point is not desired or unavailable.
diff --git a/src/FR_math_2D.h b/src/FR_math_2D.h
index ba8456f..8f16330 100644
--- a/src/FR_math_2D.h
+++ b/src/FR_math_2D.h
@@ -3,7 +3,7 @@
*
* @copy Copyright (C) <2001-2026>
* @author M A Chatterjee
- * @version 2.0.6 M. A. Chatterjee, cleaned up naming
+ * @version 2.0.7 M. A. Chatterjee, cleaned up naming
*
* This file contains integer math settable fixed point radix math routines for
* use on systems in which floating point is not desired or unavailable.
diff --git a/tests/test_tdd.cpp b/tests/test_tdd.cpp
index 80ae526..5a70a0a 100644
--- a/tests/test_tdd.cpp
+++ b/tests/test_tdd.cpp
@@ -113,9 +113,9 @@ static double stats_mean_pct(const stats_t *s) {
static int g_showpeak = 0;
/* Print one accuracy table row, optionally with peak-error input */
-static void acc_row(const char *name, const stats_t *s, double lsb, const char *note) {
- printf("| %s | %.1f | %.4f | %.4f | %s",
- name, s->max_abs_err / lsb, s->max_pct_err, stats_mean_pct(s), note);
+static void acc_row(const char *name, const stats_t *s, const char *note) {
+ printf("| %s | %.4f | %.4f | %s",
+ name, s->max_pct_err, stats_mean_pct(s), note);
if (g_showpeak)
printf(" | %.4g", s->worst_pct_input);
printf(" |\n");
@@ -1784,16 +1784,15 @@ static void section_accuracy_table(void) {
printf("\n");
if (g_showpeak) {
- printf("| Function | Max err (LSB) | Max err (%%) | Avg err (%%) | Note | Peak at |\n");
- printf("|---|---:|---:|---:|---|---:|\n");
+ printf("| Function | Max err (%%) | Avg err (%%) | Note | Peak at |\n");
+ printf("|---|---:|---:|---|---:|\n");
} else {
- printf("| Function | Max err (LSB) | Max err (%%) | Avg err (%%) | Note |\n");
- printf("|---|---:|---:|---:|---|\n");
+ printf("| Function | Max err (%%) | Avg err (%%) | Note |\n");
+ printf("|---|---:|---:|---|\n");
}
const int R = 16;
const double scale = (double)(1L << R);
- const double lsb = 1.0 / scale;
/* Persistent stats so we can print diagnostics after the table */
stats_t st_sincos, st_tan, st_asincos, st_atan2;
@@ -1820,7 +1819,7 @@ static void section_accuracy_table(void) {
stats_add(&st, d, frd(FR_SinI(d), FR_TRIG_OUT_PREC), sin(rad));
stats_add(&st, d, frd(FR_CosI(d), FR_TRIG_OUT_PREC), cos(rad));
}
- acc_row("sin / cos", &st, lsb, "65536-pt sweep + specials");
+ acc_row("sin / cos", &st, "65536-pt sweep + specials");
}
/* --- tan --- */
@@ -1841,7 +1840,7 @@ static void section_accuracy_table(void) {
double rad = d * M_PI / 180.0;
stats_add(&st, d, frd(FR_TanI(d), FR_TRIG_OUT_PREC), tan(rad));
}
- acc_row("tan", &st, lsb, "65536-pt sweep (skip poles)");
+ acc_row("tan", &st, "65536-pt sweep (skip poles)");
}
/* --- asin / acos --- */
@@ -1856,7 +1855,7 @@ static void section_accuracy_table(void) {
rad = FR_acos((s32)i, 15, R);
stats_add(&st, xd, frd(rad, R), acos(xd));
}
- acc_row("asin / acos", &st, lsb, "65536-pt; sqrt approx near boundary");
+ acc_row("asin / acos", &st, "65536-pt; sqrt approx near boundary");
}
/* --- atan2 --- */
@@ -1901,7 +1900,7 @@ static void section_accuracy_table(void) {
s32 r = FR_atan2(fy, fx, R);
stats_add(&st, specials_deg[si], frd(r, R), atan2(y, x));
}
- acc_row("atan2", &st, lsb, "65536x5 radii; asin/acos+hypot_fast8");
+ acc_row("atan2", &st, "65536x5 radii; asin/acos+hypot_fast8");
}
/* --- atan --- */
@@ -1918,7 +1917,7 @@ static void section_accuracy_table(void) {
if (fabs(ref) < 0.01) continue;
stats_add(&st, x, frd(r, R), ref);
}
- acc_row("atan", &st, lsb, "20001-pt sweep [-10,10]; via FR_atan2");
+ acc_row("atan", &st, "20001-pt sweep [-10,10]; via FR_atan2");
}
/* --- sqrt --- */
@@ -1937,7 +1936,7 @@ static void section_accuracy_table(void) {
s32 r = FR_sqrt(fr, R);
stats_add(&st, x, frd(r, R), sqrt(x));
}
- acc_row("sqrt", &st, lsb, "Round-to-nearest");
+ acc_row("sqrt", &st, "Round-to-nearest");
}
/* --- log2 --- */
@@ -1958,7 +1957,7 @@ static void section_accuracy_table(void) {
s32 r = FR_log2(fr, (u16)R, (u16)R);
stats_add(&st, x, frd(r, R), log2(x));
}
- acc_row("log2", &st, lsb, "65-entry mantissa table");
+ acc_row("log2", &st, "65-entry mantissa table");
}
/* --- pow2 --- */
@@ -1971,7 +1970,7 @@ static void section_accuracy_table(void) {
double ref = pow(2.0, x);
stats_add(&st, x, frd(r, R), ref);
}
- acc_row("pow2", &st, lsb, "65-entry fraction table");
+ acc_row("pow2", &st, "65-entry fraction table");
}
/* --- ln, log10 --- */
@@ -1988,7 +1987,7 @@ static void section_accuracy_table(void) {
ref = log10(inputs[i]);
stats_add(&st, inputs[i], frd(r, R), ref);
}
- acc_row("ln, log10", &st, lsb, "Via FR_MULK28 from log2");
+ acc_row("ln, log10", &st, "Via FR_MULK28 from log2");
}
/* --- exp (FR_EXP) --- */
@@ -2002,7 +2001,7 @@ static void section_accuracy_table(void) {
if (ref > 32000.0 || ref < 1e-6) continue; /* skip overflow/underflow */
stats_add(&st, x, frd(r, R), ref);
}
- acc_row("exp", &st, lsb, "FR_MULK28 + FR_pow2");
+ acc_row("exp", &st, "FR_MULK28 + FR_pow2");
}
/* --- exp_fast (FR_EXP_FAST) --- */
@@ -2016,7 +2015,7 @@ static void section_accuracy_table(void) {
if (ref > 32000.0 || ref < 1e-6) continue;
stats_add(&st, x, frd(r, R), ref);
}
- acc_row("exp_fast", &st, lsb, "Shift-only scaling");
+ acc_row("exp_fast", &st, "Shift-only scaling");
}
/* --- pow10 (FR_POW10) --- */
@@ -2030,7 +2029,7 @@ static void section_accuracy_table(void) {
if (ref > 32000.0 || ref < 1e-6) continue;
stats_add(&st, x, frd(r, R), ref);
}
- acc_row("pow10", &st, lsb, "FR_MULK28 + FR_pow2");
+ acc_row("pow10", &st, "FR_MULK28 + FR_pow2");
}
/* --- pow10_fast (FR_POW10_FAST) --- */
@@ -2044,7 +2043,7 @@ static void section_accuracy_table(void) {
if (ref > 32000.0 || ref < 1e-6) continue;
stats_add(&st, x, frd(r, R), ref);
}
- acc_row("pow10_fast", &st, lsb, "Shift-only scaling");
+ acc_row("pow10_fast", &st, "Shift-only scaling");
}
/* --- hypot (exact) --- */
@@ -2061,7 +2060,7 @@ static void section_accuracy_table(void) {
double ref = hypot(cases[i].x, cases[i].y);
stats_add(&st, ref, frd(r, R), ref);
}
- acc_row("hypot (exact)", &st, lsb, "64-bit intermediate");
+ acc_row("hypot (exact)", &st, "64-bit intermediate");
}
/* --- hypot_fast8 (8-seg) --- */
@@ -2078,7 +2077,7 @@ static void section_accuracy_table(void) {
double ref = hypot(cases[i].x, cases[i].y);
if (ref > 0) stats_add(&st, ref, frd(r, R), ref);
}
- acc_row("hypot_fast8 (8-seg)", &st, lsb, "Shift-only, no multiply");
+ acc_row("hypot_fast8 (8-seg)", &st, "Shift-only, no multiply");
}
printf("\n");