Files
LithosAnanke/v3/src/math_portable.c
T
Claude 16b9747d33 chore(license): the source headers name the Starship License 1.0 (SL-1.0), as LICENSE does [skip ci]
347 headers said "Licensed under the StarForth License, Version 1.0" -- a
shortening (Captain Bob, 2026-11-08: "License file SL I think I shortened
accidently"); LICENSE is, and has been since the first commit, the
Starship License 1.0 (SL-1.0, SPDX LicenseRef-Starship-1.0). 349 files,
566 occurrences, now "Starship License, Version 1.0 (SL-1.0)".

Left as they are, pending a ruling: docs/patent/ (the patent figures),
docs/formal/ (the citable volumes) and the generated reports under
docs/working/experiments/ (32 files). Comments and license text only:
the amd64 kernel builds, every changed script parses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-08 17:54:24 +00:00

264 lines
9.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the Starship License, Version 1.0 (SL-1.0) (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the Starship License, Version 1.0 (SL-1.0) (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* math_portable.c
*
* Pure 64-bit fixed-point Q48.16 math functions.
* Suitable for embedded environments with no libm or floating-point
* (HISTORICAL: also L4Re, through mid-2026; removed as an active target).
* All arithmetic is integer-only for maximum production performance.
*
* Q48.16 Format:
* - 48-bit signed integer + 16-bit fractional part
* - 1.0 = 65536 (2^16)
* - Precision: 2^-16 ≈ 0.0000153
* - Range: ±140 trillion
*/
#include <stdint.h>
#include "math_portable.h"
/**
* @brief Compute exp(@p x) via Taylor series in Q48.16 fixed-point.
*
* Evaluates the classic Taylor expansion:
* @code
* exp(x) = 1 + x + x²/2! + x³/3! + … (up to 40 terms)
* @endcode
* entirely in 64-bit integer arithmetic using the Q48.16 representation
* (1.0 = 65536 = 2^16).
*
* The function is intentionally scoped to the range the physics engine
* actually uses — the argument to @c erf_q48() is @c -x² times a ratio
* near 1, yielding values in roughly the @c [-20, 0] interval. Outside
* this range the implementation provides coarse-grained clamping:
* - @p x == 0 → returns @c Q48 (1.0 exactly)
* - @p x > 20.0 → saturates at @c Q48 << 8 (approx e^20, avoids overflow)
* - @p x < -100.0 → underflows to 0 (result is negligible)
*
* The term loop exits early on the first term smaller than 1 ULP (the
* series has converged) and guards against intermediate overflow by
* clamping @c result at @c INT64_MAX-1.
*
* @note This is a file-local (@c static) helper; callers are
* @c erf_q48() only.
*
* @param x Input value in Q48.16 format (1.0 = 65536). Intended range:
* approximately [-100×65536, +20×65536].
* @return exp(@p x) in Q48.16 format; 0 on underflow; saturated at
* @c INT64_MAX-1 on overflow.
*/
static int64_t exp_q48(int64_t x) {
const int64_t Q48 = 65536LL; /* 2^16 */
/* Handle extremes */
if (x == 0) return Q48;
if (x > (20LL << 16)) return Q48 << 8; /* Approximate e^20 */
if (x < -(100LL << 16)) return 0; /* Underflow to zero */
/* Taylor series: exp(x) = Σ x^n / n! (in fixed-point) */
int64_t result = Q48; /* Start with 1 */
int64_t term = Q48;
int64_t x_power = x; /* x^1 */
for (int i = 1; i < 40; i++) {
/* term = x^i / i! */
term = (x_power / (int64_t)i) / Q48; /* Scale down to avoid overflow */
if (term > -1 && term < 1) break; /* Negligible */
result += term;
x_power = (x_power * x) / Q48; /* Keep x^n scaled */
/* Guard against overflow */
if (result < 0 && result < (1LL << 62)) break;
if (result > 0 && result > (1LL << 62)) {
result = 9223372036854775806LL; /* Saturate at INT64_MAX-1 */
break;
}
}
return result;
}
/**
* @brief Compute sqrt(@p x) via Newton-Raphson in Q48.16 fixed-point.
*
* Applies the Newton-Raphson recurrence:
* @code
* r_{n+1} = (r_n² + x) / (2 × r_n)
* @endcode
* for up to 20 iterations, with all arithmetic scaled to the Q48.16
* representation (1.0 = 65536). Intermediate products use 8-bit right
* shifts on both operands before squaring to prevent 64-bit overflow:
* @code
* guess_squared = (guess >> 8) * (guess >> 8)
* @endcode
* This sacrifices 16 bits of precision in the squared term, which is
* acceptable because the division by @c 2*guess restores the correct
* order of magnitude. Convergence is declared when successive iterates
* differ by at most 1 in Q48.16 units (sub-nanosecond for timing uses).
*
* Special cases handled without iteration:
* - @p x ≤ 0 → returns 0 (no imaginary results)
* - @p x == 65536 (1.0) → returns 65536 exactly
*
* Initial guess: @c x/2 for @p x > 1.0, else @c 1.0. Values above 1.0
* converge from above; values in (0, 1.0) converge from the 1.0 initial
* guess.
*
* @param x Non-negative value in Q48.16 format (1.0 = 65536).
* Negative values are treated as zero.
* @return sqrt(@p x) in Q48.16 format; 0 for @p x ≤ 0.
*/
int64_t sqrt_q48(int64_t x) {
const int64_t Q48 = 65536LL;
/* Handle special cases */
if (x < 0) return 0;
if (x == 0) return 0;
if (x == Q48) return Q48; /* sqrt(1.0) = 1.0 */
/* Initial guess: average of x and 1 */
int64_t guess = (x > Q48) ? (x >> 1) : Q48;
/* Newton-Raphson iterations */
for (int i = 0; i < 20; i++) {
/* next = (guess^2 + x) / (2 * guess)
* In Q48.16: (guess^2) >> 16 + x, then divide by 2*guess
*/
int64_t guess_squared = (guess >> 8) * (guess >> 8); /* Avoid overflow */
int64_t next = (guess_squared + (x >> 16)) / (2 * guess);
/* Check convergence */
int64_t diff = next - guess;
if (diff > -2 && diff < 2) return next;
guess = next;
}
return guess;
}
/**
* @brief Compute erf(@p x) via Abramowitz & Stegun 7.1.26 in Q48.16 fixed-point.
*
* Implements the rational approximation from Abramowitz & Stegun, formula 7.1.26:
* @code
* erf(x) ≈ sign(x) × sqrt(1 − exp(−x² × (4/π + a·x²) / (1 + a·x²)))
* @endcode
* where @c a ≈ 0.147 (in Q48.16: 9633). The maximum error of the true
* mathematical approximation is @c |ε| < 1.5×10⁻⁷; the fixed-point
* encoding introduces additional rounding of approximately 1 ULP in Q48.16.
*
* All arithmetic is performed in 64-bit integer Q48.16 format — no
* floating-point operations. The computation sequence:
* 1. Extract sign; work with @c |x|.
* 2. Compute @c x² with 8-bit prescaling to prevent overflow:
* @c x_sq = (x >> 8) * (x >> 8).
* 3. Evaluate the A&S numerator @c (4/π + a·x²) and denominator @c (1 + a·x²).
* 4. Compute @c ratio = numerator / denominator (shift to preserve Q48.16 scale).
* 5. Compute @c exp_arg = −x² × ratio and evaluate @c exp_q48(exp_arg).
* 6. Compute @c base = 1 − exp_val, clamp to [0, 1].
* 7. Return @c sign × sqrt_q48(base).
*
* Q48.16 constants used:
* - @c a_q48 = 9633 (0.147 × 65536)
* - @c four_over_pi_q48 = 83328 (4/π × 65536 ≈ 1.2732 × 65536)
*
* Consumed by @c hotwords_speedup_estimate() to compute P(speedup > 1.1×)
* and P(speedup > 2.0×) via the normal CDF identity:
* @code
* P(Z ≤ z) = 0.5 × (1 + erf(z / √2))
* @endcode
* where @c √2 in Q48.16 = 92681.
*
* @param x Input value in Q48.16 format (dimensionless Z-score;
* 1.0 = 65536). May be negative; sign determines the sign
* of the result.
* @return erf(@p x) in Q48.16 format, in the range [−65536, +65536]
* corresponding to (−1.0, +1.0).
*/
int64_t erf_q48(int64_t x) {
const int64_t Q48 = 65536LL;
if (x == 0) return 0;
/* Compute sign and work with absolute value */
int sign = (x < 0) ? -1 : 1;
if (x < 0) x = -x;
/* Abramowitz & Stegun coefficient a ≈ 0.147 (in Q48.16) */
const int64_t a_q48 = 9633LL; /* 0.147 * 65536 */
const int64_t four_over_pi_q48 = 83328LL; /* 4/π * 65536 */
/* Compute x^2 in Q48.16 */
int64_t x_sq = (x >> 8) * (x >> 8); /* Scaled to avoid overflow */
/* Compute denominator: 1 + a*x^2 */
int64_t denom = Q48 + ((a_q48 * x_sq) >> 16);
if (denom == 0) denom = 1; /* Guard divide-by-zero */
/* Compute numerator: 4/π + a*x^2 */
int64_t numer = four_over_pi_q48 + ((a_q48 * x_sq) >> 16);
/* Compute ratio = numerator / denominator */
int64_t ratio = (numer << 16) / denom;
/* Compute exp argument: -x^2 * ratio */
int64_t exp_arg = -(x_sq * ratio / Q48);
/* Compute exp(-x^2 * ratio) */
int64_t exp_val = exp_q48(exp_arg);
/* Compute base: 1 - exp(exp_arg) */
int64_t base = Q48 - exp_val;
if (base < 0) base = 0;
if (base > Q48) base = Q48;
/* Compute sqrt(base) */
int64_t sqrt_base = sqrt_q48(base);
/* Apply sign and return */
return sign * sqrt_base;
}