Single point of control the math functions that break bitwise
reproducibility across compilers, GPU vendors, and optimization
levels would route through IF adopted: exp, log, sin,
cos, sqrt, pow.
Status (PR-8): the safe_* wrappers (safe_exp, safe_log,
safe_sin, safe_cos, safe_sqrt, safe_pow) are plain
elemental inlines around the bare intrinsic — zero overhead,
codegen identical to writing exp(x) directly. This module
used to carry a second “safe” mode, selected by the
RDB_BITWISE_REPRO build option, that dispatched the
wrappers to the *_polynomial implementations below instead.
That option was removed in PR-8: grep across src//app/
found zero call sites of any safe_* wrapper from a production
kernel (one stale comment, no calls) — the option changed
nothing at all, so it was a lie about a capability nothing used.
What SURVIVES this PR: every *_polynomial function and its
coefficients are correct, unit-tested (tests/test_safe_math.F90,
unconditionally compiled — no #ifdef), and are the raw material
for a future bitwise-reproducibility PR. This module is a
library with no production consumer today — adopting safe_*
in kernels (which would first require re-adding a build-time or
run-time dispatch) is future work, not a promise this module
currently keeps.
Would-be convention (not currently enforced by any build path):
a kernel wanting repro-safety would call the safe_* wrapper
instead of the bare intrinsic. For integer exponents, a**2 /
a**3 etc. compile to multiplications and are already
deterministic — don’t wrap those.
Coverage: safe_exp, safe_log, safe_sin, safe_cos each
have real polynomial paths (Cody-Waite reduction + Horner
Taylor). safe_sqrt is IEEE-correctly-rounded by mandate so
the intrinsic path is already deterministic. safe_pow’s
polynomial path is unused by safe_pow itself (which is the
bare intrinsic **) — it exists for a future
safe_exp(b * safe_log(a)) decomposition.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private, | parameter | :: | COS_C0 | = | 1.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C1 | = | -1.0_wp/2.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C2 | = | 1.0_wp/24.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C3 | = | -1.0_wp/720.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C4 | = | 1.0_wp/40320.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C5 | = | -1.0_wp/3628800.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C6 | = | 1.0_wp/479001600.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C7 | = | -1.0_wp/87178291200.0_wp | |
| real(kind=wp), | private, | parameter | :: | COS_C8 | = | 1.0_wp/20922789888000.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C0 | = | 1.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C1 | = | 1.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C10 | = | 1.0_wp/3628800.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C11 | = | 1.0_wp/39916800.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C12 | = | 1.0_wp/479001600.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C13 | = | 1.0_wp/6227020800.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C2 | = | 1.0_wp/2.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C3 | = | 1.0_wp/6.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C4 | = | 1.0_wp/24.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C5 | = | 1.0_wp/120.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C6 | = | 1.0_wp/720.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C7 | = | 1.0_wp/5040.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C8 | = | 1.0_wp/40320.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_C9 | = | 1.0_wp/362880.0_wp | |
| real(kind=wp), | private, | parameter | :: | EXP_OVERFLOW | = | log(huge(1.0_wp)) | |
| real(kind=wp), | private, | parameter | :: | EXP_UNDERFLOW | = | log(tiny(1.0_wp)) | |
| real(kind=wp), | private, | parameter | :: | INV_LN2 | = | 1.4426950408889634074_wp | |
| real(kind=wp), | private, | parameter | :: | INV_PI_OVER_2 | = | 0.6366197723675813431_wp | |
| real(kind=wp), | private, | parameter | :: | LN2_HI | = | 0.6931471805599452862_wp | |
| real(kind=wp), | private, | parameter | :: | LN2_LO | = | 2.319046813846299558e-17_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C0 | = | 1.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C1 | = | 1.0_wp/3.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C2 | = | 1.0_wp/5.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C3 | = | 1.0_wp/7.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C4 | = | 1.0_wp/9.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C5 | = | 1.0_wp/11.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C6 | = | 1.0_wp/13.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C7 | = | 1.0_wp/15.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C8 | = | 1.0_wp/17.0_wp | |
| real(kind=wp), | private, | parameter | :: | LOG_C9 | = | 1.0_wp/19.0_wp | |
| real(kind=wp), | private, | parameter | :: | PI_OVER_2_HI | = | 1.5707963267948965580_wp | |
| real(kind=wp), | private, | parameter | :: | PI_OVER_2_LO | = | 6.123233995736766036e-17_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C0 | = | 1.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C1 | = | -1.0_wp/6.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C2 | = | 1.0_wp/120.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C3 | = | -1.0_wp/5040.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C4 | = | 1.0_wp/362880.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C5 | = | -1.0_wp/39916800.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C6 | = | 1.0_wp/6227020800.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C7 | = | -1.0_wp/1307674368000.0_wp | |
| real(kind=wp), | private, | parameter | :: | SIN_C8 | = | 1.0_wp/355687428096000.0_wp | |
| real(kind=wp), | private, | parameter | :: | SQRT_HALF | = | 0.7071067811865475244_wp |
cos(x). Plain elemental inline around the intrinsic; see
safe_exp and the module docstring.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
Public only for the unit-test suite (no production module imports it);
ignore when developing production code in other modules.
cos(x) via the same Cody-Waite reduction; the quadrant
map is shifted by 1 vs sin:
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
exp(x). Plain elemental inline around the intrinsic — zero
overhead, codegen identical to writing exp(x) directly. No
production module calls this today (PR-8 removed the
RDB_BITWISE_REPRO dispatch that once routed it to
safe_exp_polynomial, which enabled nothing); see the module
docstring.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
Public only for the unit-test suite (no production module imports it);
ignore when developing production code in other modules.
Cody-Waite range reduction x = k*ln(2) + r, |r| ≤ ln(2)/2,
followed by a 14-term Horner Taylor on exp(r) and an
IEEE-exact 2^k multiply via scale(). Max relative
error ≈ 2-3 ULP across [-709, 709].
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
log(x) (natural log). Plain elemental inline around the
intrinsic; see safe_exp and the module docstring.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
Public only for the unit-test suite (no production module imports it);
ignore when developing production code in other modules.
log(x) via IEEE exponent extraction + atanh-form series.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
Non-integer exponent only. For integer exponents
(a**2, a**3, …), write the multiplication out — the
compiler unrolls those and they’re already deterministic.
Plain elemental inline around the intrinsic; see safe_exp
and the module docstring.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | base | |||
| real(kind=wp), | intent(in) | :: | exponent |
Public only for the unit-test suite (no production module imports it);
ignore when developing production code in other modules.
base^exponent via the identity
base^exponent = exp(exponent * log(base))
Routes through the polynomial log and exp paths so the
result is deterministic across builds.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | base | |||
| real(kind=wp), | intent(in) | :: | exponent |
sin(x). Plain elemental inline around the intrinsic; see
safe_exp and the module docstring.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
Public only for the unit-test suite (no production module imports it);
ignore when developing production code in other modules.
sin(x) via Cody-Waite reduction mod π/2 and a 9-term
Horner Taylor on the reduced argument. The quadrant
k mod 4 picks sin vs cos and a sign:
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
IEEE-correctly-rounded by mandate, so the intrinsic is already bit-identical across compliant compilers.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
Horner cos Taylor on |r| ≤ π/4. 9 even-power terms.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | r |
Horner sin Taylor on |r| ≤ π/4. sin(r) = r * P(r²) where
P is the odd-power polynomial in r² — avoids computing r¹,
r³, r⁵… separately. 9 terms (highest power r¹⁷) gives
< 1e-18 truncation error.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | r |