diff --git a/doc/source/fixed.rst b/doc/source/fixed.rst index 0f1662f600..d97b708bb7 100644 --- a/doc/source/fixed.rst +++ b/doc/source/fixed.rst @@ -18,6 +18,62 @@ This module is mainly optimized for 64-bit systems. With 32-bit limbs, some generated straight-line and register implementations are disabled and evaluation goes through generic and fallback code paths. +Arithmetic +------------------------------------------------------------------------------- + +Newton-based division and square root +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. function:: void fixed_inv_newton_basecase(nn_ptr q, nn_srcptr a, slong an, slong n) + void fixed_inv_newton(nn_ptr q, nn_srcptr a, slong an, slong n) + + Given `(a, an)` with `a_{an-1} \ne 0` representing a fixed-point number + `a \in [1/B, 1)` with `an` fraction limbs, sets `(q, n+2)` to an + approximation of `1/a \in (1, B]` with `n` fraction limbs and two + integral limbs (the highest limb may be zero). The absolute error is + bounded by `4 B^{-n} / a`. The *newton* suffix flags that the result + is not ulp-accurate. The basecase divides by ``mpn_tdiv_qr``; the + main function runs a Newton iteration on middle products, ported + from :func:`radix_inv_approx`. + +.. function:: void fixed_div_newton_invmul(nn_ptr q, nn_srcptr b, slong bn, nn_srcptr a, slong an, slong n) + void fixed_div_newton(nn_ptr q, nn_srcptr b, slong bn, nn_srcptr a, slong an, slong n) + + Given a numerator `(b, bn)` with `bn \ge 1` fraction limbs representing + `b \in [0, 1)` and a denominator `(a, an)` with `a_{an-1} \ne 0` + representing `a \in [1/B, 1)`, sets `(q, n+2)` to an approximation of + `b/a` with `n` fraction limbs and two integral limbs. The absolute + error is bounded by `4 B^{-n} / a`. The *invmul* variant multiplies + the numerator by :func:`fixed_inv_newton`; the main function performs + a Karp-Markstein iteration, ported from :func:`radix_div_approx`. + +.. function:: void fixed_rsqrt_ui_newton_basecase(nn_ptr res, ulong a, slong n) + void fixed_rsqrt_ui_newton(nn_ptr res, ulong a, slong n) + + Sets `(res, n)` to the fraction limbs of an approximation of + `1/\sqrt{a}`, requiring `2 \le a < B`. The error is bounded by + `2 B^{-n}`. Ported from :func:`radix_rsqrt_1_approx`. + +.. function:: void fixed_rsqrt_newton_basecase(nn_ptr q, nn_srcptr a, slong an, slong n) + void fixed_rsqrt_newton(nn_ptr q, nn_srcptr a, slong an, slong n) + + Given `(a, an)` representing `a \in [B^{-2}, 1)` with `an` fraction + limbs (at least one of the two highest limbs must be nonzero), sets + `(q, n+2)` to an approximation of `1/\sqrt{a} \in (1, B]` with `n` + fraction limbs and two integral limbs. The absolute error is bounded + by `4 B^{-n} / \sqrt{a}`. Ported from :func:`radix_rsqrt_approx`; + the basecase combines ``mpn_sqrtrem`` and ``mpn_tdiv_qr``. + +.. function:: void fixed_sqrt_newton_rsqrtmul(nn_ptr q, nn_srcptr a, slong an, slong n) + void fixed_sqrt_newton(nn_ptr q, nn_srcptr a, slong an, slong n) + + Input as for :func:`fixed_rsqrt_newton`; sets `(q, n+2)` to an + approximation of `\sqrt{a} \in [1/B, 1)` (the computed value can + round up to 1) with absolute error bounded by `4 B^{-n} / \sqrt{a}`. + Note that the error is proportional to `1/\sqrt{a}` rather than to + the output. The main function performs a Karp-Markstein iteration, + ported from :func:`radix_sqrt_approx`. + Elementary functions ------------------------------------------------------------------------------- @@ -97,6 +153,23 @@ On 32-bit systems, it is assumes that `n \ge 2`. all work happens at the output precision; callers wanting sub-ulp accuracy should pad the precision by one limb themselves. +.. function:: void fixed_exp_reduced(nn_ptr y, nn_srcptr t, slong wn, flint_bitcnt_t r, int alg) + + Sets `(y, wn + 1)` (``wn`` fraction limbs and a units limb) to an + approximation of `\exp(t)` for a reduced argument `(t, wn)` with + `t < 2^{-r}`, `r \ge 16`, independent of any particular argument + reduction (algorithms 1 and 2 additionally require + `r \ge 32`). The error is at most ``FIXED_EXP_REDUCED_MAX_ERR`` + ulps. *alg* selects the internal method: 0 the tuned automatic + choice, 1 the direct rectangular-splitting series, 2 the sinh + series plus a square root, 3 one bit-burst step (the leading + slice of *t*, on limb boundaries, evaluated by binary splitting + and the remainder by the sinh series at the doubled rate), 4 the + full bit-burst algorithm with slice lengths doubling, which is + asymptotically quasi-optimal for very large ``wn``. The + automatic thresholds can be recalibrated with + ``tune/tune-exp-reduced``. + .. function:: void fixed_exp_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r) Sets `(res, n + 1)` to an approximation of `\exp((x, n))` for any @@ -327,6 +400,32 @@ On 32-bit systems, it is assumes that `n \ge 2`. four small multiplications, and `\cos t'` cancels in every ratio just as `|W|` does. +.. function:: void fixed_log1p_2mexp_ui_bs(nn_ptr res, ulong i, slong n) + void fixed_atan_2mexp_ui_bs(nn_ptr res, ulong i, slong n) + + Sets `(res, n)` to a one-sided fixed-point approximation of + `\log(1 + 2^{-i})` resp. `\operatorname{atan}(2^{-i})`, `i \ge 1`: + at most the true value, short by no more than a couple of ulps. + These build the entries of the cached reduction tables (which call + them with one guard limb) by binary splitting in mpn arithmetic: + the split products are carried as truncated mantissas with + limb-radix exponents, an iterative basecase accumulates blocks of + terms with shifts and single-limb multiplications, and every + truncation rounds numerators down and denominators up so that the + final truncating division stays one-sided. The logarithm sums + `2\operatorname{atanh}(1/(2^{i+1}+1))`, carrying one `q^2` factor + per TERM in the materialized denominator so that ranges compose + without any `q`-power at the merges; the `q^2` multiplications + all happen at the leaves, each a single ``mpn_mul_1`` whenever + `q^2` fits in a limb. At high precision the direct `\log(1+x)` + series takes over, its larger term count offset by purely dyadic + merges. + + The smallest indices of the tables (`i \le 6` for the logarithms, + `i \le 3` for the angles) are instead combined from + `\log 2, \log 3, \ldots` resp. Gauss-machin style atans, computed + by generic binary splitting. + The reduction parameter selected by `r = 0` is tuned in two tiers. @@ -381,3 +480,4 @@ size is called once before timing so that table precomputation stays out of the measurement, and the timing loop cycles over an array of random inputs so that the branchy reductions pay their real misprediction costs. + diff --git a/src/fixed.h b/src/fixed.h index 9daf55c184..1c364bba2e 100644 --- a/src/fixed.h +++ b/src/fixed.h @@ -13,6 +13,7 @@ #define FIXED_H #include "flint.h" +#include "arb_types.h" #ifdef __cplusplus extern "C" { @@ -67,6 +68,16 @@ void fixed_exp_rs(nn_ptr res, nn_srcptr x, slong n); the output precision, so the error bound grows linearly with r; callers wanting sub-ulp accuracy should pad the precision by one limb themselves. */ +/* exp(t) of a reduced argument t < 2^-r, r >= 32, into + (y, wn + 1): wn fraction limbs and a units limb. alg: 0 = tuned + automatic choice, 1 = direct rectangular-splitting series, + 2 = sinh series + square root, 3 = one bit-burst step + sinh, + 4 = full bit-burst. Error at most FIXED_EXP_REDUCED_MAX_ERR + ulps. */ +#define FIXED_EXP_REDUCED_MAX_ERR 96 +void fixed_exp_reduced(nn_ptr y, nn_srcptr t, slong wn, + flint_bitcnt_t r, int alg); + void fixed_exp_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r); /* fully specialized per-size implementations (default dispatch; @@ -237,6 +248,7 @@ void _fixed_exp_rs_fallback(nn_ptr res, nn_srcptr x, slong n); this thread. */ void _fixed_exp_logs_ensure(slong nv, slong rc); nn_srcptr _fixed_exp_logs_entry(slong i, slong n); +void _fixed_exp_logs_clear(void); slong _fixed_exp_logs_max_index(void); /* Internal: number of slots the used array of _fixed_bitwise_reduce @@ -261,7 +273,49 @@ slong _fixed_bitwise_reduce(nn_ptr t, slong wn, int r, slong istart, fixed_tan_bitwise_rs and fixed_atan_bitwise_rs. Storage and accessors work exactly as for the logarithm table above. */ void _fixed_atans_ensure(slong nv, slong rc); + +/* Internal: n-limb one-sided fixed-point approximations of the table + values by mpn binary splitting (at most the true value, short by a + couple of ulps); i >= 1. */ +void fixed_atan_2mexp_ui_bs(nn_ptr res, ulong i, slong n); +void fixed_log1p_2mexp_ui_bs(nn_ptr res, ulong i, slong n); + +/* Approximate (not ulp-accurate) fixed-point inversion, + division and square roots by Newton / Karp-Markstein iteration on + middle products; ports of the radix_*_approx functions. Writing + B = 2^64: + + fixed_inv_newton: given (a, an), a_{an-1} != 0, representing + a in [1/B, 1) with an fraction limbs, sets (q, n+2) to 1/a in + (1, B] with n fraction limbs and two integral limbs (the top limb + may be zero); |error| <= 4 B^-n / a. + + fixed_div_newton: numerator (b, bn) in [0, 1), denominator + (a, an) in [1/B, 1) with a_{an-1} != 0; sets (q, n+2) to b/a with + n fraction limbs and two integral limbs; |error| <= 4 B^-n / a. + + fixed_rsqrt_ui_newton: 2 <= a < B; sets (res, n) to the fraction + limbs of 1/sqrt(a); |error| <= 2 B^-n. + + fixed_rsqrt_newton: (a, an) in [B^-2, 1), one of the two top limbs + nonzero; sets (q, n+2) to 1/sqrt(a) in (1, B] with n fraction + limbs and two integral limbs; |error| <= 4 B^-n / sqrt(a). + + fixed_sqrt_newton: input as for fixed_rsqrt_newton; sets (q, n+2) + to sqrt(a) in [1/B, 1) (the value can round up to 1); + |error| <= 4 B^-n / sqrt(a). */ +void fixed_inv_newton_basecase(nn_ptr q, nn_srcptr a, slong an, slong n); +void fixed_inv_newton(nn_ptr q, nn_srcptr a, slong an, slong n); +void fixed_div_newton_invmul(nn_ptr q, nn_srcptr b, slong bn, nn_srcptr a, slong an, slong n); +void fixed_div_newton(nn_ptr q, nn_srcptr b, slong bn, nn_srcptr a, slong an, slong n); +void fixed_rsqrt_ui_newton_basecase(nn_ptr res, ulong a, slong n); +void fixed_rsqrt_ui_newton(nn_ptr res, ulong a, slong n); +void fixed_rsqrt_newton_basecase(nn_ptr q, nn_srcptr a, slong an, slong n); +void fixed_rsqrt_newton(nn_ptr q, nn_srcptr a, slong an, slong n); +void fixed_sqrt_newton_rsqrtmul(nn_ptr q, nn_srcptr a, slong an, slong n); +void fixed_sqrt_newton(nn_ptr q, nn_srcptr a, slong an, slong n); nn_srcptr _fixed_atans_entry(slong i, slong n); +void _fixed_atans_clear(void); slong _fixed_atans_max_index(void); void _fixed_sin_cos_rs_fallback(nn_ptr ysin, nn_ptr ycos, nn_srcptr x, slong n, int alternating); @@ -289,6 +343,110 @@ void _fixed_atan_rs_fallback(nn_ptr res, nn_srcptr x, slong n, it in tan_bitwise_rs.c. */ #endif + +/* -------------------------------------------------------------- */ +/* Internal declarations (formerly fixed/impl.h), placed here so + that the test, tune and profile subdirectories build without + extra include paths. */ + +/* Library-internal view of the cached reduction tables. + + These thread-local objects are shared across the translation units + of the module (the dispatch files, the reduction, and the + specialized per-size implementations) but are deliberately NOT + declared in fixed.h: Windows DLLs cannot export thread-local data, + so external consumers -- the test suite -- go through the + _fixed_exp_logs_entry / _fixed_atans_entry accessors instead. + + Each entry occupies _fixed_{exp_logs,atans}_n limbs: the value + limbs with one guard limb below them. Consumers wanting the top n + limbs of entry i read tab + i * stride + (stride - n). */ + +#define FIXED_STATIC_TAB_INLINE static inline + +#ifdef __cplusplus +extern "C" { +#endif + +extern FLINT_TLS_PREFIX nn_ptr _fixed_exp_logs; +extern FLINT_TLS_PREFIX slong _fixed_exp_logs_n; +extern FLINT_TLS_PREFIX slong _fixed_exp_logs_r; + +extern FLINT_TLS_PREFIX nn_ptr _fixed_atans; +extern FLINT_TLS_PREFIX slong _fixed_atans_n; +extern FLINT_TLS_PREFIX slong _fixed_atans_r; + +/* Static prefixes of the two tables covering all reductions with + r <= FIXED_STATIC_TAB_R whose per-entry reads fit in + FIXED_STATIC_TAB_N limbs. Unlike the dynamic tables, whose + bottom limb is a guard used for in-place generation, every stored + limb here is a value limb (the entries are the top limbs of a + dynamic table built one limb deeper). The accessors below hand + out the static data when it suffices -- avoiding the + precomputation, the TLS lookup, and the wide entry stride of a + high-precision dynamic table -- and fall back to ensuring the + dynamic one. */ + +#define FIXED_STATIC_TAB_N 12 +#define FIXED_STATIC_TAB_R 32 + +/* internal: mpn binary splitting for sum_{k=1}^N x^k / (k! 2^(rk)) + (exp_sum_bs.c); T needs (N (r + 128))/64 + 4 limbs, Q needs + (N bits(N+1))/64 + 3 */ +slong _fixed_exp_bs_num_terms(flint_bitcnt_t r, slong prec); +void _fixed_exp_burst_factor(nn_ptr F, slong * fn, slong * fexp, + nn_srcptr T, slong tn, nn_srcptr Q, slong qn, + flint_bitcnt_t Qexp, slong cap); +void _fixed_exp_sum_bs_powtab(nn_ptr T, slong * tn, nn_ptr Q, + slong * qn, flint_bitcnt_t * Qexp, nn_srcptr xp, slong xn, + flint_bitcnt_t r, slong N); + +/* internal: exact-floor entry helpers (tab_exact.c) */ +int _fixed_tab_store_floor(nn_ptr e, const arb_t x, slong nc, slong prec); +void _fixed_tab_entry_exact(nn_ptr e, int which, ulong i, slong nc); + +/* fast-path entries sit at most a few guard ulps below the truth; + a guard limb this close to wrapping means the deficit may have + borrowed into the value limbs, so the entry is recomputed exactly */ +#define FIXED_TAB_GUARD_SLACK UWORD(1024) + +FLINT_DLL extern const ulong _fixed_exp_logs_static[(FIXED_STATIC_TAB_R + 1) * FIXED_STATIC_TAB_N]; +FLINT_DLL extern const ulong _fixed_atans_static[(FIXED_STATIC_TAB_R + 1) * FIXED_STATIC_TAB_N]; + +/* NOTE: the two inline _tab accessors below dereference the + thread-local table data and are therefore usable only from + translation units compiled INTO the library. Code linking + against a Windows DLL (tests, tuning and profiling programs) + must use the exported _fixed_*_entry / _fixed_*_ensure / + _fixed_*_clear functions instead: thread-local data cannot be + DLL-exported, so direct references fail to link there. */ + +FIXED_STATIC_TAB_INLINE nn_srcptr +_fixed_exp_logs_tab(slong nv, slong rc, slong * nc) +{ + if (rc <= FIXED_STATIC_TAB_R && nv + 1 <= FIXED_STATIC_TAB_N) + { + *nc = FIXED_STATIC_TAB_N; + return _fixed_exp_logs_static; + } + _fixed_exp_logs_ensure(nv, rc); + *nc = _fixed_exp_logs_n; + return _fixed_exp_logs; +} + +FIXED_STATIC_TAB_INLINE nn_srcptr +_fixed_atans_tab(slong nv, slong rc, slong * nc) +{ + if (rc <= FIXED_STATIC_TAB_R && nv + 1 <= FIXED_STATIC_TAB_N) + { + *nc = FIXED_STATIC_TAB_N; + return _fixed_atans_static; + } + _fixed_atans_ensure(nv, rc); + *nc = _fixed_atans_n; + return _fixed_atans; +} + #ifdef __cplusplus } #endif diff --git a/src/fixed/atan_bitwise_rs.c b/src/fixed/atan_bitwise_rs.c index d294daa3b2..5d6365fefe 100644 --- a/src/fixed/atan_bitwise_rs.c +++ b/src/fixed/atan_bitwise_rs.c @@ -12,7 +12,15 @@ #include "flint.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" + +/* quotient length from which the argument-reduction division runs + through fixed_div_newton instead of mpn_tdiv_qr; set from + in-context measurements (tight-loop benchmarks favor Newton much + earlier, but embedded in the evaluation it loses until the FFT + range, see dev/notes) */ +#ifndef FIXED_RED_DIV_NEWTON_CUTOFF +#define FIXED_RED_DIV_NEWTON_CUTOFF 60 +#endif /* atan(t) for t in [0, 1) by greedy vectoring (the trigonometric analog of the fixed_log1p_bitwise_rs reduction): the vector @@ -53,7 +61,7 @@ the series error (<= 15 ulp two-sided) are constants. Total below FIXED_ATAN_BITWISE_RS_MAX_ERR(n, r) = 4 r + 64. */ -#define ATANS_A(i) (_fixed_atans + (i) * _fixed_atans_n) +#define ATANS_A(i) (tab + (i) * nc) /* smallest n at which each reduction parameter becomes optimal (generated by src/fixed/tune/tune-bitwise-r.c with nmax deep enough @@ -134,8 +142,8 @@ fixed_atan_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r) wn = n + 1; - _fixed_atans_ensure(n, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(n, r, &nc); TMP_START; X = TMP_ALLOC((wn + n + wn + wn + wn + n + n + 2 * n + 2) @@ -270,7 +278,25 @@ fixed_atan_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r) { flint_mpn_zero(nd, n); flint_mpn_copyi(nd + n, Y, n - qr); - mpn_tdiv_qr(t, nd, 0, nd, ds, X, wn); + if (ds - wn + 1 < FIXED_RED_DIV_NEWTON_CUTOFF) + { + mpn_tdiv_qr(t, nd, 0, nd, ds, X, wn); + } + else + { + /* Same quotient by Karp-Markstein division: numerator + and divisor read as fractions (the numerator's n low + limbs are zero, so pass only its significant top + part), quotient computed with k + 1 fraction limbs, k + the quotient length. Truncating the two guard limbs + leaves t within 1 + eps ulps of the floored quotient + (eps = 4 B^(-1) / a' < 2^-62), which the series + argument budget absorbs like the tdiv floor. */ + slong k = ds - wn + 1; + nn_ptr q = TMP_ALLOC((k + 4) * sizeof(ulong)); + fixed_div_newton(q, nd + n, ds - n, X, wn, k + 1); + flint_mpn_copyi(t, q + 2, k); + } } /* res = acc + atan(t') (the series does not support aliased diff --git a/src/fixed/atan_opt_1.c b/src/fixed/atan_opt_1.c index 5db6b27012..1cbc3d028d 100644 --- a/src/fixed/atan_opt_1.c +++ b/src/fixed/atan_opt_1.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -84,14 +83,14 @@ fixed_atan_opt_1(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(1, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(1, r, &nc); x0 = 0; y0 = x[0]; a0 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 1)) +#define AP(ii) (tab + (ii) * nc + (nc - 1)) /* window 0 */ { diff --git a/src/fixed/atan_opt_2.c b/src/fixed/atan_opt_2.c index acdf38b667..27c48bac6f 100644 --- a/src/fixed/atan_opt_2.c +++ b/src/fixed/atan_opt_2.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -92,8 +91,8 @@ fixed_atan_opt_2(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(2, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(2, r, &nc); x0 = 0; x1 = 0; @@ -102,7 +101,7 @@ fixed_atan_opt_2(nn_ptr res, nn_srcptr x) a0 = 0; a1 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 2)) +#define AP(ii) (tab + (ii) * nc + (nc - 2)) /* window 0 */ { diff --git a/src/fixed/atan_opt_3.c b/src/fixed/atan_opt_3.c index d7c72aac1b..4ab8870d71 100644 --- a/src/fixed/atan_opt_3.c +++ b/src/fixed/atan_opt_3.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -69,8 +68,8 @@ fixed_atan_opt_3(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(3, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(3, r, &nc); x0 = 0; x1 = 0; @@ -82,7 +81,7 @@ fixed_atan_opt_3(nn_ptr res, nn_srcptr x) a1 = 0; a2 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 3)) +#define AP(ii) (tab + (ii) * nc + (nc - 3)) /* window 0 */ { diff --git a/src/fixed/atan_opt_4.c b/src/fixed/atan_opt_4.c index f28a606630..0b75490484 100644 --- a/src/fixed/atan_opt_4.c +++ b/src/fixed/atan_opt_4.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -90,8 +89,8 @@ fixed_atan_opt_4(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(4, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(4, r, &nc); x0 = 0; x1 = 0; @@ -106,7 +105,7 @@ fixed_atan_opt_4(nn_ptr res, nn_srcptr x) a2 = 0; a3 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 4)) +#define AP(ii) (tab + (ii) * nc + (nc - 4)) /* window 0 */ { diff --git a/src/fixed/atan_opt_5.c b/src/fixed/atan_opt_5.c index eb72fbe351..055241e481 100644 --- a/src/fixed/atan_opt_5.c +++ b/src/fixed/atan_opt_5.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -121,8 +120,8 @@ fixed_atan_opt_5(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(5, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(5, r, &nc); x0 = 0; x1 = 0; @@ -140,7 +139,7 @@ fixed_atan_opt_5(nn_ptr res, nn_srcptr x) a3 = 0; a4 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 5)) +#define AP(ii) (tab + (ii) * nc + (nc - 5)) /* window 0 */ { diff --git a/src/fixed/atan_opt_6.c b/src/fixed/atan_opt_6.c index 16a0006cca..6afd6202a6 100644 --- a/src/fixed/atan_opt_6.c +++ b/src/fixed/atan_opt_6.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -134,8 +133,8 @@ fixed_atan_opt_6(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(6, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(6, r, &nc); x0 = 0; x1 = 0; @@ -156,7 +155,7 @@ fixed_atan_opt_6(nn_ptr res, nn_srcptr x) a4 = 0; a5 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 6)) +#define AP(ii) (tab + (ii) * nc + (nc - 6)) /* window 0 */ { diff --git a/src/fixed/atan_opt_7.c b/src/fixed/atan_opt_7.c index e50c4813f0..179ba3bbe7 100644 --- a/src/fixed/atan_opt_7.c +++ b/src/fixed/atan_opt_7.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -173,8 +172,8 @@ fixed_atan_opt_7(nn_ptr res, nn_srcptr x) int nz; - _fixed_atans_ensure(7, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(7, r, &nc); x0 = 0; x1 = 0; @@ -198,7 +197,7 @@ fixed_atan_opt_7(nn_ptr res, nn_srcptr x) a5 = 0; a6 = 0; -#define AP(ii) (_fixed_atans + (ii) * nc + (nc - 7)) +#define AP(ii) (tab + (ii) * nc + (nc - 7)) /* window 0 */ { diff --git a/src/fixed/div_newton.c b/src/fixed/div_newton.c new file mode 100644 index 0000000000..51164e8996 --- /dev/null +++ b/src/fixed/div_newton.c @@ -0,0 +1,337 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "flint.h" +#include "mpn_extras.h" +#include "fixed.h" + +/* Approximate fixed-point inversion and division, ported from + radix_inv_approx / radix_div_approx onto mpn arithmetic (the + radix-B generality drops out: with B = 2^64 only the + middle-product branches survive, division by two is a shift, and + the binary basecases need no radix conversion). The _newton suffix + flags the contracts: not ulp-accurate, two-sided errors of a few + ulps as documented in fixed.h. + + The Newton and Karp-Markstein iterations are carried verbatim; + see radix/div.c for the error analysis (with the LIMB_RADIX(radix) + large / mulhigh case applying throughout). */ + +void +fixed_inv_newton_basecase(nn_ptr Q, nn_srcptr A, slong An, slong n) +{ + nn_ptr U, R; + nn_srcptr V; + slong Un, Vn; + TMP_INIT; + + Vn = FLINT_MIN(An, n + 1); + Un = Vn + n + 1; + + TMP_START; + U = TMP_ALLOC((Un + Vn) * sizeof(ulong)); + R = U + Un; + V = A + An - Vn; + + flint_mpn_zero(U, Un - 1); + U[Un - 1] = 1; + + if (Vn == 1) + mpn_divrem_1(Q, 0, U, Un, V[0]); + else + mpn_tdiv_qr(Q, R, 0, U, Un, V, Vn); + + TMP_END; +} + +/* Tuned on this machine (see dev/notes); must be at least 4 for the + indexing/padding operations in the code to be valid. */ +#ifndef FIXED_INV_NEWTON_CUTOFF +#define FIXED_INV_NEWTON_CUTOFF 14 +#endif + +void +fixed_inv_newton(nn_ptr Q, nn_srcptr A, slong An, slong n) +{ + if (n <= FIXED_INV_NEWTON_CUTOFF) + { + fixed_inv_newton_basecase(Q, A, An, n); + } + else + { + nn_ptr U, V, T, Vhigh, Uhigh; + nn_srcptr Ahigh; + slong m, Uhighn, Tn, Ahighn, Vhighn; + int Unegative; + TMP_INIT; + + /* T ~= 1/A with m fraction limbs, 2 integral limbs, stored in + the high part of Q */ + m = (n + 1) / 2 + 1; + fixed_inv_newton(Q + n - m, A, An, m); + + TMP_START; + + T = Q + n - m; + Tn = m + 1 + (T[m + 1] != 0); + + flint_mpn_zero(Q, n - m); + + /* U ~= 1 - T*A' for the high n+1 limbs A' of A, as a + fixed-point number with n fraction limbs: a middle product + plus one control limb, the higher fraction limbs being 0 or + B-1 a priori */ + slong control_limb = n / 2 + 2; + slong A_low_zeroes = 0; + + if (An >= n + 1) + { + Ahighn = n + 1; + Ahigh = A + An - Ahighn; + } + else + { + Ahighn = An; + Ahigh = A; + A_low_zeroes = n + 1 - An; + } + + /* the full product has n+m+1 fraction limbs; remove m-1 to + keep 2 guard limbs below the n retained ones */ + slong low_trunc = m - 1; + + if (A_low_zeroes <= low_trunc) + { + U = TMP_ALLOC((2 + (control_limb + 1)) * sizeof(ulong)); + flint_mpn_mulmid(U, Ahigh, Ahighn, T, Tn, + low_trunc - A_low_zeroes, + low_trunc - A_low_zeroes + 2 + (control_limb + 1)); + Uhigh = U + 2; + } + else + { + /* A is short: the window starts below the product of the + zero-extended operand; fill the gap with zeros */ + slong gap = A_low_zeroes - low_trunc; + U = TMP_ALLOC((2 + (control_limb + 1)) * sizeof(ulong)); + flint_mpn_mulmid(U + gap, Ahigh, Ahighn, T, Tn, 0, + 2 + (control_limb + 1) - gap); + flint_mpn_zero(U, gap); + Uhigh = U + 2; + } + + Unegative = (Uhigh[control_limb] != 0); + if (Unegative) + mpn_neg(Uhigh, Uhigh, control_limb); + + Uhighn = control_limb; + while (Uhighn > 0 && Uhigh[Uhighn - 1] == 0) + Uhighn--; + + if (Uhighn != 0) + { + /* V = T * |1 - A'T| with n+2 fraction limbs */ + V = TMP_ALLOC((Tn + Uhighn - (m - 2)) * sizeof(ulong)); + flint_mpn_mulmid(V, T, Tn, Uhigh, Uhighn, + m - 2, Tn + Uhighn); + Vhigh = V + 2; + Vhighn = Tn + Uhighn - (m - 2) - 2; + + /* T + T(1 - A'T) resp. T - T(A'T - 1) */ + if (Unegative) + mpn_add(Q, Q, n + 2, Vhigh, Vhighn); + else + mpn_sub(Q, Q, n + 2, Vhigh, Vhighn); + } + + TMP_END; + } +} + +void +fixed_div_newton_invmul(nn_ptr Q, nn_srcptr B, slong Bn, nn_srcptr A, + slong An, slong n) +{ + nn_ptr T; + TMP_INIT; + TMP_START; + + if (Bn > n) + { + B = B + Bn - n; + Bn = n; + } + + T = TMP_ALLOC((Bn + n + 2) * sizeof(ulong)); + + fixed_inv_newton(Q, A, An, n); + /* should be a high multiplication */ + if (n + 2 >= Bn) + flint_mpn_mul(T, Q, n + 2, B, Bn); + else + flint_mpn_mul(T, B, Bn, Q, n + 2); + flint_mpn_copyi(Q, T + Bn, n + 2); + + TMP_END; +} + +#ifndef FIXED_DIV_NEWTON_CUTOFF +#define FIXED_DIV_NEWTON_CUTOFF 12 +#endif + +/* Karp-Markstein division: evaluates TB + T * (B - A * TB) with + T ~= 1/A carried at half precision */ +void +fixed_div_newton(nn_ptr Q, nn_srcptr B, slong Bn, nn_srcptr A, + slong An, slong n) +{ + nn_ptr U, V, T, Vhigh, Uhigh; + nn_srcptr Ahigh; + slong m, Uhighn, Tn, Ahighn, Vhighn; + int Unegative; + TMP_INIT; + + if (n <= FIXED_DIV_NEWTON_CUTOFF) + { + fixed_div_newton_invmul(Q, B, Bn, A, An, n); + return; + } + + m = (n + 1) / 2 + 1; + + fixed_inv_newton(Q + n - m, A, An, m); + + TMP_START; + T = Q + n - m; + Tn = m + 1 + (T[m + 1] != 0); + + flint_mpn_zero(Q, n - m); + + /* TB ~= B/A with m fraction limbs */ + nn_ptr TB, TBhigh; + slong Bn2 = FLINT_MIN(m, Bn); + nn_srcptr B2 = B + Bn - Bn2; + + if (Bn2 > 2) + { + TB = TMP_ALLOC((Tn + 2) * sizeof(ulong)); + flint_mpn_mulmid(TB, T, Tn, B2, Bn2, Bn2 - 2, Bn2 + Tn); + TBhigh = TB + 2; + } + else + { + TB = TMP_ALLOC((Bn2 + Tn) * sizeof(ulong)); + if (Tn >= Bn2) + flint_mpn_mul(TB, T, Tn, B2, Bn2); + else + flint_mpn_mul(TB, B2, Bn2, T, Tn); + TBhigh = TB + Bn2; + } + + slong control_limb = n / 2 + 2; + slong A_low_zeroes = 0; + + if (An >= n + 1) + { + Ahighn = n + 1; + Ahigh = A + An - Ahighn; + } + else + { + Ahighn = An; + Ahigh = A; + A_low_zeroes = n + 1 - An; + } + + slong low_trunc = m - 1; + + if (A_low_zeroes <= low_trunc) + { + U = TMP_ALLOC((2 + (control_limb + 1)) * sizeof(ulong)); + flint_mpn_mulmid(U, Ahigh, Ahighn, TBhigh, Tn, + low_trunc - A_low_zeroes, + low_trunc - A_low_zeroes + 2 + (control_limb + 1)); + Uhigh = U + 2; + } + else + { + slong gap = A_low_zeroes - low_trunc; + U = TMP_ALLOC((2 + (control_limb + 1)) * sizeof(ulong)); + flint_mpn_mulmid(U + gap, Ahigh, Ahighn, TBhigh, Tn, 0, + 2 + (control_limb + 1) - gap); + flint_mpn_zero(U, gap); + Uhigh = U + 2; + } + + slong Un = control_limb + 1; + + /* (B - A*TB) with n fraction limbs, with sign */ + if (Bn > n - Un) + { + if (Bn >= n) + { + nn_srcptr Bhigh = B + Bn - n; + mpn_sub_n(Uhigh, Bhigh, Uhigh, Un); + } + else + { + slong nz = n - Bn; + ulong cy = mpn_neg(Uhigh, Uhigh, nz); + mpn_sub_n(Uhigh + nz, B, Uhigh + nz, Un - nz); + mpn_sub_1(Uhigh + nz, Uhigh + nz, Un - nz, cy); + } + + Unegative = 1; + if (Uhigh[Un - 1] >> (FLINT_BITS - 1)) + { + mpn_neg(Uhigh, Uhigh, Un); + Unegative = 0; + } + } + else + { + Unegative = 0; + if (Uhigh[Un - 1] >> (FLINT_BITS - 1)) + { + mpn_neg(Uhigh, Uhigh, Un); + Unegative = 1; + } + } + + Uhighn = Un; + while (Uhighn > 0 && Uhigh[Uhighn - 1] == 0) + Uhighn--; + + if (Uhighn != 0) + { + V = TMP_ALLOC((Tn + Uhighn - (m - 2)) * sizeof(ulong)); + flint_mpn_mulmid(V, T, Tn, Uhigh, Uhighn, m - 2, Tn + Uhighn); + Vhigh = V + 2; + Vhighn = Tn + Uhighn - (m - 2) - 2; + + /* overwrite T with TB */ + Q[n + 1] = 0; + flint_mpn_copyi(Q + n - m, TBhigh, Tn); + + if (Unegative) + mpn_add(Q, Q, n + 2, Vhigh, Vhighn); + else + mpn_sub(Q, Q, n + 2, Vhigh, Vhighn); + } + else + { + /* overwrite T with TB */ + Q[n + 1] = 0; + flint_mpn_copyi(Q + n - m, TBhigh, Tn); + } + + TMP_END; +} diff --git a/src/fixed/exp_bitwise_rs.c b/src/fixed/exp_bitwise_rs.c index cce88d3868..60fa7b31a0 100644 --- a/src/fixed/exp_bitwise_rs.c +++ b/src/fixed/exp_bitwise_rs.c @@ -13,7 +13,6 @@ #include "mpn_extras.h" #include "arb.h" #include "fixed.h" -#include "impl.h" /* exp via bitwise argument reduction: subtract in turn each L_i = log(1 + 2^-i), i = 0, 1, ..., r, for which L_i <= x, evaluate @@ -74,28 +73,9 @@ _fixed_exp_logs_cleanup(void) _fixed_exp_logs_cleanup_registered = 0; } -#include "frac_bsplit.inc" - #define LOGS_L(i) (_fixed_exp_logs + (i) * _fixed_exp_logs_n) #define TAB_E(i) (tab + (i) * nc) -/* floor(x 2^(FLINT_BITS nc)) of a nonnegative arb into nc limbs */ -static void -_fixed_tab_store(nn_ptr e, const arb_t x, slong nc) -{ - arf_t lb; - fmpz_t f; - - arf_init(lb); - fmpz_init(f); - arb_get_lbound_arf(lb, x, FLINT_BITS * nc + 30); - arf_mul_2exp_si(lb, lb, FLINT_BITS * nc); - arf_get_fmpz(f, lb, ARF_RND_FLOOR); - FLINT_ASSERT(fmpz_sgn(f) >= 0); - fmpz_get_ui_array(e, nc, f); - arf_clear(lb); - fmpz_clear(f); -} /* acc (nc limbs) += or -= t >> s, where t has nc limbs and the bits shifted below position 0 are dropped (they fall below the guard @@ -149,13 +129,10 @@ _fixed_tab_subbit(nn_ptr acc, slong nc, slong e) Small i (below about sqrt(precision) bits, where the series is long): binary splitting. For i <= 6 the arguments (2^i + 1)/2^i factor over the primes up to 17, so seven bsplit logarithms serve - all of them; beyond that, log(1 + 2^-i) = 2 atanh(1/(2^(i+1) + 1)) - through arb_atan1_frac_bsplit (frac_bsplit.inc), switching to the - direct log(1 + 2^-i) series of arb_log1_frac_bsplit once the - precision passes 6000 bits and i passes 30, where its plainer - recursion wins. TODO: reimplement the binary splitting natively - with mpn arithmetic; arb is fine for now, and this tier is not - the bottleneck. + all of them; beyond that, fixed_log1p_2mexp_ui_bs (tab_bsplit.c) + splits the series natively in mpn arithmetic, writing straight + into the entry. TODO: the remaining arb dependency of this tier + is the seven-logarithm prime-vector combination for i <= 6. Large i: fixed-point multi-summation of @@ -243,28 +220,15 @@ _fixed_exp_logs_ensure(slong nv, slong rc) arb_submul_ui(x, lp + 0, 6, wp); break; default: - if (prec >= 6000 && i >= 30) - { - /* direct log(1 + 1/2^i) series */ - fmpz_one(p); - fmpz_one(q); - fmpz_mul_2exp(q, q, i); - arb_log1_frac_bsplit(x, p, q, wp); - } - else - { - /* log(1 + 2^-i) = 2 atanh(1 / (2^(i+1) + 1)) */ - fmpz_one(p); - fmpz_one(q); - fmpz_mul_2exp(q, q, i + 1); - fmpz_add_ui(q, q, 1); - arb_atan1_frac_bsplit(x, p, q, 1, wp); - arb_mul_2exp_si(x, x, 1); - } - break; + /* native mpn binary splitting, straight into the + entry (atanh form, switching to the direct + series at high precision; see tab_bsplit.c) */ + fixed_log1p_2mexp_ui_bs(LOGS_L(i), (ulong) i, nc); + continue; } - _fixed_tab_store(LOGS_L(i), x, nc); + if (!_fixed_tab_store_floor(LOGS_L(i), x, nc, wp)) + _fixed_tab_entry_exact(LOGS_L(i), 0, (ulong) i, nc); } _arb_vec_clear(lp, 7); @@ -331,11 +295,63 @@ _fixed_exp_logs_ensure(slong nv, slong rc) flint_register_cleanup_function(_fixed_exp_logs_cleanup); _fixed_exp_logs_cleanup_registered = 1; } + /* Direct tail band: for 3i >= 64 nc every term of + log(1 + 2^-i) = 2^-i - 2^(-2i-1) + 2^(-3i)/3 - ... from the + third on falls below the entry, with the omitted alternating + mass s satisfying 0 < s 2^(64 nc) <= 1/3, so the exact floor + is a pure bit pattern: 2^(64 nc - i) - 2^(64 nc - 2i - 1) + when the second term is still representable, and + 2^(64 nc - i) - 1 when it too falls below (then the omitted + mass sits in (0, 1/2]). Writing these directly covers the + entries whose guard limb legitimately sits near wrapping. + + Exactness rescan below the band: the fast tiers produce + entries at most a few guard ulps below the truth, so a guard + limb within FIXED_TAB_GUARD_SLACK of wrapping means that + deficit may have borrowed into the value limbs -- recompute + such (now genuinely astronomically rare) entries via arb. + Together with the exact-floor tier-1 store, every entry's + value limbs equal the exact floor at their precision, hence + at every shorter truncation as well. */ + { + slong wp = FLINT_BITS * nc, band = (wp + 2) / 3; + + for (i = FLINT_MAX(band, 7); i <= rc; i++) + { + nn_ptr acc = LOGS_L(i); + slong b = wp - i; + + flint_mpn_zero(acc, nc); + if (2 * i + 1 <= wp) + { + /* ones on bits [64 nc - 2i - 1, 64 nc - i) */ + slong lo = wp - 2 * i - 1; + slong j; + for (j = lo; j < wp - i; j++) + acc[j / FLINT_BITS] |= UWORD(1) << (j % FLINT_BITS); + } + else + { + flint_mpn_store(acc, b / FLINT_BITS, ~UWORD(0)); + if (b % FLINT_BITS) + acc[b / FLINT_BITS] = + (UWORD(1) << (b % FLINT_BITS)) - 1; + } + } + + for (i = 7; i < FLINT_MIN(band, rc + 1); i++) + { + ulong g = _fixed_exp_logs[i * nc]; + if (g + FIXED_TAB_GUARD_SLACK < g) + _fixed_tab_entry_exact(LOGS_L(i), 0, (ulong) i, nc); + } + } + } /* Read-only view of the table for code outside the library (the - thread-local storage itself is not exported; see impl.h): + thread-local storage itself is not exported; see fixed.h): the top n limbs of entry i, valid until the next ensure call on this thread. */ nn_srcptr @@ -347,6 +363,18 @@ _fixed_exp_logs_entry(slong i, slong n) + (_fixed_exp_logs_n - n); } +/* free the cached table (tuning programs measure cold rebuilds; + exported so that code outside the library never touches the + thread-local data directly) */ +void +_fixed_exp_logs_clear(void) +{ + flint_free(_fixed_exp_logs); + _fixed_exp_logs = NULL; + _fixed_exp_logs_n = 0; + _fixed_exp_logs_r = 0; +} + /* the largest index the cached table currently covers */ slong _fixed_exp_logs_max_index(void) @@ -355,64 +383,129 @@ _fixed_exp_logs_max_index(void) } -/* Use the sinh series (half the terms) plus a squaring and a square - root once the direct exp series gets long enough. Measured - crossovers on x86-64: for r < 64 (series through the pre32 path) - sinh wins from about 45 terms (n ~ 24 at r = 32); for r >= 64 the - windowed pre64 series is more efficient per term and mpn_sqrtrem - sets a higher floor, moving the crossover to about 128 terms - (n ~ 2r). The margins near the crossovers are within a few - percent, so the exact placement is not critical. */ -#define EXP_USE_SINH(wn, r) \ - (FLINT_BITS * (wn) >= (((r) >= 64) ? 128 : 45) * (slong) (r)) - -/* exp(t) of the reduced argument t < 2^-r into y (wn + 1 limbs: - wn fraction limbs and a units limb); the series functions pick the - 32- or 64-bit internal range from the top limb of t. When the - direct series would need many terms, evaluate sinh(t) instead -- - the odd series has half the terms -- and reconstruct - exp(t) = sinh(t) + sqrt(1 + sinh(t)^2), costing one squaring and - one square root. The sinh error (FIXED_SINH_RS_MAX_ERR = 15 ulp), - the truncated squaring and the floored integer square root - together contribute some 20 ulp, amplified below e by the - reconstruction: this is part of the constant term of - FIXED_EXP_BITWISE_RS_MAX_ERR. */ + + +/* Product-form reconstruction for large ylen: accumulate + P = prod (1 + 2^-used[j]) exactly in a growing lsb-anchored buffer + (P = m 2^-B with B the accumulated bottom extent, so several + factors fit in one limb, then two, three, ...), capping the extent + at 64 ylen + 64 bits -- each capped factor then drops less than + one bit below the cap, u such drops staying far under an output + ulp -- and finish with a single multiplication y <- floor(y P) + instead of one shift-and-add per factor. With the used indices + increasing, B grows only quadratically (about (sum of used + indices) bits, r^2/4 in the worst case), so for large ylen the + final multiplication is short times long and the whole + reconstruction costs a fraction of the shift-and-add chain; the + result is also tighter, one truncation instead of one per + factor. With the middle-product finish the path wins on the + whole exp call from n ~ 256 at the default ladder on the + development machine (7% there, 12-13% at n = 4096..8192, a wash + only around n = 512 where the capped extent is at its relative + largest). Retune with the Newton cutoffs. */ static void -_fixed_exp_reduced(nn_ptr y, nn_srcptr t, slong wn, int r, int use_sinh) +_fixed_exp_recon_prod(nn_ptr y, slong ylen, const slong * used, + slong j, slong num) { - if (!use_sinh) + slong capd = FLINT_BITS * (ylen + 1); /* keep bits above 2^-capd */ + slong B = 0, mlen = 1, alloc, plen; + nn_ptr m, t, prod; + TMP_INIT; + + TMP_START; + /* the mantissa extent stays below capd + 63 + max_index bits: + the depth cap plus one partial limb plus one factor's shift. + A fixed "+ 16" slack here encoded max_index / 64 <= 12 and + OVERFLOWED on 32-bit limbs, where the same indices span twice + the limbs (an all-ones input at n = 420, r >= 576 corrupted + the product a few limbs deep) */ + alloc = ylen + 4 + used[num - 1] / FLINT_BITS; + m = TMP_ALLOC((2 * alloc + alloc + ylen + 2) * sizeof(ulong)); + t = m + alloc; + prod = t + alloc; + + m[0] = 1; + + for (; j < num; j++) { - fixed_exp_rs(y, t, wn); + slong i = used[j], q = i / FLINT_BITS, b = i % FLINT_BITS; + slong tl, d; + + /* m' 2^-(B+i) = (m 2^i + m) 2^-(B+i) = m 2^-B (1 + 2^-i) */ + tl = mlen + q + (b != 0); + flint_mpn_zero(t, q); + if (b) + t[mlen + q] = mpn_lshift(t + q, m, mlen, (int) b); + else + flint_mpn_copyi(t + q, m, mlen); + while (tl > 1 && t[tl - 1] == 0) + tl--; + /* t = m 2^i (tl limbs, tl >= mlen); m' = t + m */ + { + ulong cy = mpn_add(t, t, tl, m, mlen); + t[tl] = cy; + mlen = tl + (cy != 0); + } + { nn_ptr u_ = m; m = t; t = u_; } + B += i; + + /* drop whole limbs sitting deeper than capd bits: each drop + loses less than 2^-capd, one output guard bit, and there + are at most r of them in total */ + d = (B - capd) / FLINT_BITS; + if (d > 0) + { + flint_mpn_copyi(m, m + d, mlen - d); + mlen -= d; + B -= FLINT_BITS * d; + } } - else - { - nn_ptr s, u2, rt, rem; - TMP_INIT; - - TMP_START; - s = TMP_ALLOC(((wn + 1) + (2 * wn + 1) + (wn + 1) + (wn + 2)) - * sizeof(ulong)); - u2 = s + (wn + 1); - rt = u2 + (2 * wn + 1); - rem = rt + (wn + 1); - - fixed_sinh_rs(s, t, wn); - /* u2 = (1 + sinh(t)^2) 2^(2 FLINT_BITS wn) */ - flint_mpn_zero(u2, wn); - flint_mpn_sqrhigh(u2 + wn, s, wn); - u2[2 * wn] = 1; - - /* cosh(t) = sqrt(1 + sinh(t)^2) */ - mpn_sqrtrem(rt, rem, u2, 2 * wn + 1); - - /* exp(t) = sinh(t) + cosh(t) */ - mpn_add_n(y, rt, s, wn + 1); + /* y <- floor(y P) = (y m) >> B. Only the product window at and + above limb B/64 matters, so when the operands are nearly + balanced a middle product beats the full multiplication. Its + lower-approximation deficit is bounded by + min(ylen, mlen, zlo) 2^64 window units -- up to ~min ulps of + the limb ABOVE the window bottom, a bound that all-ones + operands very nearly attain -- so ONE sacrificial limb is not + enough (at bit offset b = 0 up to min ulps would leak into + the kept result); TWO are, leaving the deficit below + min / 2^64 < 1 ulp of the result, hence within 1 ulp of the + exact floor like the full product's plain truncation (the + capped drops sit further below still), far inside the budget + the per-factor chain used to spend. */ + plen = ylen + mlen; + { + slong q = B / FLINT_BITS, b = B % FLINT_BITS; - TMP_END; + if (2 * mlen >= ylen && q >= 2) + { + /* window two limbs below the kept result: both + sacrificial limbs absorb the deficit */ + flint_mpn_mulmid(prod + q - 2, y, ylen, m, mlen, + q - 2, plen); + if (b) + mpn_rshift(prod + q - 2, prod + q - 2, + plen - q + 2, (int) b); + flint_mpn_copyi(y, prod + q, ylen); + } + else + { + if (mlen >= ylen) + flint_mpn_mul(prod, m, mlen, y, ylen); + else + flint_mpn_mul(prod, y, ylen, m, mlen); + if (b) + mpn_rshift(prod + q, prod + q, plen - q, (int) b); + flint_mpn_copyi(y, prod + q, ylen); + } } + TMP_END; } +#ifndef FIXED_EXP_RECON_PROD_CUTOFF +#define FIXED_EXP_RECON_PROD_CUTOFF 192 +#endif /* multiply y (ylen limbs, fraction plus a units limb) by the factors (1 + 2^-used[j]) for j0 <= j < num: each is a shift and an add. @@ -423,6 +516,12 @@ void _fixed_exp_recon(nn_ptr y, nn_ptr sh, slong ylen, const slong * used, slong j, slong num) { + if (ylen >= FIXED_EXP_RECON_PROD_CUTOFF && num - j >= 8) + { + _fixed_exp_recon_prod(y, ylen, used, j, num); + return; + } + if (j == 0 && num > 0 && used[0] == 0) { mpn_add_n(y, y, y, ylen); /* factor 2 */ @@ -664,7 +763,7 @@ fixed_exp_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r) _fixed_exp_logs_n, used); /* exp of the reduced argument */ - _fixed_exp_reduced(y, t, wn, r, EXP_USE_SINH(wn, r)); + fixed_exp_reduced(y, t, wn, (flint_bitcnt_t) r, 0); _fixed_exp_recon(y, sh, n + 1, used, 0, num); diff --git a/src/fixed/exp_opt_1.c b/src/fixed/exp_opt_1.c index 170b4008d8..2fd62cab7a 100644 --- a/src/fixed/exp_opt_1.c +++ b/src/fixed/exp_opt_1.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -76,15 +75,15 @@ fixed_exp_opt_1(nn_ptr res, nn_srcptr x) slong used[1 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(1, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(1, r, &nc); t0 = x[0]; /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 1); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 1); \ sub_ddmmss(bw, d0, \ UWORD(0), t0, \ UWORD(0), Lq[0]); \ @@ -97,7 +96,7 @@ fixed_exp_opt_1(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 1); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 1); \ t0 -= Lq[0]; \ } diff --git a/src/fixed/exp_opt_2.c b/src/fixed/exp_opt_2.c index 4e55929bff..2943632b29 100644 --- a/src/fixed/exp_opt_2.c +++ b/src/fixed/exp_opt_2.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -92,8 +91,8 @@ fixed_exp_opt_2(nn_ptr res, nn_srcptr x) slong used[2 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(2, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(2, r, &nc); t0 = x[0]; t1 = x[1]; @@ -101,7 +100,7 @@ fixed_exp_opt_2(nn_ptr res, nn_srcptr x) /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 2); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 2); \ sub_dddmmmsss(bw, d1, d0, \ UWORD(0), t1, t0, \ UWORD(0), Lq[1], Lq[0]); \ @@ -115,7 +114,7 @@ fixed_exp_opt_2(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 2); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 2); \ sub_ddmmss(t1, t0, \ t1, t0, \ Lq[1], Lq[0]); \ diff --git a/src/fixed/exp_opt_3.c b/src/fixed/exp_opt_3.c index 69268b937a..fc4864d783 100644 --- a/src/fixed/exp_opt_3.c +++ b/src/fixed/exp_opt_3.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -91,8 +90,8 @@ fixed_exp_opt_3(nn_ptr res, nn_srcptr x) slong used[3 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(3, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(3, r, &nc); t0 = x[0]; t1 = x[1]; @@ -101,7 +100,7 @@ fixed_exp_opt_3(nn_ptr res, nn_srcptr x) /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 3); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 3); \ sub_ddddmmmmssss(bw, d2, d1, d0, \ UWORD(0), t2, t1, t0, \ UWORD(0), Lq[2], Lq[1], Lq[0]); \ @@ -116,7 +115,7 @@ fixed_exp_opt_3(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 3); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 3); \ sub_dddmmmsss(t2, t1, t0, \ t2, t1, t0, \ Lq[2], Lq[1], Lq[0]); \ @@ -139,7 +138,7 @@ fixed_exp_opt_3(nn_ptr res, nn_srcptr x) bj = num; \ h = hreg; \ e = 0; \ - lp = _fixed_exp_logs + (FLINT_BITS * (cc) + 1) * nc \ + lp = tab + (FLINT_BITS * (cc) + 1) * nc \ + (nc - 1 - (cc)); \ for (i = FLINT_BITS * (cc) + 1; i <= i1; i++, lp += nc) \ { \ diff --git a/src/fixed/exp_opt_4.c b/src/fixed/exp_opt_4.c index cd13dbe1aa..100a6476e2 100644 --- a/src/fixed/exp_opt_4.c +++ b/src/fixed/exp_opt_4.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -122,8 +121,8 @@ fixed_exp_opt_4(nn_ptr res, nn_srcptr x) slong used[4 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(4, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(4, r, &nc); t0 = x[0]; t1 = x[1]; @@ -133,7 +132,7 @@ fixed_exp_opt_4(nn_ptr res, nn_srcptr x) /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 4); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 4); \ sub_dddddmmmmmsssss(bw, d3, d2, d1, d0, \ UWORD(0), t3, t2, t1, t0, \ UWORD(0), Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -149,7 +148,7 @@ fixed_exp_opt_4(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 4); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 4); \ sub_ddddmmmmssss(t3, t2, t1, t0, \ t3, t2, t1, t0, \ Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -172,7 +171,7 @@ fixed_exp_opt_4(nn_ptr res, nn_srcptr x) bj = num; \ h = hreg; \ e = 0; \ - lp = _fixed_exp_logs + (FLINT_BITS * (cc) + 1) * nc \ + lp = tab + (FLINT_BITS * (cc) + 1) * nc \ + (nc - 1 - (cc)); \ for (i = FLINT_BITS * (cc) + 1; i <= i1; i++, lp += nc) \ { \ diff --git a/src/fixed/exp_opt_5.c b/src/fixed/exp_opt_5.c index dac96a8641..7c3c9b475c 100644 --- a/src/fixed/exp_opt_5.c +++ b/src/fixed/exp_opt_5.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -158,8 +157,8 @@ fixed_exp_opt_5(nn_ptr res, nn_srcptr x) slong used[5 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(5, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(5, r, &nc); t0 = x[0]; t1 = x[1]; @@ -170,7 +169,7 @@ fixed_exp_opt_5(nn_ptr res, nn_srcptr x) /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 5); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 5); \ sub_ddddddmmmmmmssssss(bw, d4, d3, d2, d1, d0, \ UWORD(0), t4, t3, t2, t1, t0, \ UWORD(0), Lq[4], Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -187,7 +186,7 @@ fixed_exp_opt_5(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 5); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 5); \ sub_dddddmmmmmsssss(t4, t3, t2, t1, t0, \ t4, t3, t2, t1, t0, \ Lq[4], Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -210,7 +209,7 @@ fixed_exp_opt_5(nn_ptr res, nn_srcptr x) bj = num; \ h = hreg; \ e = 0; \ - lp = _fixed_exp_logs + (FLINT_BITS * (cc) + 1) * nc \ + lp = tab + (FLINT_BITS * (cc) + 1) * nc \ + (nc - 1 - (cc)); \ for (i = FLINT_BITS * (cc) + 1; i <= i1; i++, lp += nc) \ { \ diff --git a/src/fixed/exp_opt_6.c b/src/fixed/exp_opt_6.c index 7ec4e157ca..bad63d6c94 100644 --- a/src/fixed/exp_opt_6.c +++ b/src/fixed/exp_opt_6.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -156,8 +155,8 @@ fixed_exp_opt_6(nn_ptr res, nn_srcptr x) slong used[6 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(6, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(6, r, &nc); t0 = x[0]; t1 = x[1]; @@ -169,7 +168,7 @@ fixed_exp_opt_6(nn_ptr res, nn_srcptr x) /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 6); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 6); \ sub_dddddddmmmmmmmsssssss(bw, d5, d4, d3, d2, d1, d0, \ UWORD(0), t5, t4, t3, t2, t1, t0, \ UWORD(0), Lq[5], Lq[4], Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -187,7 +186,7 @@ fixed_exp_opt_6(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 6); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 6); \ sub_ddddddmmmmmmssssss(t5, t4, t3, t2, t1, t0, \ t5, t4, t3, t2, t1, t0, \ Lq[5], Lq[4], Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -210,7 +209,7 @@ fixed_exp_opt_6(nn_ptr res, nn_srcptr x) bj = num; \ h = hreg; \ e = 0; \ - lp = _fixed_exp_logs + (FLINT_BITS * (cc) + 1) * nc \ + lp = tab + (FLINT_BITS * (cc) + 1) * nc \ + (nc - 1 - (cc)); \ for (i = FLINT_BITS * (cc) + 1; i <= i1; i++, lp += nc) \ { \ diff --git a/src/fixed/exp_opt_7.c b/src/fixed/exp_opt_7.c index 25426b804d..61245d7be7 100644 --- a/src/fixed/exp_opt_7.c +++ b/src/fixed/exp_opt_7.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -153,8 +152,8 @@ fixed_exp_opt_7(nn_ptr res, nn_srcptr x) slong used[7 * FLINT_BITS + 2]; - _fixed_exp_logs_ensure(7, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(7, r, &nc); t0 = x[0]; t1 = x[1]; @@ -167,7 +166,7 @@ fixed_exp_opt_7(nn_ptr res, nn_srcptr x) /* exact compare-subtract of L_ii, recording the index */ #define STEP(ii) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 7); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 7); \ sub_ddddddddmmmmmmmmssssssss(bw, d6, d5, d4, d3, d2, d1, d0, \ UWORD(0), t6, t5, t4, t3, t2, t1, t0, \ UWORD(0), Lq[6], Lq[5], Lq[4], Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -186,7 +185,7 @@ fixed_exp_opt_7(nn_ptr res, nn_srcptr x) #define FLUSH() \ for (; bj < num; bj++) \ { \ - nn_srcptr Lq = _fixed_exp_logs + used[bj] * nc + (nc - 7); \ + nn_srcptr Lq = tab + used[bj] * nc + (nc - 7); \ sub_dddddddmmmmmmmsssssss(t6, t5, t4, t3, t2, t1, t0, \ t6, t5, t4, t3, t2, t1, t0, \ Lq[6], Lq[5], Lq[4], Lq[3], Lq[2], Lq[1], Lq[0]); \ @@ -209,7 +208,7 @@ fixed_exp_opt_7(nn_ptr res, nn_srcptr x) bj = num; \ h = hreg; \ e = 0; \ - lp = _fixed_exp_logs + (FLINT_BITS * (cc) + 1) * nc \ + lp = tab + (FLINT_BITS * (cc) + 1) * nc \ + (nc - 1 - (cc)); \ for (i = FLINT_BITS * (cc) + 1; i <= i1; i++, lp += nc) \ { \ diff --git a/src/fixed/exp_reduced.c b/src/fixed/exp_reduced.c new file mode 100644 index 0000000000..539b5197f4 --- /dev/null +++ b/src/fixed/exp_reduced.c @@ -0,0 +1,566 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "flint.h" +#include "longlong.h" +#include "mpn_extras.h" +#include "fixed.h" + +/* fixed_exp_reduced: exp(t) of a reduced argument t < 2^-r, + r >= 16 (the series algorithms 1 and 2 require r >= 32), into y + (wn + 1 limbs: wn fraction limbs and a units limb), independent + of any particular argument-reduction scheme. + + alg selects the method: + 0 the tuned automatic choice (see the selection logic at the + bottom and tune/tune-exp-reduced.c) + 1 the direct exponential rectangular-splitting series + 2 the sinh rectangular-splitting series plus a squaring and + a square root + 3 one bit-burst step -- t = x1 + x2 with x1 the leading + slice down to the doubled limb boundary and x2 below it -- + combining exp(x1) from the mpn binary splitting with + exp(x2) from the sinh series at the doubled rate + 4 the full bit-burst algorithm: exp(t) = prod_k exp(x_k) + with slices doubling in length on limb boundaries, each + factor from the binary splitting; asymptotically + quasi-optimal for very large wn (or very large r) + + The slice boundaries sit on limbs: the leading slice runs from + below the r zero bits down to limb wn - D, D = 2 max(r/64, 1), + so its value is below 2^-r and the remainder below 2^-64D; the + binary splitting reads the slice as an integer scaled by + 2^(-64 D), so an r not divisible by FLINT_BITS merely leaves a + few zero bits at the top of the slice. Error: the series paths + carry their documented budgets (~20 ulp for sinh); each burst + level adds at most ~4 ulps (a floored short division within 2 and + a middle product taken two limbs below the kept window within 2), + and alg 4 uses at most log2(wn) levels. */ + +/* Use the sinh series (half the terms) plus a squaring and a square + root once the direct exp series gets long enough. Measured + crossovers on x86-64: for r < 64 (series through the pre32 path) + sinh wins from about 45 terms (n ~ 24 at r = 32); for r >= 64 the + windowed pre64 series is more efficient per term and mpn_sqrtrem + sets a higher floor, moving the crossover to about 128 terms + (n ~ 2r). The margins near the crossovers are within a few + percent, so the exact placement is not critical. */ +/* working precision from which the sinh-path square root runs + through fixed_sqrt_newton instead of mpn_sqrtrem; kept above the + measured range on the development machine (see the comment at the + call site), lower it to experiment */ +#ifndef FIXED_EXP_SQRT_NEWTON_CUTOFF +#define FIXED_EXP_SQRT_NEWTON_CUTOFF 2000 +#endif + +#define EXP_USE_SINH(wn, r) \ + (FLINT_BITS * (wn) >= (((r) >= 64) ? 128 : 45) * (slong) (r)) + +/* exp(t) of the reduced argument t < 2^-r into y (wn + 1 limbs: + wn fraction limbs and a units limb); the series functions pick the + 32- or 64-bit internal range from the top limb of t. When the + direct series would need many terms, evaluate sinh(t) instead -- + the odd series has half the terms -- and reconstruct + exp(t) = sinh(t) + sqrt(1 + sinh(t)^2), costing one squaring and + one square root. The sinh error (FIXED_SINH_RS_MAX_ERR = 15 ulp), + the truncated squaring and the floored integer square root + together contribute some 20 ulp, amplified below e by the + reconstruction: this is part of the constant term of + FIXED_EXP_BITWISE_RS_MAX_ERR. */ +/* One bit-burst step doubles the convergence rate: for t < 2^-r + with r a whole number of limbs, split t = x1 + x2 where x1 is the + leading r-bit chunk (bits [r, 2r)) and x2 < 2^-2r the remainder, + and compute exp(t) = exp(x1) exp(x2). exp(x1) = 1 + s1 comes + from the mpn binary splitting _fixed_exp_sum_bs_powtab -- the + chunk is an r-bit rational, so the splitting tree stays small -- + and exp(x2) = 1 + f2 recurses into this function at the doubled + rate (possibly bursting again). The combination + (1 + s1)(1 + f2) = 1 + s1 + f2 + s1 f2 costs two additions and + one balanced middle product for s1 f2 < 2^-3r, taken two limbs + below the kept window per the mulmid deficit bound. s1 is a + floored quotient within 2 ulps (shift truncation plus tdiv), the + middle product adds at most 2 more; all far inside the sinh + reconstruction budget. */ +#ifndef FIXED_EXP_BURST_TERMS +#define FIXED_EXP_BURST_TERMS 512 +#endif +/* terms threshold for one burst step; the slice mechanics work for + any r >= 32 since the boundaries sit on limbs */ +#define EXP_USE_BURST(wn, r) \ + (FLINT_BITS * (wn) >= FIXED_EXP_BURST_TERMS * (slong) (r)) + +/* Thresholds from tune-exp-reduced on the development VM after the + single-division restructure (terms = 64 wn / r): one burst step + from ~512 terms -- LATER than under the per-level divisions, + whose small level-0 divisor was cheaper than the generic rational + accumulation for one slice -- and the full bit-burst from ~4096 + terms, EARLIER than before since the cascade is what the + restructure optimizes. Retune on target hardware. */ +#ifndef FIXED_EXP_FULLBURST_TERMS +#define FIXED_EXP_FULLBURST_TERMS 4096 +#endif + +/* Assemble the bit-burst factor F = Q 2^Qexp + T as + value = (F, *fn) 2^(*fexp): exactly when it fits within + cap + 2 limbs (then *fexp = 0), and otherwise as its top window + of about cap + 1 limbs -- Q shifted into place and T's + limb-aligned tail above the window bottom added -- with the + dropped bits, under one bit at the limb-aligned bottom, going + into *fexp. F must have room for cap + 3 limbs. Exposed + non-statically so that the window arithmetic, which the burst + only reaches in specific precision regimes, can be unit-tested + directly (t-exp_sum_bs). */ +void +_fixed_exp_burst_factor(nn_ptr F, slong * fn, slong * fexp, + nn_srcptr T, slong tn, nn_srcptr Q, slong qn, + flint_bitcnt_t Qexp, slong cap) +{ + slong qb = FLINT_BITS * (qn - 1) + FLINT_BIT_COUNT(Q[qn - 1]); + slong fbits = (slong) Qexp + qb + 1; /* F < 1.5 Q 2^Qexp */ + slong l; + + /* Q must fit inside the window (in the burst, qbits stays well + under 0.6 wp against a window of 64 (wn + 4) bits) */ + FLINT_ASSERT(qb < FLINT_BITS * (cap + 1)); + + if (fbits <= FLINT_BITS * (cap + 2)) + { + /* small factor: form it exactly */ + slong Fq = (slong) Qexp / FLINT_BITS; + int Fb = (int) ((slong) Qexp % FLINT_BITS); + + flint_mpn_zero(F, cap + 3); + if (Fb) + F[Fq + qn] = mpn_lshift(F + Fq, Q, qn, Fb); + else + flint_mpn_copyi(F + Fq, Q, qn); + if (tn > 0) + mpn_add(F, F, cap + 3, T, tn); + *fexp = 0; + } + else + { + /* assemble only the top window: wbot is the window's + bottom bit, limb-aligned */ + slong wbot = ((fbits - FLINT_BITS * (cap + 1)) + / FLINT_BITS) * FLINT_BITS; + slong sh1 = (slong) Qexp - wbot; + slong s1q = sh1 / FLINT_BITS; + int s1b = (int) (sh1 % FLINT_BITS); + + FLINT_ASSERT(sh1 >= 0); + flint_mpn_zero(F, cap + 3); + if (s1b) + F[s1q + qn] = mpn_lshift(F + s1q, Q, qn, s1b); + else + flint_mpn_copyi(F + s1q, Q, qn); + /* add T's limb-aligned tail above wbot; dropping the rest + loses under one bit at wbot, a relative 2^(-64 cap) */ + { + slong dq2 = wbot / FLINT_BITS; + if (dq2 < tn) + { + ulong cy = mpn_add(F, F, cap + 3, T + dq2, + tn - dq2); + FLINT_ASSERT(cy == 0); + (void) cy; + } + } + *fexp = wbot; + } + l = cap + 3; + while (l > 1 && F[l - 1] == 0) + l--; + *fn = l; +} + +/* Bit-burst evaluation. Slice boundaries double from depth r: + b_0 = r, b_{k+1} = min(2 b_k, wp); slice k = bits [b_k, b_{k+1}) + of t, extracted at bit granularity as u = t >> (wp - b_{k+1}) + with the bits above depth b_k masked off (t is read in place; arb + instead subtracts used bits from a working copy). Each factor is + the exact rational + + 1 + s_k = (Q_k 2^{Qexp_k} + T_k) / (Q_k 2^{Qexp_k}), + + and instead of dividing per level, the loop accumulates + + NUM = prod (Q_k 2^{Qexp_k} + T_k), DEN = prod Q_k, + QE = sum Qexp_k, + + NUM and DEN msb-truncated to wn + 3 limbs with dropped-bit + exponents, and finishes with a SINGLE balanced division by + fixed_div_newton. The slices are processed in REVERSE (deepest + first): a deep factor's numerator carries only + qbits_k + (wp - b_k) + O(1) significant bits -- it is + Q_k (1 + s_k) with s_k < 2^{-b_k} -- and its denominator only + qbits_k, so both products grow from small to large and every + accumulation multiplication is balanced against the content + gathered so far, the same growing-product scheme as the exp and + trig reconstructions. For the level-capped variant (alg 3) the + remainder below the last splitting slice is evaluated by the + tuned series choice and folded in as the deepest factor + (numerator 1 + f_res, denominator 1). + + Frames: every quantity is a mantissa with an explicit power of + two, value = m 2^e. A factor F_k = Q_k 2^{Qexp_k} + T_k is + formed exactly when it fits in the cap window and otherwise only + its top window is assembled, Q_k shifted into place and T_k's + limb-aligned tail added, the dropped low bits going into e. + Errors: each factor and product truncation is one ulp at the + cap, a relative 2^(-64 (wn + 3) + 64); over at most log2(wp) + levels on both sides of the quotient, plus the Newton division's + 4 B^{-wn-2}/den and the final one-ulp placement, everything + lands orders of magnitude inside FIXED_EXP_REDUCED_MAX_ERR. No + per-level quotient frames, shifts or additions remain, and the + product starts from the deepest factor rather than from a + full-precision 1. */ +#define EXP_BURST_GUARD 3 + +static void +_fixed_exp_reduced_burst(nn_ptr y, nn_srcptr t, slong wn, + flint_bitcnt_t r, int levels) +{ + slong wp = FLINT_BITS * wn; + slong cap = wn + EXP_BURST_GUARD; + slong bnds[FLINT_BITS + 2]; + slong nb = 0, k, nn_, dn, l; + slong nexp, dexp, QE; + nn_ptr num, den, sc, q; + TMP_INIT; + + /* boundary ladder b_0 = r < b_1 < ... < b_nb = wp */ + bnds[nb++] = (slong) r; + while (bnds[nb - 1] < wp) + { + slong nxt = FLINT_MIN(2 * bnds[nb - 1], wp); + if (levels > 0 && nb > levels) + nxt = wp; /* level cap: one final series slice */ + bnds[nb] = nxt; + nb++; + } + nb--; /* nb slices: [bnds[k], bnds[k+1]) */ + + TMP_START; + num = TMP_ALLOC((2 * (cap + 1) + 2 * cap + 2 + (wn + 4)) + * sizeof(ulong)); + den = num + cap + 1; + sc = den + cap + 1; + q = sc + 2 * cap + 2; + + num[0] = 1; + nn_ = 1; + nexp = 0; + den[0] = 1; + dn = 1; + dexp = 0; + QE = 0; + + for (k = nb - 1; k >= 0; k--) + { + slong blo = bnds[k], bhi = bnds[k + 1]; + int series = (levels > 0 && k >= levels); + slong fn, fexp; + nn_ptr f; + TMP_INIT; + + TMP_START; + + if (series) + { + /* remainder below depth blo: tuned series on a copy + with the bits above depth blo cleared; factor + 1 + f_res over denominator 1 */ + nn_ptr xres, fac; + slong zq = blo / FLINT_BITS; + int zb = (int) (blo % FLINT_BITS); + + fac = TMP_ALLOC((wn + 2 + wn) * sizeof(ulong)); + xres = fac + wn + 2; + flint_mpn_copyi(xres, t, wn); + flint_mpn_zero(xres + wn - zq, zq); + if (zb && zq < wn) + xres[wn - zq - 1] &= (UWORD(1) + << (FLINT_BITS - zb)) - 1; + fixed_exp_reduced(fac, xres, wn, (flint_bitcnt_t) blo, 0); + + /* value = FacInt 2^(-64 wn), FacInt over wn + 1 limbs + with the units limb on top; cap >= wn + 1, so no + truncation is ever needed here */ + f = fac; + fn = wn + 1; + fexp = -FLINT_BITS * wn; + } + else + { + slong sq = (wp - bhi) / FLINT_BITS; + int sb = (int) ((wp - bhi) % FLINT_BITS); + slong un = (bhi + FLINT_BITS - 1) / FLINT_BITS + 1; + slong xn, N, tn, qn, mq; + int mb; + flint_bitcnt_t Qexp; + nn_ptr u, T, Q, F; + + u = TMP_ALLOC(un * sizeof(ulong)); + if (sb) + mpn_rshift(u, t + sq, wn - sq, sb); + else + flint_mpn_copyi(u, t + sq, wn - sq); + xn = FLINT_MIN(un, wn - sq); + /* mask the bits above depth blo (shallower slices) */ + mq = (bhi - blo) / FLINT_BITS; + mb = (int) ((bhi - blo) % FLINT_BITS); + if (mq < xn) + { + if (mb) + { + u[mq] &= (UWORD(1) << mb) - 1; + flint_mpn_zero(u + mq + 1, xn - mq - 1); + } + else + flint_mpn_zero(u + mq, xn - mq); + xn = FLINT_MIN(xn, mq + 1); + } + while (xn > 0 && u[xn - 1] == 0) + xn--; + + if (xn == 0) + { + TMP_END; + continue; + } + + { + slong ubits = FLINT_BITS * (xn - 1) + + FLINT_BIT_COUNT(u[xn - 1]); + N = _fixed_exp_bs_num_terms( + (flint_bitcnt_t) (bhi - ubits), wp + 64); + } + + T = TMP_ALLOC(( + (N * (bhi + 128)) / FLINT_BITS + 4 + + (N * FLINT_BIT_COUNT((ulong) N + 1)) / FLINT_BITS + + 3 + (cap + 3)) * sizeof(ulong)); + Q = T + (N * (bhi + 128)) / FLINT_BITS + 4; + F = Q + (N * FLINT_BIT_COUNT((ulong) N + 1)) + / FLINT_BITS + 3; + + _fixed_exp_sum_bs_powtab(T, &tn, Q, &qn, &Qexp, + u, xn, (flint_bitcnt_t) bhi, N); + + /* DEN *= Q_k, msb-truncated at cap */ + if (dn >= qn) + flint_mpn_mul(sc, den, dn, Q, qn); + else + flint_mpn_mul(sc, Q, qn, den, dn); + l = dn + qn; + while (l > 1 && sc[l - 1] == 0) + l--; + if (l > cap) + { + flint_mpn_copyi(den, sc + (l - cap), cap); + dexp += FLINT_BITS * (l - cap); + dn = cap; + } + else + { + flint_mpn_copyi(den, sc, l); + dn = l; + } + QE += (slong) Qexp; + + _fixed_exp_burst_factor(F, &fn, &fexp, T, tn, Q, qn, + Qexp, cap); + f = F; + } + + /* NUM *= f, msb-truncated at cap */ + if (nn_ >= fn) + flint_mpn_mul(sc, num, nn_, f, fn); + else + flint_mpn_mul(sc, f, fn, num, nn_); + l = nn_ + fn; + while (l > 1 && sc[l - 1] == 0) + l--; + nexp += fexp; + if (l > cap) + { + flint_mpn_copyi(num, sc + (l - cap), cap); + nexp += FLINT_BITS * (l - cap); + nn_ = cap; + } + else + { + flint_mpn_copyi(num, sc, l); + nn_ = l; + } + + TMP_END; + } + + /* y = (num 2^nexp) / (den 2^{dexp + QE}), one balanced Newton + division. Normalize both mantissas to fractions in [1/2, 1) + (so the denominator's top limb is nonzero as the kernel + requires); the log difference is then exactly + (nexp + numb) - (dexp + QE + denb) with numb, denb the + mantissa bit lengths, and y in [1, e) makes that difference + 0 or 1. */ + { + slong numb = FLINT_BITS * (nn_ - 1) + + FLINT_BIT_COUNT(num[nn_ - 1]); + slong denb = FLINT_BITS * (dn - 1) + + FLINT_BIT_COUNT(den[dn - 1]); + slong sh = (nexp + numb) - (dexp + QE + denb); + slong tot, yq; + int lz, yb; + nn_ptr a2, b2; + + a2 = sc; + b2 = sc + cap + 1; + + lz = (int) ((FLINT_BITS - (denb % FLINT_BITS)) + % FLINT_BITS); + if (lz) + mpn_lshift(a2, den, dn, lz); + else + flint_mpn_copyi(a2, den, dn); + + lz = (int) ((FLINT_BITS - (numb % FLINT_BITS)) + % FLINT_BITS); + if (lz) + mpn_lshift(b2, num, nn_, lz); + else + flint_mpn_copyi(b2, num, nn_); + + fixed_div_newton(q, b2, nn_, a2, dn, wn + 2); + + /* q = b/a in [1/2, 2) over wn + 2 fraction limbs; value + y = (b/a) 2^sh with sh in {0, 1}. Place into the y + frame (wn fraction limbs + units): a plain truncating + shift, one-sided within 1 ulp on top of the division's + error. */ + FLINT_ASSERT(sh == 0 || sh == 1); + tot = 2 * FLINT_BITS - sh; + yq = tot / FLINT_BITS; + yb = (int) (tot % FLINT_BITS); + if (yb) + { + mpn_rshift(q, q + yq, wn + 4 - yq, yb); + flint_mpn_copyi(y, q, wn + 1); + } + else + flint_mpn_copyi(y, q + yq, wn + 1); + } + TMP_END; +} + + +void +fixed_exp_reduced(nn_ptr y, nn_srcptr t, slong wn, flint_bitcnt_t r, + int alg) +{ + FLINT_ASSERT(r >= 16); + + if (alg == 0) + { + /* tuned automatic choice; thresholds from tune-exp-reduced. + For 16 <= r < 32 only the burst paths apply (the series + functions require t < 2^-32). */ + if (FLINT_BITS * (ulong) wn >= FIXED_EXP_FULLBURST_TERMS * r) + alg = 4; + else if (r < 32 || EXP_USE_BURST(wn, r)) + alg = 3; + else if (EXP_USE_SINH(wn, r)) + alg = 2; + else + alg = 1; + } + + FLINT_ASSERT(alg >= 3 || r >= 32); + + if (alg == 3 || alg == 4) + { + _fixed_exp_reduced_burst(y, t, wn, r, (alg == 4) ? 0 : 1); + return; + } + + if (alg == 1) + { + fixed_exp_rs(y, t, wn); + } + else + { + nn_ptr s, u2, rt, rem; + TMP_INIT; + + TMP_START; + s = TMP_ALLOC(((wn + 1) + (2 * wn + 1) + (wn + 1) + (wn + 2)) + * sizeof(ulong)); + u2 = s + (wn + 1); + rt = u2 + (2 * wn + 1); + rem = rt + (wn + 1); + + fixed_sinh_rs(s, t, wn); + + /* u2 = (1 + sinh(t)^2) 2^(2 FLINT_BITS wn) */ + flint_mpn_zero(u2, wn); + flint_mpn_sqrhigh(u2 + wn, s, wn); + u2[2 * wn] = 1; + + /* cosh(t) = sqrt(1 + sinh(t)^2). Above the cutoff, run the + Karp-Markstein square root instead of mpn_sqrtrem: view u2 + as a fraction with 2wn + 2 limbs -- a zero limb on top, the + widened input normalization of fixed_rsqrt_newton, so that + the square root's scale factor is a limb shift -- in + [B^-2, 1.3 B^-2), and compute sqrt with wn + 3 fraction + limbs. Truncating the guard limbs leaves rt within + 1 + eps ulps of the floored square root (the two-sided + error 4 B^(-wn-3) / sqrt(u2') plus half an ulp from the + short operand stay under one rt ulp) -- absorbed by the + ~20 ulp reconstruction budget accounted in + FIXED_EXP_BITWISE_RS_MAX_ERR. + + The default cutoff is deliberately high: up to n = 8192 + this measured as a wash to a few percent slower in context + on the development machine (mpn_sqrtrem's + divide-and-conquer being too good and the square root too + small a fraction of exp; lowering the EXP_USE_SINH + crossover to exploit a cheaper square root also gained + nothing). The Newton square root's asymptotic edge should + eventually tell; the path is kept behind the cutoff for + experiments beyond that range. See dev/notes. */ + if (wn < FIXED_EXP_SQRT_NEWTON_CUTOFF) + { + mpn_sqrtrem(rt, rem, u2, 2 * wn + 1); + } + else + { + nn_ptr q, a2; + TMP_INIT; + TMP_START; + a2 = TMP_ALLOC((wn + 2 + wn + 5) * sizeof(ulong)); + q = a2 + wn + 2; + /* the Newton square root reads short input: only the top + wn + 1 limbs of u2 matter at this precision, plus the + zero top limb that makes the scale a limb shift */ + flint_mpn_copyi(a2, u2 + wn, wn + 1); + a2[wn + 1] = 0; + fixed_sqrt_newton(q, a2, wn + 2, wn + 3); + flint_mpn_copyi(rt, q + 2, wn + 1); + TMP_END; + } + + /* exp(t) = sinh(t) + cosh(t) */ + mpn_add_n(y, rt, s, wn + 1); + + TMP_END; + } +} diff --git a/src/fixed/exp_sum_bs.c b/src/fixed/exp_sum_bs.c new file mode 100644 index 0000000000..d36de432e3 --- /dev/null +++ b/src/fixed/exp_sum_bs.c @@ -0,0 +1,445 @@ +/* + Copyright (C) 2014, 2022, 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "flint.h" +#include "mpn_extras.h" +#include "fixed.h" + +/* Pure mpn port of _arb_exp_sum_bs_powtab (single-threaded): + + T / (Q 2^Qexp) = sum_{k=1}^{N} x^k / (k! 2^(r k)), + + x > 0 given as (xp, xn) limbs, by binary splitting with a table of + the powers x^step that the fixed midpoint recursion needs (the + b - a <= 2 cases inlined), the 2-adic parts of the factorials + folded into Qexp. All numbers are nonnegative mpn integers with + tracked lengths; every flint_mpn_mul call orders its operands. + + The split-step exponent computation and the power-table structure + are verbatim from the arb original, and t-exp_sum_bs checks the + port against it for bit identity. */ + +/* exponents (m - a) reachable from [0, n) with m = a + (b-a)/2 and + b - a = 2 inlined; at most 2 FLINT_BITS entries, returned sorted */ +static slong +compute_bs_exponents(slong * tab, slong n) +{ + slong a, b, aa, ba, bb, length; + + if (n == 1) + { + tab[0] = 1; + return 1; + } + if (n == 2 || n == 3 || n == 4) + { + tab[0] = 1; + tab[1] = 2; + return 2; + } + if (n == 6) + { + tab[0] = 1; + tab[1] = 2; + tab[2] = 3; + return 3; + } + + a = n >> 1; + b = n - (n >> 1); + tab[0] = a; + length = 1; + + for (;;) + { + aa = a >> 1; + ba = b >> 1; + bb = b - ba; + + tab[length] = ba; + length++; + + if (ba == 3) + { + tab[length] = 2; + tab[length + 1] = 1; + length += 2; + break; + } + + if (ba == 1 || (ba == 2 && (n & (n - 1)) == 0)) + break; + + if (aa != ba && aa != 1) + { + tab[length] = aa; + length++; + } + + a = aa; + b = bb; + } + + if (tab[length - 1] != 1) + { + tab[length] = 1; + length++; + } + + for (a = 0; a < length / 2; a++) + { + b = tab[a]; + tab[a] = tab[length - a - 1]; + tab[length - a - 1] = b; + } + + return length; +} + +static slong +get_exp_pos(const slong * tab, slong step) +{ + slong i; + for (i = 0; ; i++) + { + if (tab[i] == step) + return i; + FLINT_ASSERT(tab[i] != 0); + } +} + + +/* Number of series terms for the binary splitting at |x| < 2^-r and + target precision prec bits, accounting for the factorial growth + (not only the geometric decay -- decisive for small r at high + precision), then padded to a higher 2-valuation so that the fixed + midpoint splitting needs fewer distinct entries in the power + table. Port of bs_num_terms / _arb_exp_taylor_bound; the short + table bounds lg(1/N!) from above, and beyond it a floor-log sum + continues the bound safely (each lg j >= floor(lg j)). */ + +#define FAC_TABSIZE 256 + +static const short rec_fac_bound_tab[FAC_TABSIZE] = +{ + 0, 0, -1, -2, -4, -6, -9, -12, -15, -18, -21, -25, -28, -32, -36, + -40, -44, -48, -52, -56, -61, -65, -69, -74, -79, -83, -88, -93, -97, + -102, -107, -112, -117, -122, -127, -132, -138, -143, -148, -153, + -159, -164, -169, -175, -180, -186, -191, -197, -202, -208, -214, + -219, -225, -231, -237, -242, -248, -254, -260, -266, -272, -278, + -284, -289, -295, -302, -308, -314, -320, -326, -332, -338, -344, + -350, -357, -363, -369, -375, -382, -388, -394, -401, -407, -413, + -420, -426, -433, -439, -446, -452, -458, -465, -472, -478, -485, + -491, -498, -504, -511, -518, -524, -531, -538, -544, -551, -558, + -564, -571, -578, -585, -591, -598, -605, -612, -619, -626, -632, + -639, -646, -653, -660, -667, -674, -681, -688, -695, -702, -709, + -716, -723, -730, -737, -744, -751, -758, -765, -772, -779, -786, + -793, -801, -808, -815, -822, -829, -836, -844, -851, -858, -865, + -872, -880, -887, -894, -901, -909, -916, -923, -931, -938, -945, + -952, -960, -967, -975, -982, -989, -997, -1004, -1011, -1019, -1026, + -1034, -1041, -1049, -1056, -1064, -1071, -1078, -1086, -1093, -1101, + -1108, -1116, -1123, -1131, -1139, -1146, -1154, -1161, -1169, -1176, + -1184, -1192, -1199, -1207, -1214, -1222, -1230, -1237, -1245, -1253, + -1260, -1268, -1276, -1283, -1291, -1299, -1306, -1314, -1322, -1329, + -1337, -1345, -1353, -1360, -1368, -1376, -1384, -1391, -1399, -1407, + -1415, -1423, -1430, -1438, -1446, -1454, -1462, -1470, -1477, -1485, + -1493, -1501, -1509, -1517, -1525, -1532, -1540, -1548, -1556, -1564, + -1572, -1580, -1588, -1596, -1604, -1612, -1620, -1628, -1636, -1644, + -1652, -1660, -1668, -1675 +}; + +slong +_fixed_exp_bs_num_terms(flint_bitcnt_t r, slong prec) +{ + slong N, fb; + + fb = 0; + for (N = 1; ; N++) + { + if (N < FAC_TABSIZE) + fb = rec_fac_bound_tab[N]; + else + fb -= FLINT_BIT_COUNT((ulong) N) - 1; + + if (-(slong) r * N + fb < -prec - 1) + break; + } + + if (N > 10000) + while (N % 128 != 0) + N++; + if (N > 1000) + while (N % 16 != 0) + N++; + if (N > 100) + while (N % 2 != 0) + N++; + + return N; +} + +/* ---- small mpn-number helpers: (ptr, len), len 0 means zero ---- */ + +static slong +nnn_normalize(nn_srcptr p, slong n) +{ + while (n > 0 && p[n - 1] == 0) + n--; + return n; +} + +/* z = a * b, ordered; z not aliased; returns length */ +static slong +nnn_mul(nn_ptr z, nn_srcptr a, slong an, nn_srcptr b, slong bn) +{ + if (an == 0 || bn == 0) + return 0; + if (an >= bn) + flint_mpn_mul(z, a, an, b, bn); + else + flint_mpn_mul(z, b, bn, a, an); + return nnn_normalize(z, an + bn); +} + +/* z = a << e (bits), z not aliased below a; returns length */ +static slong +nnn_shl(nn_ptr z, nn_srcptr a, slong an, flint_bitcnt_t e) +{ + slong q = e / FLINT_BITS; + int b = (int) (e % FLINT_BITS); + if (an == 0) + return 0; + flint_mpn_zero(z, q); + if (b) + { + z[an + q] = mpn_lshift(z + q, a, an, b); + return nnn_normalize(z, an + q + 1); + } + flint_mpn_copyi(z + q, a, an); + return an + q; +} + +/* z += a (in place, z has room); returns new length */ +static slong +nnn_add(nn_ptr z, slong zn, nn_srcptr a, slong an) +{ + ulong cy; + if (an == 0) + return zn; + if (zn >= an) + { + cy = mpn_add(z, z, zn, a, an); + z[zn] = cy; + return zn + (cy != 0); + } + flint_mpn_zero(z + zn, an - zn); + cy = mpn_add(z, a, an, z, zn); + z[an] = cy; + return an + (cy != 0); +} + +/* ---- the recursion ---- */ + +typedef struct +{ + const slong * xexp; + nn_srcptr * xpow; + const slong * xlen; + flint_bitcnt_t r; + slong xn; /* limbs of x, for size bounds */ +} +bs_args; + +/* T length bound for the range [a, b): T spans Qexp + O(qbits) + bits with Qexp = sum (r + ctz) over the range, so the r term is + intrinsic -- the splitting integers scale with r, which is why a + bit-burst caller slicing on limb boundaries pays a factor + (64 D) / r in tree content over bit-granular slices (measured + ~1.5x peak RSS vs arb_exp_arf_bb at r = 16; see dev/notes) */ +static slong +tbound(const bs_args * args, slong a, slong b) +{ + /* T < Q 2^Qexp: Qexp <= (b-a)(r+64) and Q < b^(b-a) */ + return ((b - a) * ((slong) args->r + 2 * FLINT_BITS)) + / FLINT_BITS + 4; +} + +static slong +qbound(const bs_args * args, slong a, slong b) +{ + return ((b - a) * FLINT_BIT_COUNT((ulong) b + 1)) / FLINT_BITS + 3; +} + +static void +bsplit(nn_ptr T, slong * tn, nn_ptr Q, slong * qn, + flint_bitcnt_t * Qexp, const bs_args * args, slong a, slong b) +{ + int cc; + + if (b - a == 1) + { + cc = flint_ctz((ulong) (a + 1)); + Q[0] = (ulong) (a + 1) >> cc; + *qn = 1; + *Qexp = args->r + cc; + flint_mpn_copyi(T, args->xpow[0], args->xlen[0]); + *tn = args->xlen[0]; + } + else if (b - a == 2) + { + /* T = x (a+2) 2^r + x^2 */ + nn_ptr t1; + slong l1; + TMP_INIT; + TMP_START; + t1 = TMP_ALLOC((args->xlen[0] + 2) * sizeof(ulong)); + t1[args->xlen[0]] = mpn_mul_1(t1, args->xpow[0], + args->xlen[0], (ulong) (a + 2)); + l1 = nnn_normalize(t1, args->xlen[0] + 1); + *tn = nnn_shl(T, t1, l1, args->r); + FLINT_ASSERT(args->xexp[1] == 2); + *tn = nnn_add(T, *tn, args->xpow[1], args->xlen[1]); + TMP_END; + + cc = flint_ctz((ulong) (a + 2)); + Q[0] = (ulong) (a + 2) >> cc; + *Qexp = args->r + cc; + cc = flint_ctz((ulong) (a + 1)); + { + ulong hi; + umul_ppmm(hi, Q[0], Q[0], (ulong) (a + 1) >> cc); + Q[1] = hi; + *qn = 1 + (hi != 0); + } + *Qexp += args->r + cc; + } + else + { + slong step, m, i, t2n, q2n, l; + flint_bitcnt_t Q2exp; + nn_ptr T2, Q2, sc; + TMP_INIT; + + step = (b - a) / 2; + m = a + step; + + TMP_START; + T2 = TMP_ALLOC((tbound(args, m, b) + qbound(args, m, b) + + tbound(args, a, b) + 8) * sizeof(ulong)); + Q2 = T2 + tbound(args, m, b); + sc = Q2 + qbound(args, m, b); + + bsplit(T, tn, Q, qn, Qexp, args, a, m); + bsplit(T2, &t2n, Q2, &q2n, &Q2exp, args, m, b); + + /* T = (T Q2) << Q2exp + x^step T2 */ + l = nnn_mul(sc, T, *tn, Q2, q2n); + *tn = nnn_shl(T, sc, l, Q2exp); + i = get_exp_pos(args->xexp, step); + l = nnn_mul(sc, args->xpow[i], args->xlen[i], T2, t2n); + *tn = nnn_add(T, *tn, sc, l); + + /* Q = Q Q2 */ + l = nnn_mul(sc, Q, *qn, Q2, q2n); + flint_mpn_copyi(Q, sc, l); + *qn = l; + *Qexp = *Qexp + Q2exp; + TMP_END; + } +} + +/* T (returned length *tn) / (Q (*qn) 2^Qexp) = the sum above. + T must have room for ((N (r + 128)) / 64 + 4) limbs and Q for + ((N bits(N+1)) / 64 + 3) limbs. */ +void +_fixed_exp_sum_bs_powtab(nn_ptr T, slong * tn, nn_ptr Q, slong * qn, + flint_bitcnt_t * Qexp, nn_srcptr xp, slong xn, flint_bitcnt_t r, + slong N) +{ + slong xexp[2 * FLINT_BITS]; + slong xlen[2 * FLINT_BITS]; + nn_srcptr xpow[2 * FLINT_BITS]; + nn_ptr storage; + slong length, i, off; + bs_args args; + + FLINT_ASSERT(N >= 1 && xn >= 1 && xp[xn - 1] != 0); + /* the size bounds assume a chunk-sized argument x < 2^r */ + FLINT_ASSERT(FLINT_BITS * (xn - 1) + FLINT_BIT_COUNT(xp[xn - 1]) + <= (slong) r); + + for (i = 0; i < 2 * FLINT_BITS; i++) + xexp[i] = 0; + length = compute_bs_exponents(xexp, N); + + /* power table: xpow[i] = x^(xexp[i]), by squarings, preferring + squares of the previous entries (same structure as the arb + original, which guarantees one of the four cases applies) */ + { + slong total = 0; + for (i = 0; i < length; i++) + total += xexp[i] * xn + 1; + storage = flint_malloc(total * sizeof(ulong)); + } + off = 0; + for (i = 0; i < length; i++) + { + nn_ptr dst = storage + off; + off += xexp[i] * xn + 1; + + if (i == 0) + { + FLINT_ASSERT(xexp[0] == 1); + flint_mpn_copyi(dst, xp, xn); + xlen[0] = xn; + } + else if (xexp[i] == 2 * xexp[i - 1]) + { + flint_mpn_sqr(dst, xpow[i - 1], xlen[i - 1]); + xlen[i] = nnn_normalize(dst, 2 * xlen[i - 1]); + } + else if (i >= 2 && xexp[i] == 2 * xexp[i - 2]) + { + flint_mpn_sqr(dst, xpow[i - 2], xlen[i - 2]); + xlen[i] = nnn_normalize(dst, 2 * xlen[i - 2]); + } + else + { + /* an odd 2k+1 case: square then multiply by x, via a + scratch for the square */ + nn_ptr sq; + slong sl, src; + if (xexp[i] == 2 * xexp[i - 1] + 1) + src = i - 1; + else + { + FLINT_ASSERT(i >= 2 && xexp[i] == 2 * xexp[i - 2] + 1); + src = i - 2; + } + sq = flint_malloc((2 * xlen[src] + 1) * sizeof(ulong)); + flint_mpn_sqr(sq, xpow[src], xlen[src]); + sl = nnn_normalize(sq, 2 * xlen[src]); + xlen[i] = nnn_mul(dst, sq, sl, xp, xn); + flint_free(sq); + } + xpow[i] = dst; + } + + args.xexp = xexp; + args.xpow = xpow; + args.xlen = xlen; + args.r = r; + args.xn = xn; + + bsplit(T, tn, Q, qn, Qexp, &args, 0, N); + + flint_free(storage); +} diff --git a/src/fixed/frac_bsplit.inc b/src/fixed/frac_bsplit.inc deleted file mode 100644 index 5f2f1b1eff..0000000000 --- a/src/fixed/frac_bsplit.inc +++ /dev/null @@ -1,282 +0,0 @@ -/* - Copyright (C) 2026 Fredrik Johansson - - This file is part of FLINT. - - FLINT is free software: you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License (LGPL) as published - by the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. See . -*/ - -/* Binary splitting for atan(p/q) and atanh(p/q) - (arb_atan1_frac_bsplit, valid for p = 1: the leaves build the - terms from q alone) and for log(1 + p/q) (arb_log1_frac_bsplit), - carried from the newfastexp2 prototype; the alternating - (non-hyperbolic) leaf signs were added here, everything else is - verbatim. Compared with arb_atan_frac_bsplit these fold pairs of - terms at the leaves and strip two-powers from the denominators as - they go. TODO: reimplement natively with mpn arithmetic. */ - -#ifndef FIXED_FRAC_BSPLIT_INC -#define FIXED_FRAC_BSPLIT_INC - -#include -#include "mag.h" - -#ifndef FIXED_LOG2_CONST -#define FIXED_LOG2_CONST 0.69314718055994530942 -#endif - -static void -atan1_bsplit(fmpz_t p1, fmpz_t q1, fmpz_t r1, - const fmpz_t p, const fmpz_t q, - const fmpz_t ppow2, const fmpz_t qpow2, const fmpz_t qpow4, - int alternate, slong a, slong b, int cont) -{ - if (b - a == 1) - { - /* alternating: the terms carry (-1)^a absolutely, so the - merges below need no sign logic */ - if (alternate && (a & 1)) - fmpz_set_si(p1, -1); - else - fmpz_one(p1); - fmpz_mul_ui(q1, a == 0 ? q : qpow2, 2*a+1); - fmpz_set_ui(r1, 2*a+1); - } - else if (b - a == 2 && a < (WORD(1) << (FLINT_BITS / 2 - 2))) - { - fmpz_mul_ui(p1, qpow2, 2 * a + 3); - if (alternate) - { - fmpz_sub_ui(p1, p1, 2 * a + 1); - if (a & 1) - fmpz_neg(p1, p1); - } - else - fmpz_add_ui(p1, p1, 2 * a + 1); - - if (a == 0) - { - fmpz_mul(q1, q, qpow2); - fmpz_mul_ui(q1, q1, (2 * a + 1) * (2 * a + 3)); - } - else - { - fmpz_mul_ui(q1, qpow4, (2 * a + 1) * (2 * a + 3)); - } - - fmpz_set_ui(r1, (2 * a + 1) * (2 * a + 3)); - } - else - { - fmpz_t p2, q2, r2; - slong m; - - m = (a + b) / 2; - - fmpz_init(p2); - fmpz_init(q2); - fmpz_init(r2); - - atan1_bsplit(p1, q1, r1, p, q, ppow2, qpow2, qpow4, alternate, a, m, 1); - atan1_bsplit(p2, q2, r2, p, q, ppow2, qpow2, qpow4, alternate, m, b, cont); - - fmpz_mul(p1, p1, q2); - fmpz_addmul(p1, r1, p2); - fmpz_mul(q1, q1, q2); - - if (cont) - fmpz_mul(r1, r1, r2); - - fmpz_clear(p2); - fmpz_clear(q2); - fmpz_clear(r2); - } -} - -static void -arb_atan1_frac_bsplit(arb_t s, const fmpz_t p, const fmpz_t q, int hyperbolic, slong prec) -{ - fmpz_t P, Q, R, p2, q2, q4; - double logqp; - slong N; - mag_t err; - - if (fmpz_is_zero(p)) - { - arb_zero(s); - return; - } - - if (fmpz_is_zero(q)) - { - arb_indeterminate(s); - return; - } - - if (fmpz_sgn(p) < 0) - { - fmpz_t t; - fmpz_init(t); - fmpz_neg(t, p); - arb_atan1_frac_bsplit(s, t, q, hyperbolic, prec); - arb_neg(s, s); - fmpz_clear(t); - return; - } - - fmpz_init(P); - fmpz_init(Q); - fmpz_init(R); - fmpz_init(p2); - fmpz_init(q2); - fmpz_init(q4); - mag_init(err); - - /* If N >= 1 and p/q <= 1/2, the error is bounded by (p/q)^(2N+1). - For error <= 2^-prec, it is sufficient to pick - N >= (1/2) * (prec * log(2) / log(q/p) - 1). */ - - { - slong qexp, pexp; - double logp, logq; - - logp = fmpz_get_d_2exp(&pexp, p); - logq = fmpz_get_d_2exp(&qexp, q); - - logqp = fabs(logq) / logp; - logqp = mag_d_log_lower_bound(logqp) * (1.0 - 1e-12); - logqp = logqp + (qexp - pexp) * FIXED_LOG2_CONST * (1.0 - 1e-12); - } - - N = ceil((prec * (0.5 * FIXED_LOG2_CONST) / logqp) * (1.0 + 1e-12)); - N = FLINT_MAX(N, 1); - N = FLINT_MIN(N, 4 * prec); - if (N % 2) - N++; - - fmpz_mul(p2, p, p); - fmpz_mul(q2, q, q); - fmpz_mul(q4, q2, q2); - atan1_bsplit(P, Q, R, p, q, p2, q2, q4, !hyperbolic, 0, N, 0); - - mag_set_fmpz(err, p); - mag_div_fmpz(err, err, q); - mag_geom_series(err, err, 2 * N + 1); - mag_div_ui(err, err, 2 * N + 1); - - arb_fmpz_div_fmpz(s, P, Q, prec); - arb_add_error_mag(s, err); - - fmpz_clear(p2); - fmpz_clear(q2); - fmpz_clear(q4); - fmpz_clear(P); - fmpz_clear(Q); - fmpz_clear(R); - mag_clear(err); -} - -#ifndef FIXED_FRAC_BSPLIT_NO_LOG1 - -static void -log1_bsplit(fmpz_t p1, fmpz_t q1, slong * q1exp, fmpz_t r1, - const fmpz_t p, const fmpz_t q, - int alternate, slong a, slong b, int cont) -{ - if (b - a == 1) - { - fmpz_set(p1, p); - fmpz_set(q1, q); - fmpz_mul_ui(q1, q1, a+1); - fmpz_mul_ui(r1, p1, a+1); - - q1exp[0] = fmpz_val2(q1); - fmpz_tdiv_q_2exp(q1, q1, q1exp[0]); - - if (alternate) - { - fmpz_neg(p1, p1); - fmpz_neg(q1, q1); - } - } - else - { - fmpz_t p2, q2, r2; - slong q2exp[1]; - slong m; - - m = (a + b) / 2; - - fmpz_init(p2); - fmpz_init(q2); - fmpz_init(r2); - - log1_bsplit(p1, q1, q1exp, r1, p, q, alternate, a, m, 1); - log1_bsplit(p2, q2, q2exp, r2, p, q, alternate, m, b, cont); - - fmpz_mul(p1, p1, q2); - fmpz_mul_2exp(p1, p1, q2exp[0]); - fmpz_addmul(p1, r1, p2); - fmpz_mul(q1, q1, q2); - q1exp[0] += q2exp[0]; - if (cont) - fmpz_mul(r1, r1, r2); - - fmpz_clear(p2); - fmpz_clear(q2); - fmpz_clear(r2); - } -} - -static void -arb_log1_frac_bsplit(arb_t s, const fmpz_t p, const fmpz_t q, slong prec) -{ - fmpz_t P, Q, R; - double logqp; - slong N, Qexp; - mag_t err; - - fmpz_init(P); - fmpz_init(Q); - fmpz_init(R); - mag_init(err); - - { - slong qexp, pexp; - double logp, logq; - - logp = fmpz_get_d_2exp(&pexp, p); - logq = fmpz_get_d_2exp(&qexp, q); - - logqp = fabs(logq) / logp; - logqp = mag_d_log_lower_bound(logqp) * (1.0 - 1e-12); - logqp = logqp + (qexp - pexp) * FIXED_LOG2_CONST * (1.0 - 1e-12); - } - - N = ceil((prec * (FIXED_LOG2_CONST) / logqp) * (1.0 + 1e-12)); - N = FLINT_MAX(N, 1); - N = FLINT_MIN(N, 4 * prec); - - log1_bsplit(P, Q, &Qexp, R, p, q, 1, 0, N, 0); - - mag_set_fmpz(err, p); - mag_div_fmpz(err, err, q); - mag_geom_series(err, err, N + 1); - mag_div_ui(err, err, N + 1); - - arb_fmpz_div_fmpz(s, P, Q, prec); - arb_mul_2exp_si(s, s, -Qexp); - arb_add_error_mag(s, err); - - fmpz_clear(P); - fmpz_clear(Q); - fmpz_clear(R); - mag_clear(err); -} - -#endif /* FIXED_FRAC_BSPLIT_NO_LOG1 */ - -#endif /* FIXED_FRAC_BSPLIT_INC */ diff --git a/src/fixed/impl.h b/src/fixed/impl.h deleted file mode 100644 index cfdfd5507f..0000000000 --- a/src/fixed/impl.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (C) 2026 Fredrik Johansson - - This file is part of FLINT. - - FLINT is free software: you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License (LGPL) as published - by the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. See . -*/ - -/* Library-internal view of the cached reduction tables. - - These thread-local objects are shared across the translation units - of the module (the dispatch files, the reduction, and the - specialized per-size implementations) but are deliberately NOT - declared in fixed.h: Windows DLLs cannot export thread-local data, - so external consumers -- the test suite -- go through the - _fixed_exp_logs_entry / _fixed_atans_entry accessors instead. - - Each entry occupies _fixed_{exp_logs,atans}_n limbs: the value - limbs with one guard limb below them. Consumers wanting the top n - limbs of entry i read tab + i * stride + (stride - n). */ - -#ifndef FIXED_IMPL_H -#define FIXED_IMPL_H - -#include "flint.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern FLINT_TLS_PREFIX nn_ptr _fixed_exp_logs; -extern FLINT_TLS_PREFIX slong _fixed_exp_logs_n; -extern FLINT_TLS_PREFIX slong _fixed_exp_logs_r; - -extern FLINT_TLS_PREFIX nn_ptr _fixed_atans; -extern FLINT_TLS_PREFIX slong _fixed_atans_n; -extern FLINT_TLS_PREFIX slong _fixed_atans_r; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/fixed/log1p_bitwise_rs.c b/src/fixed/log1p_bitwise_rs.c index 7be949b9bc..453ecf2297 100644 --- a/src/fixed/log1p_bitwise_rs.c +++ b/src/fixed/log1p_bitwise_rs.c @@ -12,7 +12,15 @@ #include "flint.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" + +/* quotient length from which the argument-reduction division runs + through fixed_div_newton instead of mpn_tdiv_qr; set from + in-context measurements (tight-loop benchmarks favor Newton much + earlier, but embedded in the evaluation it loses until the FFT + range, see dev/notes) */ +#ifndef FIXED_RED_DIV_NEWTON_CUTOFF +#define FIXED_RED_DIV_NEWTON_CUTOFF 60 +#endif /* log(1 + x) by the dual of the bitwise exp reduction (the L-mode BKM recurrence): greedily multiply P (starting from 1) by factors @@ -58,7 +66,7 @@ <= 30 ulp. Total < 3 (r + 2) + 40, bounded by FIXED_LOG1P_BITWISE_RS_MAX_ERR(n, r) = 3 r + 64. */ -#define LOGS_L(i) (_fixed_exp_logs + (i) * _fixed_exp_logs_n) +#define LOGS_L(i) (tab + (i) * nc) #if FLINT_BITS == 64 @@ -141,8 +149,8 @@ fixed_log1p_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r) wn = n + 1; - _fixed_exp_logs_ensure(n, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(n, r, &nc); TMP_START; P = TMP_ALLOC((wn + wn + n + n + n + 2 * n + 2) * sizeof(ulong)); @@ -305,8 +313,26 @@ fixed_log1p_bitwise_rs(nn_ptr res, nn_srcptr x, slong n, int r) flint_mpn_zero(nd, n); flint_mpn_copyi(nd + n, D, n - qr); /* quotient has ds - wn + 1 = n - qr limbs */ + if (ds - wn + 1 < FIXED_RED_DIV_NEWTON_CUTOFF) + { mpn_tdiv_qr(t, nd, 0, nd, ds, sh, wn); } + else + { + /* Same quotient by Karp-Markstein division: numerator + and divisor read as fractions (the numerator's n low + limbs are zero, so pass only its significant top + part), quotient computed with k + 1 fraction limbs, k + the quotient length. Truncating the two guard limbs + leaves t within 1 + eps ulps of the floored quotient + (eps = 4 B^(-1) / a' < 2^-62), which the series + argument budget absorbs like the tdiv floor. */ + slong k = ds - wn + 1; + nn_ptr q = TMP_ALLOC((k + 4) * sizeof(ulong)); + fixed_div_newton(q, nd + n, ds - n, sh, wn, k + 1); + flint_mpn_copyi(t, q + 2, k); + } + } } /* log X = acc + 2 atanh(t); on 32-bit limbs n = 1 clamps r to diff --git a/src/fixed/log1p_opt_1.c b/src/fixed/log1p_opt_1.c index 99a953cd67..45954a4163 100644 --- a/src/fixed/log1p_opt_1.c +++ b/src/fixed/log1p_opt_1.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -39,14 +38,14 @@ fixed_log1p_opt_1(nn_ptr res, nn_srcptr x) int k; - _fixed_exp_logs_ensure(1, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(1, r, &nc); for (i = 1; i <= r + 1; i++) { slong ii = FLINT_MIN(i, (slong) r); ulong v = MPN_RIGHT_SHIFT_LOW(UWORD(1), p0, (int) ii); - ulong lt = _fixed_exp_logs[ii * nc + (nc - 1)]; + ulong lt = tab[ii * nc + (nc - 1)]; ulong m = -(ulong) (v <= d0); d0 -= v & m; diff --git a/src/fixed/log1p_opt_2.c b/src/fixed/log1p_opt_2.c index b62860c047..8ef43390bf 100644 --- a/src/fixed/log1p_opt_2.c +++ b/src/fixed/log1p_opt_2.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -66,12 +65,12 @@ fixed_log1p_opt_2(nn_ptr res, nn_srcptr x) slong i, nc; - _fixed_exp_logs_ensure(2, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(2, r, &nc); #define STEP2(ii, v1, v0) \ do { \ - nn_srcptr Lq = _fixed_exp_logs + (ii) * nc + (nc - 2); \ + nn_srcptr Lq = tab + (ii) * nc + (nc - 2); \ ulong bw, e1, e0, m; \ sub_dddmmmsss(bw, e1, e0, UWORD(0), d1, d0, \ UWORD(0), v1, v0); \ diff --git a/src/fixed/log1p_opt_3.c b/src/fixed/log1p_opt_3.c index 11dd5266b5..37776347e3 100644 --- a/src/fixed/log1p_opt_3.c +++ b/src/fixed/log1p_opt_3.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -99,8 +98,8 @@ fixed_log1p_opt_3(nn_ptr res, nn_srcptr x) slong i, nc; - _fixed_exp_logs_ensure(3, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(3, r, &nc); d0 = x[0]; d1 = x[1]; @@ -112,7 +111,7 @@ fixed_log1p_opt_3(nn_ptr res, nn_srcptr x) a1 = 0; a2 = 0; -#define LP(ii) (_fixed_exp_logs + (ii) * nc + (nc - 3)) +#define LP(ii) (tab + (ii) * nc + (nc - 3)) /* window 0 */ { diff --git a/src/fixed/log1p_opt_4.c b/src/fixed/log1p_opt_4.c index 1eadeab7d7..8a82c612b4 100644 --- a/src/fixed/log1p_opt_4.c +++ b/src/fixed/log1p_opt_4.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -78,8 +77,8 @@ fixed_log1p_opt_4(nn_ptr res, nn_srcptr x) slong i, nc; - _fixed_exp_logs_ensure(4, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(4, r, &nc); d0 = x[0]; d1 = x[1]; @@ -94,7 +93,7 @@ fixed_log1p_opt_4(nn_ptr res, nn_srcptr x) a2 = 0; a3 = 0; -#define LP(ii) (_fixed_exp_logs + (ii) * nc + (nc - 4)) +#define LP(ii) (tab + (ii) * nc + (nc - 4)) /* window 0 */ { diff --git a/src/fixed/log1p_opt_5.c b/src/fixed/log1p_opt_5.c index f3cb028cac..465fc5fbf1 100644 --- a/src/fixed/log1p_opt_5.c +++ b/src/fixed/log1p_opt_5.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -87,8 +86,8 @@ fixed_log1p_opt_5(nn_ptr res, nn_srcptr x) slong i, nc; - _fixed_exp_logs_ensure(5, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(5, r, &nc); d0 = x[0]; d1 = x[1]; @@ -106,7 +105,7 @@ fixed_log1p_opt_5(nn_ptr res, nn_srcptr x) a3 = 0; a4 = 0; -#define LP(ii) (_fixed_exp_logs + (ii) * nc + (nc - 5)) +#define LP(ii) (tab + (ii) * nc + (nc - 5)) /* window 0 */ { diff --git a/src/fixed/log1p_opt_6.c b/src/fixed/log1p_opt_6.c index 066cc6a3ae..8d7cc0e8a1 100644 --- a/src/fixed/log1p_opt_6.c +++ b/src/fixed/log1p_opt_6.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -102,8 +101,8 @@ fixed_log1p_opt_6(nn_ptr res, nn_srcptr x) slong i, nc; - _fixed_exp_logs_ensure(6, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(6, r, &nc); d0 = x[0]; d1 = x[1]; @@ -124,7 +123,7 @@ fixed_log1p_opt_6(nn_ptr res, nn_srcptr x) a4 = 0; a5 = 0; -#define LP(ii) (_fixed_exp_logs + (ii) * nc + (nc - 6)) +#define LP(ii) (tab + (ii) * nc + (nc - 6)) /* window 0 */ { diff --git a/src/fixed/log1p_opt_7.c b/src/fixed/log1p_opt_7.c index 4e360b5db6..18ae94bc02 100644 --- a/src/fixed/log1p_opt_7.c +++ b/src/fixed/log1p_opt_7.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 @@ -137,8 +136,8 @@ fixed_log1p_opt_7(nn_ptr res, nn_srcptr x) slong i, nc; - _fixed_exp_logs_ensure(7, r); - nc = _fixed_exp_logs_n; + nn_srcptr tab; + tab = _fixed_exp_logs_tab(7, r, &nc); d0 = x[0]; d1 = x[1]; @@ -162,7 +161,7 @@ fixed_log1p_opt_7(nn_ptr res, nn_srcptr x) a5 = 0; a6 = 0; -#define LP(ii) (_fixed_exp_logs + (ii) * nc + (nc - 7)) +#define LP(ii) (tab + (ii) * nc + (nc - 7)) /* window 0 */ { diff --git a/src/fixed/sin_cos_bitwise_rs.c b/src/fixed/sin_cos_bitwise_rs.c index e5474e9753..3ae4bbe8b3 100644 --- a/src/fixed/sin_cos_bitwise_rs.c +++ b/src/fixed/sin_cos_bitwise_rs.c @@ -13,7 +13,6 @@ #include "mpn_extras.h" #include "arb.h" #include "fixed.h" -#include "impl.h" /* sin and cos on [0, 1) by the rotation analog of the bitwise exp algorithm, following the identity of [Joh2022]: with @@ -76,6 +75,16 @@ FLINT_TLS_PREFIX nn_ptr _fixed_atans = NULL; FLINT_TLS_PREFIX slong _fixed_atans_n = 0; FLINT_TLS_PREFIX slong _fixed_atans_r = 0; + +/* free the cached table; see _fixed_exp_logs_clear */ +void +_fixed_atans_clear(void) +{ + flint_free(_fixed_atans); + _fixed_atans = NULL; + _fixed_atans_n = 0; + _fixed_atans_r = 0; +} static FLINT_TLS_PREFIX int _fixed_atans_cleanup_registered = 0; static void @@ -88,28 +97,8 @@ _fixed_atans_cleanup(void) _fixed_atans_cleanup_registered = 0; } -#define FIXED_FRAC_BSPLIT_NO_LOG1 -#include "frac_bsplit.inc" - #define ATANS_A(i) (_fixed_atans + (i) * _fixed_atans_n) -/* floor(x 2^(FLINT_BITS nc)) of a nonnegative arb into nc limbs */ -static void -_fixed_tab_store(nn_ptr e, const arb_t x, slong nc) -{ - arf_t lb; - fmpz_t f; - - arf_init(lb); - fmpz_init(f); - arb_get_lbound_arf(lb, x, FLINT_BITS * nc + 30); - arf_mul_2exp_si(lb, lb, FLINT_BITS * nc); - arf_get_fmpz(f, lb, ARF_RND_FLOOR); - FLINT_ASSERT(fmpz_sgn(f) >= 0); - fmpz_get_ui_array(e, nc, f); - arf_clear(lb); - fmpz_clear(f); -} /* acc (nc limbs) += or -= t >> s; the bits shifted below position 0 fall below the guard limb and are dropped. scratch: nc limbs. */ @@ -159,10 +148,10 @@ _fixed_tab_addshift(nn_ptr acc, nn_srcptr t, slong nc, slong s, atan(1/4) = 2 theta_0 - theta_3 (complement of atan 4) atan(1/8) = theta_1 - theta_2 (8 + i = -i(1+2i)(3+2i)) - and beyond that arb_atan1_frac_bsplit (frac_bsplit.inc, in its - alternating mode) sums the series of 1/2^i directly. TODO: - reimplement the binary splitting natively with mpn arithmetic; - arb is fine for now, and this tier is not the bottleneck. + and beyond that fixed_atan_2mexp_ui_bs (tab_bsplit.c) splits the + series natively in mpn arithmetic, writing straight into the + entry. TODO: the remaining arb dependency of this tier is the + gauss-prime vector combination for i <= 3. Large i: fixed-point multi-summation of @@ -235,14 +224,14 @@ _fixed_atans_ensure(slong nv, slong rc) arb_sub(x, th + 1, th + 2, wp); break; default: - fmpz_one(p); - fmpz_one(q); - fmpz_mul_2exp(q, q, i); - arb_atan1_frac_bsplit(x, p, q, 0, wp); - break; + /* native mpn binary splitting, straight into the + entry (see tab_bsplit.c) */ + fixed_atan_2mexp_ui_bs(ATANS_A(i), (ulong) i, nc); + continue; } - _fixed_tab_store(ATANS_A(i), x, nc); + if (!_fixed_tab_store_floor(ATANS_A(i), x, nc, wp)) + _fixed_tab_entry_exact(ATANS_A(i), 1, (ulong) i, nc); } _arb_vec_clear(th, 4); @@ -297,10 +286,43 @@ _fixed_atans_ensure(slong nv, slong rc) flint_register_cleanup_function(_fixed_atans_cleanup); _fixed_atans_cleanup_registered = 1; } + /* Direct tail band: for 3i >= 64 nc every term of + atan(2^-i) = 2^-i - 2^(-3i)/3 + ... beyond the first falls + below the entry, and the omitted mass r satisfies + 0 < r 2^(64 nc) <= 1/3, so the exact floor is the pure bit + pattern 2^(64 nc - i) - 1. Writing these directly (they are + exactly the entries whose guard limb legitimately sits at + 0xfff..., which would otherwise trip the rescan below) keeps + the arb fallback for genuinely borderline entries only. */ + { + slong wp = FLINT_BITS * nc, band = (wp + 2) / 3; + + for (i = FLINT_MAX(band, 4); i <= rc; i++) + { + nn_ptr acc = ATANS_A(i); + slong b = wp - i; /* rc < 64 nv by the r convention, + so b >= 1 */ + flint_mpn_zero(acc, nc); + flint_mpn_store(acc, b / FLINT_BITS, ~UWORD(0)); + if (b % FLINT_BITS) + acc[b / FLINT_BITS] = + (UWORD(1) << (b % FLINT_BITS)) - 1; + } + + /* exactness rescan of the fast tiers below the band; see + the log table's twin loop for the reasoning */ + for (i = 4; i < FLINT_MIN(band, rc + 1); i++) + { + ulong g = _fixed_atans[i * nc]; + if (g + FIXED_TAB_GUARD_SLACK < g) + _fixed_tab_entry_exact(ATANS_A(i), 1, (ulong) i, nc); + } + } + } /* Read-only view of the table for code outside the library (the - thread-local storage itself is not exported; see impl.h): + thread-local storage itself is not exported; see fixed.h): the top n limbs of entry i, valid until the next ensure call on this thread. */ nn_srcptr diff --git a/src/fixed/sqrt_newton.c b/src/fixed/sqrt_newton.c new file mode 100644 index 0000000000..bd750c73be --- /dev/null +++ b/src/fixed/sqrt_newton.c @@ -0,0 +1,419 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "flint.h" +#include "mpn_extras.h" +#include "fixed.h" + +/* Approximate fixed-point reciprocal square roots and square roots, + ported from radix_rsqrt_1_approx / radix_rsqrt_approx / + radix_sqrt_approx onto mpn arithmetic; see div_newton.c for the + porting conventions and radix/sqrt.c for the error analyses. */ + +void +fixed_rsqrt_ui_newton_basecase(nn_ptr Y, ulong a, slong n) +{ + nn_ptr U; + slong nU; + TMP_INIT; + + TMP_START; + U = TMP_ALLOC((2 * n + 1) * sizeof(ulong)); + + /* floor(sqrt(a B^(2n))) = floor(B^n sqrt(a)), then divide by a */ + flint_mpn_zero(U, 2 * n); + U[2 * n] = a; + + mpn_sqrtrem(U, NULL, U, 2 * n + 1); + nU = n + 1; + while (nU > 0 && U[nU - 1] == 0) + nU--; + mpn_divrem_1(U, 0, U, nU, a); + while (nU > 0 && U[nU - 1] == 0) + nU--; + + FLINT_ASSERT(nU <= n); + flint_mpn_copyi(Y, U, nU); + flint_mpn_zero(Y + nU, n - nU); + + TMP_END; +} + +#ifndef FIXED_RSQRT_UI_NEWTON_CUTOFF +#define FIXED_RSQRT_UI_NEWTON_CUTOFF 24 +#endif + +void +fixed_rsqrt_ui_newton(nn_ptr Y, ulong a, slong n) +{ + if (n <= FIXED_RSQRT_UI_NEWTON_CUTOFF) + { + fixed_rsqrt_ui_newton_basecase(Y, a, n); + } + else + { + slong m, Un; + ulong cy; + nn_ptr T, U; + nn_ptr Yhi, Thi; + TMP_INIT; + + m = (n + 1) / 2 + 1; + + Yhi = Y + n - m; + fixed_rsqrt_ui_newton(Yhi, a, m); + flint_mpn_zero(Y, n - m); + + TMP_START; + T = TMP_ALLOC((m + 3 + m + 2) * sizeof(ulong)); + U = T + m + 3; + + /* T = low m+2 window of Yhi^2 (2m fraction limbs) */ + flint_mpn_mulmid(T, Yhi, m, Yhi, m, 0, m + 2); + cy = mpn_mul_1(U, T, m + 2, a); /* mod B^(m+2) */ + (void) cy; + cy = (U[m + 1] == 0); + /* a*y^2 - 1 or 1 - a*y^2 */ + if (!cy) + mpn_neg(U, U, m + 2); + + mpn_rshift(U, U, m + 2, 1); + Un = m + 2; + while (Un > 0 && U[Un - 1] == 0) + Un--; + if (Un + n - 2 * m <= 0) + goto cleanup; + + flint_mpn_mulmid(T, Yhi, m, U, Un, m - 2, Un + m); + Thi = T + 2; + + if (cy) + mpn_sub(Y, Y, n, Thi + 2 * m - n, Un + n - 2 * m); + else + mpn_add(Y, Y, n, Thi + 2 * m - n, Un + n - 2 * m); + +cleanup: + TMP_END; + } +} + +void +fixed_rsqrt_newton_basecase(nn_ptr Q, nn_srcptr A, slong An, slong n) +{ + nn_ptr W, U, bq, br; + nn_srcptr V; + slong Vn, g, Wn, Un, bsn, bqn; + slong guard_limbs = 2; + TMP_INIT; + + Vn = FLINT_MIN(An, n + guard_limbs); + g = FLINT_MAX(0, n + guard_limbs - Vn); + V = A + An - Vn; + + Wn = 2 * (Vn + g); + Un = n + Vn + g + 1; + + TMP_START; + W = TMP_ALLOC((Wn + Un + (Wn + 3) / 2 + Un) * sizeof(ulong)); + U = W + Wn; + bq = U + Un; + br = bq + (Wn + 3) / 2; + + /* W = V B^(Vn + 2g); its top limb may be zero when A < 1/B */ + flint_mpn_zero(W, Vn + 2 * g); + flint_mpn_copyi(W + Vn + 2 * g, V, Vn); + + while (Wn > 0 && W[Wn - 1] == 0) + Wn--; + FLINT_ASSERT(Wn >= 2 * (Vn + g) - 1); + + flint_mpn_zero(U, Un - 1); + U[Un - 1] = 1; + + /* bq = floor(B^(Un-1) / floor(sqrt(W))); the quotient has at most + n+2 limbs since sqrt(W) >= B^(Vn+g-1) */ + bsn = (Wn + 1) / 2; + mpn_sqrtrem(br, NULL, W, Wn); /* sqrt into br as scratch */ + bqn = Un - bsn + 1; + FLINT_ASSERT(bqn <= n + 2); + mpn_tdiv_qr(bq, W, 0, U, Un, br, bsn); /* remainder into W */ + + while (bqn > 0 && bq[bqn - 1] == 0) + bqn--; + flint_mpn_copyi(Q, bq, bqn); + flint_mpn_zero(Q + bqn, (n + 2) - bqn); + + TMP_END; +} + +/* Tuned on this machine (see dev/notes); must be at least 5 so that + m - 2 >= 2 and 2m - n - 2 >= 0 hold in the recursive cases of + fixed_rsqrt_newton and fixed_sqrt_newton. */ +#ifndef FIXED_RSQRT_NEWTON_CUTOFF +#define FIXED_RSQRT_NEWTON_CUTOFF 20 +#endif + +void +fixed_rsqrt_newton(nn_ptr Q, nn_srcptr A, slong An, slong n) +{ + if (n <= FIXED_RSQRT_NEWTON_CUTOFF) + { + fixed_rsqrt_newton_basecase(Q, A, An, n); + } + else + { + nn_ptr U, V, W, T, Vhigh, Uhigh; + nn_srcptr Ahigh; + slong m, Uhighn, Tn, Ahighn, Vhighn; + int Unegative; + TMP_INIT; + + /* T ~= 1/sqrt(A) with m fraction limbs, 2 integral limbs, in + the high part of Q */ + m = (n + 1) / 2 + 1; + fixed_rsqrt_newton(Q + n - m, A, An, m); + + TMP_START; + + T = Q + n - m; + Tn = m + 1 + (T[m + 1] != 0); + + flint_mpn_zero(Q, n - m); + + /* W = T^2 with 2m fraction limbs (full product; nearly all + its limbs are consumed below) */ + W = TMP_ALLOC(2 * Tn * sizeof(ulong)); + flint_mpn_mul(W, T, Tn, T, Tn); + + /* U ~= 1 - A'*T^2 with n fraction limbs, via the middle + product + control limb construction of fixed_inv_newton */ + slong control_limb = n / 2 + 2; + slong A_low_zeroes = 0; + + if (An >= n + 2) + { + Ahighn = n + 2; + Ahigh = A + An - Ahighn; + } + else + { + Ahighn = An; + Ahigh = A; + A_low_zeroes = n + 2 - An; + } + + /* full product: n+2+2m fraction limbs; keep 2 guard limbs + under the n retained ones */ + slong low_trunc = 2 * m; + + /* A_low_zeroes <= n + 1 <= 2m - 1, so the band always starts + at limb >= 1 */ + FLINT_ASSERT(A_low_zeroes <= low_trunc - 1); + + U = TMP_ALLOC((2 + (control_limb + 1)) * sizeof(ulong)); + flint_mpn_mulmid(U, Ahigh, Ahighn, W, 2 * Tn, + low_trunc - A_low_zeroes, + low_trunc - A_low_zeroes + 2 + (control_limb + 1)); + Uhigh = U + 2; + + Unegative = (Uhigh[control_limb] != 0); + if (Unegative) + mpn_neg(Uhigh, Uhigh, control_limb); + + Uhighn = control_limb; + while (Uhighn > 0 && Uhigh[Uhighn - 1] == 0) + Uhighn--; + + if (Uhighn != 0) + { + /* correction T * (1 - A T^2) / 2 */ + mpn_rshift(Uhigh, Uhigh, Uhighn, 1); + while (Uhighn > 0 && Uhigh[Uhighn - 1] == 0) + Uhighn--; + } + + if (Uhighn != 0) + { + V = TMP_ALLOC((Tn + Uhighn - (m - 2)) * sizeof(ulong)); + flint_mpn_mulmid(V, T, Tn, Uhigh, Uhighn, + m - 2, Tn + Uhighn); + Vhigh = V + 2; + Vhighn = Tn + Uhighn - (m - 2) - 2; + + if (Unegative) + mpn_add(Q, Q, n + 2, Vhigh, Vhighn); + else + mpn_sub(Q, Q, n + 2, Vhigh, Vhighn); + } + + TMP_END; + } +} + +void +fixed_sqrt_newton_rsqrtmul(nn_ptr Q, nn_srcptr A, slong An, slong n) +{ + nn_ptr T; + nn_srcptr A2; + slong A2n; + TMP_INIT; + TMP_START; + + A2n = FLINT_MIN(An, n + 2); + A2 = A + An - A2n; + + T = TMP_ALLOC((A2n + n + 2) * sizeof(ulong)); + + fixed_rsqrt_newton(Q, A, An, n); + /* should be a high multiplication */ + if (n + 2 >= A2n) + flint_mpn_mul(T, Q, n + 2, A2, A2n); + else + flint_mpn_mul(T, A2, A2n, Q, n + 2); + flint_mpn_copyi(Q, T + A2n, n + 2); + + TMP_END; +} + +#ifndef FIXED_SQRT_NEWTON_CUTOFF +#define FIXED_SQRT_NEWTON_CUTOFF 12 +#endif + +/* Karp-Markstein square root: S + T * (A - S^2) / 2 with S = T * A, + T ~= 1/sqrt(A) carried at half precision */ +void +fixed_sqrt_newton(nn_ptr Q, nn_srcptr A, slong An, slong n) +{ + nn_ptr U, V, T, TS, Vhigh, Uhigh, TShigh; + slong m, Un, Uhighn, Tn, Vhighn; + int Unegative; + TMP_INIT; + + if (n <= FIXED_SQRT_NEWTON_CUTOFF) + { + fixed_sqrt_newton_rsqrtmul(Q, A, An, n); + return; + } + + m = (n + 1) / 2 + 1; + + fixed_rsqrt_newton(Q + n - m, A, An, m); + + TMP_START; + T = Q + n - m; + Tn = m + 1 + (T[m + 1] != 0); + + flint_mpn_zero(Q, n - m); + + /* S = T * A2 ~= sqrt(A) with m fraction limbs; two extra limbs of + the truncated operand are needed, the truncation error being + multiplied by T <= B */ + slong A2n = FLINT_MIN(m + 2, An); + nn_srcptr A2 = A + An - A2n; + + if (A2n > 2) + { + TS = TMP_ALLOC((Tn + 2) * sizeof(ulong)); + flint_mpn_mulmid(TS, T, Tn, A2, A2n, A2n - 2, A2n + Tn); + TShigh = TS + 2; + } + else + { + TS = TMP_ALLOC((A2n + Tn) * sizeof(ulong)); + if (Tn >= A2n) + flint_mpn_mul(TS, T, Tn, A2, A2n); + else + flint_mpn_mul(TS, A2, A2n, T, Tn); + TShigh = TS + A2n; + } + + slong control_limb = n / 2 + 2; + + Un = control_limb + 1; + + /* U = band of S^2 at weights B^-n .. B^(control_limb - n), plus 2 + guard limbs; the full square has 2m fraction limbs, so the band + starts 2m - n limbs up */ + FLINT_ASSERT(2 * m - n - 2 >= 0); + U = TMP_ALLOC((2 + Un) * sizeof(ulong)); + flint_mpn_mulmid(U, TShigh, Tn, TShigh, Tn, + 2 * m - n - 2, 2 * m - n + Un); + Uhigh = U + 2; + + /* (A - S^2) with n fraction limbs, with sign */ + if (An > n - Un) + { + if (An >= n) + { + nn_srcptr Ahigh = A + An - n; + mpn_sub_n(Uhigh, Ahigh, Uhigh, Un); + } + else + { + slong nz = n - An; + ulong cy = mpn_neg(Uhigh, Uhigh, nz); + mpn_sub_n(Uhigh + nz, A, Uhigh + nz, Un - nz); + mpn_sub_1(Uhigh + nz, Uhigh + nz, Un - nz, cy); + } + + Unegative = 1; + if (Uhigh[Un - 1] >> (FLINT_BITS - 1)) + { + mpn_neg(Uhigh, Uhigh, Un); + Unegative = 0; + } + } + else + { + Unegative = 0; + if (Uhigh[Un - 1] >> (FLINT_BITS - 1)) + { + mpn_neg(Uhigh, Uhigh, Un); + Unegative = 1; + } + } + + Uhighn = Un; + while (Uhighn > 0 && Uhigh[Uhighn - 1] == 0) + Uhighn--; + + if (Uhighn != 0) + { + /* correction T * (A - S^2) / 2 */ + mpn_rshift(Uhigh, Uhigh, Uhighn, 1); + while (Uhighn > 0 && Uhigh[Uhighn - 1] == 0) + Uhighn--; + } + + if (Uhighn != 0) + { + V = TMP_ALLOC((Tn + Uhighn - (m - 2)) * sizeof(ulong)); + flint_mpn_mulmid(V, T, Tn, Uhigh, Uhighn, m - 2, Tn + Uhighn); + Vhigh = V + 2; + Vhighn = Tn + Uhighn - (m - 2) - 2; + + /* overwrite T with S */ + Q[n + 1] = 0; + flint_mpn_copyi(Q + n - m, TShigh, Tn); + + if (Unegative) + mpn_add(Q, Q, n + 2, Vhigh, Vhighn); + else + mpn_sub(Q, Q, n + 2, Vhigh, Vhighn); + } + else + { + Q[n + 1] = 0; + flint_mpn_copyi(Q + n - m, TShigh, Tn); + } + + TMP_END; +} diff --git a/src/fixed/tab_bsplit.c b/src/fixed/tab_bsplit.c new file mode 100644 index 0000000000..18788d1ef1 --- /dev/null +++ b/src/fixed/tab_bsplit.c @@ -0,0 +1,913 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "flint.h" +#include "mpn_extras.h" +#include "fixed.h" + +/* Binary splitting for the table values log(1 + 2^-i) and + atan(2^-i), in pure mpn arithmetic. + + Both series have dyadic ratios (2^-i resp. 2^-2i), except that the + logarithm switches to log(1 + 2^-i) = 2 atanh(1/q), q = 2^(i+1)+1, + below the direct-series cutoff; there the ratio q^-2 is not dyadic, + but multiplication by q is a shift and an addition. + + INTEGERS AS LIMB-RADIX FLOATS. The split products are carried as + normalized triples (d, size, exp): value = (d, size) 2^(64 exp), + top limb nonzero, trailing zero LIMBS stripped into exp (bit-level + trailing zeros stay in the mantissa). Whenever a mantissa would + exceed the cap (the target size plus a few limbs of margin), it is + truncated and the dropped limbs move into the exponent. + + ONE-SIDEDNESS. Every truncation on the numerator side rounds + DOWN and every one on the denominator side rounds UP, the final + division truncates, and the omitted series tail is positive (the + term count is even-aligned, so for the alternating series the + first omitted term has a plus sign). The result is therefore at + most the true value, short by a couple of ulps -- exactly the + contract of the reduction tables. + + SIGNS. The alternating series are evaluated over blocks aligned + to even indices: every block sum and every merge is then a sum of + positive quantities, and only the iterative basecase subtracts + (with a positive running value, since the terms decrease). + + BASECASE. Instead of recursing to length 1 or 2, blocks of + TAB_BS_BLK terms are accumulated iteratively with linear + operations: with V(b) = 0 and + + V(j) = 1/d_j (+/-) x V(j+1), + + maintaining V = N / (D 2^F) (dyadic x = 2^-e) gives + + N <- (D << (F + e - v_j)) (-/+) o_j N, D <- o_j D, + + where d_j = o_j 2^(v_j) with o_j odd -- a shift of the small + denominator product plus an mpn_(add/sub)mul_1. The atanh + basecase is the same with the shift of D replaced by + multiplication by q^2 = (D << (2i+2)) + (D << (i+2)) + D. */ + +#define TAB_BS_BLK 8 + +/* dyadic_finish divides by Newton iteration (fixed_div_newton) + instead of mpn_tdiv_qr when the denominator has at least this many + limbs AND at least a third of the numerator's: with a short + divisor GMP's division exploits the shape while the Newton + division always pays for a full-precision reciprocal (measured + in situ: 1.15-1.85x faster above the gate, as low as 0.5x below) */ +#ifndef TAB_BS_DIV_NEWTON_CUTOFF +#define TAB_BS_DIV_NEWTON_CUTOFF 40 +#endif + +/* extra buffer limbs beyond the truncation cap: a basecase block + spans up to TAB_BS_BLK terms of e bits each, plus small change */ +#define BUFSLACK(e) ((TAB_BS_BLK * (e)) / FLINT_BITS + 8) + +/* triple (d, n, e): value = (d, n) 2^(FLINT_BITS e); n = 0 means 0 */ +typedef struct +{ + nn_ptr d; + slong n; + slong e; +} +fx_t; + +/* strip leading zero limbs and move trailing zero limbs into e */ +static void +fx_norm(fx_t * x) +{ + slong t; + + while (x->n > 0 && x->d[x->n - 1] == 0) + x->n--; + + if (x->n == 0) + { + x->e = 0; + return; + } + + for (t = 0; x->d[t] == 0; t++) + ; + + if (t > 0) + { + flint_mpn_copyi(x->d, x->d + t, x->n - t); + x->n -= t; + x->e += t; + } +} + +/* truncate the mantissa to at most cap limbs; round_up adds one ulp + (of the truncated mantissa) when any dropped limb was nonzero */ +static void +fx_trunc(fx_t * x, slong cap, int round_up) +{ + slong drop = x->n - cap; + int inexact = 0; + slong t; + + if (drop <= 0) + return; + + if (round_up) + for (t = 0; t < drop && !inexact; t++) + inexact = (x->d[t] != 0); + + flint_mpn_copyi(x->d, x->d + drop, cap); + x->n = cap; + x->e += drop; + + if (inexact) + { + if (mpn_add_1(x->d, x->d, cap, 1)) + { + /* carry out: value 2^(64 cap) */ + x->d[0] = 1; + x->n = 1; + x->e += cap; + return; + } + } + + fx_norm(x); +} + +/* dst = a * b, truncated to cap; dst->d must hold a->n + b->n limbs + (callers reserve cap + 4 and keep operands at most cap + 1 after + their own truncation, with muls going through scratch when both + operands are large) */ +static void +fx_mul(fx_t * dst, const fx_t * a, const fx_t * b, slong cap, + int round_up) +{ + slong ps = a->n + b->n; + + if (a->n == 0 || b->n == 0) + { + dst->n = 0; + dst->e = 0; + return; + } + + /* When the full product exceeds the cap and the operands are + near-balanced -- the steady state at the top of the splitting + tree, where truncation pins both at cap -- compute only the + kept window with the windowed middle product instead of + forming and discarding the low limbs. flint_mpn_mulmid is a + LOWER approximation: it never exceeds the exact window, and + the deficit is bounded by min(an, bn, zlo) 2^64 window units + -- up to ~min ulps of the limb ABOVE the window bottom, a + bound that all-ones operands nearly attain -- NOT by min + 1 + ulps of the bottom limb as an earlier version of this comment + claimed. The round-down case still needs no correction (the + deficit points down and sits two limbs below the entry + guard), while the round-up case must add min + 2 at limb 1, + covering the full deficit; the overshoot, of the same + magnitude, lands equally far below the guard. Measured + 1.1-1.4x faster than + flint_mpn_mul at these shapes from 64 to 10240 limbs; the + skewed shapes stay on the full product, where the mulmid + dispatcher is not yet competitive below ~4096 limbs. */ + if (ps > cap && FLINT_MIN(a->n, b->n) >= cap - 4) + { + flint_mpn_mulmid(dst->d, a->d, a->n, b->d, b->n, + ps - cap, ps); + dst->n = cap; + dst->e = a->e + b->e + (ps - cap); + + if (round_up) + { + if (mpn_add_1(dst->d + 1, dst->d + 1, cap - 1, + (ulong) FLINT_MIN(a->n, b->n) + 2)) + { + dst->d[0] = 1; + dst->n = 1; + dst->e += cap; + return; + } + } + + fx_norm(dst); + return; + } + + if (a->n >= b->n) + flint_mpn_mul(dst->d, a->d, a->n, b->d, b->n); + else + flint_mpn_mul(dst->d, b->d, b->n, a->d, a->n); + + dst->n = a->n + b->n; + dst->e = a->e + b->e; + fx_norm(dst); + fx_trunc(dst, cap, round_up); +} + +/* x <<= s bits, in place; d must hold n + s/64 + 1 limbs */ +static void +fx_shift_up(fx_t * x, slong s) +{ + slong q = s / FLINT_BITS; + int b = (int) (s % FLINT_BITS); + + if (x->n == 0 || s == 0) + { + x->e += (x->n == 0) ? 0 : q; + if (x->n != 0 && b != 0) + { + ulong cy = mpn_lshift(x->d, x->d, x->n, b); + if (cy) + x->d[x->n++] = cy; + } + return; + } + + x->e += q; + if (b != 0) + { + ulong cy = mpn_lshift(x->d, x->d, x->n, b); + if (cy) + x->d[x->n++] = cy; + fx_norm(x); + } +} + +/* dst = a + b (both nonnegative), truncated to cap rounding down; + dst->d must hold cap + 3 limbs; dst may alias neither operand */ +static void +fx_add(fx_t * dst, const fx_t * a, const fx_t * b, slong cap) +{ + const fx_t * lo, * hi; + slong off, top, bot, span; + + if (a->n == 0) { flint_mpn_copyi(dst->d, b->d, b->n); + dst->n = b->n; dst->e = b->e; return; } + if (b->n == 0) { flint_mpn_copyi(dst->d, a->d, a->n); + dst->n = a->n; dst->e = a->e; return; } + + if (a->e <= b->e) { lo = a; hi = b; } + else { lo = b; hi = a; } + + top = FLINT_MAX(lo->e + lo->n, hi->e + hi->n); + bot = lo->e; + span = top - bot; + + if (span > cap + 2) + { + /* the low limbs cannot survive truncation: drop them + (rounding down) and re-anchor */ + bot = top - (cap + 2); + span = cap + 2; + } + + /* assemble hi at its offset, over [bot, top) */ + off = hi->e - bot; + if (off >= 0) + { + flint_mpn_zero(dst->d, off); + flint_mpn_copyi(dst->d + off, hi->d, hi->n); + } + else + { + /* hi partially below the window (possible only when lo was + re-anchored above hi->e; then hi == the smaller-exponent... + cannot happen: bot <= lo->e <= hi->e) */ + FLINT_ASSERT(0); + } + flint_mpn_zero(dst->d + off + hi->n, span - off - hi->n); + dst->n = span; + dst->e = bot; + + /* add lo where it overlaps the window */ + off = lo->e - bot; + if (off + lo->n > 0) + { + slong skip = (off < 0) ? -off : 0; /* skip == 0 here */ + ulong cy = mpn_add(dst->d + off + skip, dst->d + off + skip, + dst->n - off - skip, lo->d + skip, lo->n - skip); + if (cy) + { + dst->d[dst->n++] = cy; /* room: cap + 3 */ + } + } + + fx_norm(dst); + fx_trunc(dst, cap, 0); +} + +/* ---------------------------------------------------------------- */ +/* dyadic engine: S(a, b) = sum_{j=a}^{b-1} (-1)^(j-a) x^(j-a) / d_j + with x = 2^-e and d_j = odd(base + step j) 2^(v_j); a and b even. + Output: S = N / (D 2^F), N and D nonnegative, D odd, F in BITS. + N and D are written into caller-provided fx buffers of cap + 4 + limbs; scratch provides working space for this frame and every + frame below it. */ + +typedef struct +{ + slong e; /* bit shift per term */ + ulong dbase, dstep; /* d_j = dbase + dstep * j */ + slong cap; +} +dyadic_t; + +static void +dyadic_bs(fx_t * N, fx_t * D, slong * F, slong a, slong b, + const dyadic_t * par, nn_ptr scratch, slong scratch_alloc) +{ + slong cap = par->cap; + + if (b - a <= TAB_BS_BLK) + { + /* iterative basecase, top down */ + slong j; + ulong dj, oj; + int vj; + fx_t T; + + T.d = scratch; /* cap + BUFSLACK(e) limbs */ + + j = b - 1; + dj = par->dbase + par->dstep * (ulong) j; + oj = dj >> flint_ctz(dj); + vj = flint_ctz(dj); + + N->d[0] = 1; N->n = 1; N->e = 0; + D->d[0] = oj; D->n = 1; D->e = 0; + *F = vj; + + for (j = b - 2; j >= a; j--) + { + slong s; + + dj = par->dbase + par->dstep * (ulong) j; + vj = flint_ctz(dj); + oj = dj >> vj; + + /* T = D << (F + e - v_j) (in bits; exponent limb-split) */ + s = *F + par->e - vj; + FLINT_ASSERT(s >= 0); + flint_mpn_copyi(T.d, D->d, D->n); + T.n = D->n; T.e = D->e; + fx_shift_up(&T, s); + + /* N = T - o_j N (positive: alternating decreasing) */ + { + /* align: N scaled by o_j, then subtract from T at + exponent offsets. Materialize both at T's window: + N has smaller magnitude. */ + slong off = N->e - T.e; + ulong cy; + + /* multiply N by o_j in place (room: cap + 4) */ + cy = mpn_mul_1(N->d, N->d, N->n, oj); + if (cy) N->d[N->n++] = cy; + + if (off < 0) + { + /* extend T downward so the subtraction is exact + (the buffers carry enough slack: a block spans + at most BLK terms of e bits each) */ + slong ext = -off; + slong t; + for (t = T.n - 1; t >= 0; t--) + T.d[t + ext] = T.d[t]; + flint_mpn_zero(T.d, ext); + T.n += ext; + T.e -= ext; + off = 0; + } + + FLINT_ASSERT(off + N->n <= T.n); + cy = mpn_sub(T.d + off, T.d + off, T.n - off, + N->d, N->n); + FLINT_ASSERT(cy == 0); + } + + flint_mpn_copyi(N->d, T.d, T.n); + N->n = T.n; N->e = T.e; + fx_norm(N); + fx_trunc(N, cap, 0); + + /* D <- o_j D */ + { + ulong cy = mpn_mul_1(D->d, D->d, D->n, oj); + if (cy) D->d[D->n++] = cy; + fx_trunc(D, cap, 1); + } + + *F = *F + par->e; + } + return; + } + + { + slong m = a + (((b - a) / 2 + 1) & ~(slong) 1); + fx_t N2, D2, T1, T2; + slong F2, s; + nn_ptr sc = scratch; + + slong bufn = cap + BUFSLACK(par->e); + + N2.d = sc; sc += bufn; + D2.d = sc; sc += bufn; + T1.d = sc; sc += 2 * bufn; + T2.d = sc; sc += 2 * bufn; + + dyadic_bs(N, D, F, a, m, par, sc, + scratch_alloc - (sc - scratch)); + dyadic_bs(&N2, &D2, &F2, m, b, par, sc, + scratch_alloc - (sc - scratch)); + + /* S = N1/(D1 2^F1) + 2^(-e (m-a)) N2/(D2 2^F2) + = [N1 D2 2^(F2 + e(m-a) - F1) + N2 D1] + / (D1 D2 2^(F2 + e(m-a))) */ + fx_mul(&T1, N, &D2, cap, 0); + fx_mul(&T2, &N2, D, cap, 0); + + s = F2 + par->e * (m - a) - *F; + if (s >= 0) + fx_shift_up(&T1, s); + else + fx_shift_up(&T2, -s); /* rebalance: multiply + the OTHER side; denominator exponent adjusts below */ + + fx_add(N, &T1, &T2, cap); + fx_mul(&T1, D, &D2, cap, 1); + flint_mpn_copyi(D->d, T1.d, T1.n); + D->n = T1.n; D->e = T1.e; + + *F = F2 + par->e * (m - a) + ((s < 0) ? -s : 0); + } +} + +/* result = floor-ish of (N / (D 2^F)) * 2^(-outer) * 2^(64 n), + one-sided (at most the true value); res gets n limbs */ +static void +dyadic_finish(nn_ptr res, slong n, const fx_t * N, const fx_t * D, + slong F, slong outer) +{ + slong s, num_n, den_n, qn; + nn_ptr num, den, q, r; + TMP_INIT; + + /* target = N 2^(64 N->e) 2^(64 n - F - outer) / (D 2^(64 D->e)) */ + s = FLINT_BITS * (n + N->e - D->e) - F - outer; + + TMP_START; + + if (s >= 0) + { + num_n = N->n + s / FLINT_BITS + 2; + num = TMP_ALLOC(num_n * sizeof(ulong)); + flint_mpn_zero(num, num_n); + flint_mpn_copyi(num + s / FLINT_BITS, N->d, N->n); + if (s % FLINT_BITS) + mpn_lshift(num + s / FLINT_BITS, num + s / FLINT_BITS, + N->n + 1, (int) (s % FLINT_BITS)); + den_n = D->n; + den = (nn_ptr) D->d; + } + else + { + num_n = N->n; + num = (nn_ptr) N->d; + den_n = D->n + (-s) / FLINT_BITS + 2; + den = TMP_ALLOC(den_n * sizeof(ulong)); + flint_mpn_zero(den, den_n); + flint_mpn_copyi(den + (-s) / FLINT_BITS, D->d, D->n); + if ((-s) % FLINT_BITS) + mpn_lshift(den + (-s) / FLINT_BITS, + den + (-s) / FLINT_BITS, D->n + 1, + (int) ((-s) % FLINT_BITS)); + } + + while (num_n > 0 && num[num_n - 1] == 0) num_n--; + while (den_n > 0 && den[den_n - 1] == 0) den_n--; + + flint_mpn_zero(res, n); + + if (num_n >= den_n && num_n > 0) + { + if (den_n >= TAB_BS_DIV_NEWTON_CUTOFF && 3 * den_n >= num_n) + { + /* Newton division instead of mpn_tdiv_qr: view both + operands as fractions, so the wanted quotient is + (num/B^num_n) / (den/B^den_n) * B^k with + k = num_n - den_n, and compute it with k + 3 fraction + limbs; the numerator's long run of low zero limbs + (from the shift) is skipped, which leaves the value + unchanged. fixed_div_newton's error is two-sided, at + most 4 B^(3-k) / (den/B^den_n) <= 4 B^(2-k) -- far + below B^-1 of an output ulp -- so truncating the three + guard limbs yields floor(num/den) or one below it, and + the entry stays one-sided like the tdiv floor. */ + slong k = num_n - den_n; + slong nd = k + 3; + slong j, tz = 0; + + while (tz < num_n && num[tz] == 0) + tz++; + + q = TMP_ALLOC((nd + 2) * sizeof(ulong)); + fixed_div_newton(q, num + tz, num_n - tz, den, den_n, nd); + + for (j = 0; j < n; j++) + res[j] = (3 + j < nd + 2) ? q[3 + j] : 0; + } + else + { + qn = num_n - den_n + 1; + q = TMP_ALLOC((qn + 1) * sizeof(ulong)); + r = TMP_ALLOC(den_n * sizeof(ulong)); + mpn_tdiv_qr(q, r, 0, num, num_n, den, den_n); + while (qn > 0 && q[qn - 1] == 0) qn--; + FLINT_ASSERT(qn <= n); + flint_mpn_copyi(res, q, qn); + } + } + + TMP_END; +} + +void +fixed_atan_2mexp_ui_bs(nn_ptr res, ulong i, slong n) +{ + dyadic_t par; + slong Nterms, F, prec, depth, alloc; + fx_t N, D; + nn_ptr buf, scratch; + + FLINT_ASSERT(i >= 1); + FLINT_ASSERT(n >= 1); + + prec = FLINT_BITS * n + 8; + Nterms = prec / (2 * (slong) i) + 2; + Nterms = (Nterms + 1) & ~(slong) 1; /* even */ + + par.e = 2 * (slong) i; + par.dbase = 1; + par.dstep = 2; /* d_j = 2j + 1 */ + par.cap = n + 3; + + for (depth = 1; (TAB_BS_BLK << depth) < Nterms; depth++) + ; + /* every buffer holds a full basecase block span (BLK terms of + e bits) on top of the cap */ + alloc = (depth + 2) * (6 * (par.cap + BUFSLACK(par.e)) + 40) + + 4 * (par.cap + BUFSLACK(par.e)) + 40; + + buf = flint_malloc((2 * (par.cap + BUFSLACK(par.e)) + alloc) + * sizeof(ulong)); + N.d = buf; + D.d = buf + (par.cap + BUFSLACK(par.e)); + scratch = buf + 2 * (par.cap + BUFSLACK(par.e)); + + dyadic_bs(&N, &D, &F, 0, Nterms, &par, scratch, alloc); + dyadic_finish(res, n, &N, &D, F, (slong) i); + + flint_free(buf); +} + +/* ---------------------------------------------------------------- */ +/* atanh engine, computing + W(a, b) = q^-2 sum_{j=a}^{b-1} q^(-2(j-a)) / (2j+1), + q = 2^(i+1) + 1, all terms positive. Output W = N / DEN with the + denominator fully materialized as DEN = prod d_j q^(2(b-a)) -- + one q^2 per TERM, not per term less one -- plus the bare odd part + D = prod d_j. With that convention ranges compose without any + q-power at all: + + W(a,b) = W(a,m) + q^(-2(m-a)) W(m,b) + = (N1 DEN2 + N2 D1) / (DEN1 DEN2), + + four products per merge. The q^2 factors enter only at the + leaves, one linear multiplication + x q^2 = (x << (2i+2)) + (x << (i+2)) + x per term (a single + mpn_mul_1 whenever q^2 fits in a limb, i.e. 2i + 2 < FLINT_BITS); + no full-size q-power is ever formed. + + Rounding: N, D and DEN all truncate DOWN (DEN appears in merge + numerators, so it cannot round up); the final denominator is + instead INFLATED by a rigorous bound on its rounded-down history, + which restores one-sidedness at a relative cost of + 2^(-64 (cap-1) + ~13), far below an output ulp. */ + +/* x <- x q^2 = (x << (2i+2)) + (x << (i+2)) + x; scratch: room for + x->n + (2i+2)/64 + 4 limbs plus a shifted copy of x */ +static void +fx_mul_q2(fx_t * x, ulong i, slong cap, int round_up, nn_ptr scratch) +{ + fx_t T; + slong sh = 2 * (slong) i + 2, q, off; + int b; + ulong cy; + + if (x->n == 0) + return; + + /* q^2 fits in a limb whenever 2i + 2 < FLINT_BITS -- in + particular for every i below the direct-series cutoff on + 64-bit -- and the three shift-and-adds collapse to a single + mpn_mul_1 pass. (The product of odd values stays odd, so no + trailing limb can appear and fx_norm is unnecessary.) */ + if (2 * i + 2 < FLINT_BITS) + { + ulong qsq = (UWORD(1) << (2 * i + 2)) + + (UWORD(1) << (i + 2)) + 1; + cy = mpn_mul_1(x->d, x->d, x->n, qsq); + if (cy) + x->d[x->n++] = cy; + fx_trunc(x, cap, round_up); + return; + } + + T.d = scratch; + flint_mpn_copyi(T.d, x->d, x->n); + T.n = x->n; T.e = x->e; + fx_shift_up(&T, sh); + + /* T += x << (i+2): limb offset relative to T.e */ + sh = (slong) i + 2; + q = sh / FLINT_BITS + x->e; + b = (int) (sh % FLINT_BITS); + off = q - T.e; + if (off < 0) + { + slong ext = -off, t; + for (t = T.n - 1; t >= 0; t--) + T.d[t + ext] = T.d[t]; + flint_mpn_zero(T.d, ext); + T.n += ext; T.e -= ext; off = 0; + } + { + /* materialize x << b into the tail of scratch */ + nn_ptr xs = scratch + x->n + (2 * (slong) i + 2) / FLINT_BITS + 6; + slong xn = x->n; + flint_mpn_copyi(xs, x->d, xn); + if (b) + { + cy = mpn_lshift(xs, xs, xn, b); + if (cy) xs[xn++] = cy; + } + while (off + xn > T.n) + T.d[T.n++] = 0; + cy = mpn_add(T.d + off, T.d + off, T.n - off, xs, xn); + if (cy) T.d[T.n++] = cy; + } + + /* T += x */ + off = x->e - T.e; + if (off < 0) + { + slong ext = -off, t; + for (t = T.n - 1; t >= 0; t--) + T.d[t + ext] = T.d[t]; + flint_mpn_zero(T.d, ext); + T.n += ext; T.e -= ext; off = 0; + } + while (off + x->n > T.n) + T.d[T.n++] = 0; + cy = mpn_add(T.d + off, T.d + off, T.n - off, x->d, x->n); + if (cy) T.d[T.n++] = cy; + + flint_mpn_copyi(x->d, T.d, T.n); + x->n = T.n; x->e = T.e; + fx_norm(x); + fx_trunc(x, cap, round_up); +} + +static void +atanh_bs(fx_t * N, fx_t * DEN, fx_t * D, slong a, slong b, ulong i, + slong cap, nn_ptr scratch, slong scratch_alloc) +{ + if (b - a <= TAB_BS_BLK) + { + slong j; + slong bufn = cap + BUFSLACK(2 * (slong) i + 2); + nn_ptr sc = scratch; + + /* W(j) = 1/d_j + q^-2 W(j+1): + with V = DEN q^2: N <- d_j N + V, DEN <- d_j V. + DEN stays EXACT within the block (it feeds both sides). */ + j = b - 1; + N->d[0] = 1; N->n = 1; N->e = 0; + DEN->d[0] = 2 * (ulong) j + 1; DEN->n = 1; DEN->e = 0; + D->d[0] = 2 * (ulong) j + 1; D->n = 1; D->e = 0; + + for (j = b - 2; j >= a; j--) + { + ulong oj = 2 * (ulong) j + 1; + ulong cy; + slong off; + + /* DEN <- DEN q^2 (becomes V) */ + fx_mul_q2(DEN, i, bufn, 0, sc); + + /* N <- o_j N + V */ + cy = mpn_mul_1(N->d, N->d, N->n, oj); + if (cy) N->d[N->n++] = cy; + + off = DEN->e - N->e; + if (off < 0) + { + slong ext = -off, t; + for (t = N->n - 1; t >= 0; t--) + N->d[t + ext] = N->d[t]; + flint_mpn_zero(N->d, ext); + N->n += ext; N->e -= ext; + off = 0; + } + while (off + DEN->n > N->n) + N->d[N->n++] = 0; + cy = mpn_add(N->d + off, N->d + off, N->n - off, + DEN->d, DEN->n); + if (cy) N->d[N->n++] = cy; + fx_norm(N); + fx_trunc(N, cap, 0); + + /* DEN <- o_j V (exact within the block) */ + cy = mpn_mul_1(DEN->d, DEN->d, DEN->n, oj); + if (cy) DEN->d[DEN->n++] = cy; + + /* D <- o_j D */ + cy = mpn_mul_1(D->d, D->d, D->n, oj); + if (cy) D->d[D->n++] = cy; + fx_trunc(D, cap, 0); + } + + /* one more q^2 converts to the per-term convention + DEN = prod d_j q^(2(b-a)) */ + fx_mul_q2(DEN, i, bufn, 0, sc); + fx_trunc(DEN, cap, 0); + return; + } + + { + slong bufn = cap + BUFSLACK(2 * (slong) i + 2); + slong m = a + (((b - a) / 2 + 1) & ~(slong) 1); + fx_t N2, DEN2, D2, T1, T2; + nn_ptr sc = scratch; + + N2.d = sc; sc += bufn; + DEN2.d = sc; sc += bufn; + D2.d = sc; sc += bufn; + T1.d = sc; sc += 2 * bufn; + T2.d = sc; sc += 2 * bufn; + + atanh_bs(N, DEN, D, a, m, i, cap, sc, + scratch_alloc - (sc - scratch)); + atanh_bs(&N2, &DEN2, &D2, m, b, i, cap, sc, + scratch_alloc - (sc - scratch)); + + /* N = N1 DEN2 + N2 D1 */ + fx_mul(&T1, N, &DEN2, cap, 0); + fx_mul(&T2, &N2, D, cap, 0); + fx_add(N, &T1, &T2, cap); + + /* DEN = DEN1 DEN2 */ + fx_mul(&T1, DEN, &DEN2, cap, 0); + flint_mpn_copyi(DEN->d, T1.d, T1.n); + DEN->n = T1.n; DEN->e = T1.e; + + /* D = D1 D2 */ + fx_mul(&T1, D, &D2, cap, 0); + flint_mpn_copyi(D->d, T1.d, T1.n); + D->n = T1.n; D->e = T1.e; + } +} + +void +fixed_log1p_2mexp_ui_bs(nn_ptr res, ulong i, slong n) +{ + slong prec = FLINT_BITS * n + 8; + + FLINT_ASSERT(i >= 1); + FLINT_ASSERT(n >= 1); + + if (prec >= 6000 && i >= 30) + { + /* direct series: log(1 + 2^-i) + = 2^-i sum_{a>=0} (-1)^a 2^(-i a) / (a + 1) */ + dyadic_t par; + slong Nterms, F, depth, alloc, bufn; + fx_t N, D; + nn_ptr buf, scratch; + + Nterms = prec / (slong) i + 2; + Nterms = (Nterms + 1) & ~(slong) 1; + + par.e = (slong) i; + par.dbase = 1; + par.dstep = 1; /* d_j = j + 1 */ + par.cap = n + 3; + bufn = par.cap + BUFSLACK(par.e); + + for (depth = 1; (TAB_BS_BLK << depth) < Nterms; depth++) + ; + alloc = (depth + 2) * (6 * bufn + 40) + 4 * bufn + 40; + + buf = flint_malloc((2 * bufn + alloc) * sizeof(ulong)); + N.d = buf; + D.d = buf + bufn; + scratch = buf + 2 * bufn; + + dyadic_bs(&N, &D, &F, 0, Nterms, &par, scratch, alloc); + dyadic_finish(res, n, &N, &D, F, (slong) i); + + flint_free(buf); + } + else + { + /* log(1 + 2^-i) = 2 atanh(1/q), q = 2^(i+1) + 1 */ + slong Nterms, depth, alloc, cap, bufn; + fx_t N, DEN, D; + nn_ptr buf, scratch; + + Nterms = prec / (2 * ((slong) i + 1)) + 2; + Nterms = (Nterms + 1) & ~(slong) 1; + + cap = n + 3; + bufn = cap + BUFSLACK(2 * (slong) i + 2); + + for (depth = 1; (TAB_BS_BLK << depth) < Nterms; depth++) + ; + alloc = (depth + 2) * (7 * bufn + 40) + 8 * bufn + 80; + + buf = flint_malloc((3 * bufn + alloc) * sizeof(ulong)); + N.d = buf; + DEN.d = buf + bufn; + D.d = buf + 2 * bufn; + scratch = buf + 3 * bufn; + + atanh_bs(&N, &DEN, &D, 0, Nterms, i, cap, scratch, alloc); + + /* value = 2 atanh(1/q) = (2/q) sum q^(-2j)/d_j = 2 q W + = 2 (N q) / DEN: the final q lands on the NUMERATOR + (mpn_mul_1 when q fits a limb, else shift-and-add, + rounding down). DEN is then inflated past its + rounded-down history -- at most a few events per node and + per basecase step, each losing up to cap + 2 ulps now that + the merges may go through the lower-approximating middle + product -- so the denominator certainly reaches the true + value and the quotient stays one-sided. */ + if (i + 1 < FLINT_BITS - 1) + { + ulong cy; + cy = mpn_mul_1(N.d, N.d, N.n, (UWORD(1) << (i + 1)) + 1); + if (cy) + N.d[N.n++] = cy; + fx_trunc(&N, cap + 2, 0); + } + else + { + fx_t T; + slong off, t; + ulong cy; + + T.d = scratch; + flint_mpn_copyi(T.d, N.d, N.n); + T.n = N.n; T.e = N.e; + fx_shift_up(&T, (slong) i + 1); + off = N.e - T.e; + if (off < 0) + { + slong ext = -off; + for (t = T.n - 1; t >= 0; t--) + T.d[t + ext] = T.d[t]; + flint_mpn_zero(T.d, ext); + T.n += ext; T.e -= ext; off = 0; + } + while (off + N.n > T.n) + T.d[T.n++] = 0; + cy = mpn_add(T.d + off, T.d + off, T.n - off, N.d, N.n); + if (cy) T.d[T.n++] = cy; + flint_mpn_copyi(N.d, T.d, T.n); + N.n = T.n; N.e = T.e; + fx_norm(&N); + fx_trunc(&N, cap + 2, 0); + } + + { + ulong cy; + cy = mpn_add_1(DEN.d, DEN.d, DEN.n, + (4 * (ulong) Nterms + 64) * ((ulong) cap + 4)); + if (cy) + DEN.d[DEN.n++] = cy; + fx_trunc(&DEN, cap + 2, 1); + } + + dyadic_finish(res, n, &N, &DEN, 0, -1); + + flint_free(buf); + } +} diff --git a/src/fixed/tab_exact.c b/src/fixed/tab_exact.c new file mode 100644 index 0000000000..706d5d6012 --- /dev/null +++ b/src/fixed/tab_exact.c @@ -0,0 +1,84 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "flint.h" +#include "arb.h" +#include "fixed.h" + +/* Exact floors for the table entries. + + The tables guarantee that every entry equals the floor of the + infinite-precision value at the full entry precision -- hence at + every shorter truncation too, floors being nested. The fast + generation paths (native binary splitting and the fixed-point + multi-summation) produce one-sided values at most a few ulps + below the truth at the bottom (guard) limb, which coincide with + the exact floor UNLESS the true value sits within that deficit + above a limb boundary -- detectable, after the fact, as a guard + limb within the error bound of 0xfff...: the deficit then + borrowed through. The ensure functions rescan for such guard + limbs and recompute the (astronomically rare) offenders here, and + route the small-index arb-combination entries through + _fixed_tab_store_floor with this as the retry fallback. */ + +/* write e = floor(x 2^(64 nc)) if x's radius determines that floor + uniquely; returns 0 otherwise. prec must be at least the working + precision of x: the floor itself is taken at that precision, since + the value can sit arbitrarily close below an integer (the tail + entries atan(2^-i) ~ 2^-i - 2^(-3i)/3 do, once the correction + falls below the entry) and a fixed floor precision would never + separate them from it. */ +int +_fixed_tab_store_floor(nn_ptr e, const arb_t x, slong nc, slong prec) +{ + arb_t y; + fmpz_t f; + int ok; + + arb_init(y); + fmpz_init(f); + arb_mul_2exp_si(y, x, FLINT_BITS * nc); + arb_floor(y, y, FLINT_MAX(prec, FLINT_BITS * nc) + 64); + ok = arb_get_unique_fmpz(f, y); + if (ok) + { + FLINT_ASSERT(fmpz_sgn(f) >= 0 + && fmpz_bits(f) <= FLINT_BITS * nc); + fmpz_get_ui_array(e, nc, f); + } + arb_clear(y); + fmpz_clear(f); + return ok; +} + +/* e = floor(v 2^(64 nc)) exactly, v = log(1 + 2^-i) (which = 0) or + atan(2^-i) (which = 1), retrying at increasing precision until + the floor is unique */ +void +_fixed_tab_entry_exact(nn_ptr e, int which, ulong i, slong nc) +{ + arb_t x; + slong wp; + + arb_init(x); + for (wp = FLINT_BITS * nc + 64; ; wp *= 2) + { + arb_one(x); + arb_mul_2exp_si(x, x, -(slong) i); + if (which == 0) + arb_log1p(x, x, wp); + else + arb_atan(x, x, wp); + if (_fixed_tab_store_floor(e, x, nc, wp)) + break; + } + arb_clear(x); +} diff --git a/src/fixed/tab_static.c b/src/fixed/tab_static.c new file mode 100644 index 0000000000..4eda1a2a20 --- /dev/null +++ b/src/fixed/tab_static.c @@ -0,0 +1,578 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +/* Static log and atan tables covering the r <= 32 reductions at up + to FIXED_STATIC_TAB_N - 1 = 11 value limbs read per entry plus + the extra limb the working precision may read: 33 entries each at + stride FIXED_STATIC_TAB_N, and EVERY stored limb is a value limb + (an exact truncation, or one ulp below, of the true entry) -- + unlike the dynamic tables, whose bottom limb is a guard used to + generate them in place, no guard is stored here: consumers read + entries by (tab, stride) alone. Generated by + dev/notes/harnesses/gen_static_tab.c as the top limbs of a + dynamic table built one limb deeper; t-tab_static enforces that + identity. + + Small-n evaluations read these instead of the thread-local + dynamic tables, so short-lived threads skip the precomputation + entirely, entries stay adjacent regardless of the precision the + dynamic tables happen to hold, and the hardcoded small-n routines + avoid the TLS lookup. */ + +#include "flint.h" +#include "fixed.h" + +#if FLINT_BITS == 64 + +const ulong _fixed_exp_logs_static[(32 + 1) * 12] = { + /* 0 */ + UWORD(0x256fa0ec7657f74b), UWORD(0xb9ea9bc3b136603b), UWORD(0x1acbda11317c387e), UWORD(0x3e96ca16224ae8c5), + UWORD(0x27573b291169b825), UWORD(0xed2eae35c1382144), UWORD(0x559552fb4afa1b10), UWORD(0xe7b876206debac98), + UWORD(0x8a0d175b8baafa2b), UWORD(0x40f343267298b62d), UWORD(0xc9e3b39803f2f6af), UWORD(0xb17217f7d1cf79ab), + /* 1 */ + UWORD(0x0411cd963de66d19), UWORD(0x65209360074ba0ac), UWORD(0x2d139480cb153ccc), UWORD(0x4839cacbfc29e7e0), + UWORD(0xa7e09dc7febb0e3f), UWORD(0x752d66d15d6f9b5d), UWORD(0xfadca437b8a073c4), UWORD(0xc2df0cb19edaebb7), + UWORD(0xb38ad78ec59e3b60), UWORD(0x7d20ffb34547d7c2), UWORD(0xda35d9bd01488606), UWORD(0x67cc8fb2fe612fca), + /* 2 */ + UWORD(0xd5380a61e55f390c), UWORD(0x8fdb6776172bfb37), UWORD(0x89070cdb2eec1e73), UWORD(0x24ef8447c3518a2a), + UWORD(0x78383094d75732ea), UWORD(0xe9fc85bfc087279f), UWORD(0x3103360ed08de46d), UWORD(0xa192a8fd7cdd3baa), + UWORD(0x70f133f564bfc746), UWORD(0xc765ea7411adc1b1), UWORD(0x4bb03de5ff734495), UWORD(0x391fef8f35344358), + /* 3 */ + UWORD(0xe2b3fa400574e2e7), UWORD(0x10568afc5d60e11c), UWORD(0x3f5b4ef064ae411a), UWORD(0x51dccb81d608e6fb), + UWORD(0x286a0066ec0c6459), UWORD(0xfd2c1f6cf9a71577), UWORD(0xa023f5742646cc77), UWORD(0x9e05a342cfca2ad7), + UWORD(0xdd0897c1ff917c95), UWORD(0xb94ebc4017f6f957), UWORD(0xea87ffe1fe9e155d), UWORD(0x1e27076e2af2e5e9), + /* 4 */ + UWORD(0xd2692bbceee9d358), UWORD(0xcce585dd73d21d72), UWORD(0xe0f5584423c4de58), UWORD(0x68fdf95d5f36b22c), + UWORD(0x9bc3578977e6bc16), UWORD(0x99f0cc1de7a9459e), UWORD(0x908722d985badcca), UWORD(0x6172da6bde861006), + UWORD(0x71851f0a96f69849), UWORD(0xd3474d3375b52596), UWORD(0xbe64b8b775997898), UWORD(0x0f85186008b15330), + /* 5 */ + UWORD(0x48625f868fd1d5f6), UWORD(0xc6ee8bcfc1bb60f6), UWORD(0xf6cf97038d212c6b), UWORD(0xff7bd74c38eed29c), + UWORD(0x02127ef47cb29271), UWORD(0x9ce344310f6e3a39), UWORD(0x55d9a7bae0a5d2e3), UWORD(0x8a38a3ffec979e9d), + UWORD(0xdf6c758fe3673dcd), UWORD(0xaefae14cddf35ad1), UWORD(0x3e3f04f1ef229fae), UWORD(0x07e0a6c39e0cc013), + /* 6 */ + UWORD(0x0755b0eb65dc2153), UWORD(0x567d103c79a8ae2c), UWORD(0x750162515d2a0984), UWORD(0x15567658a1fc017a), + UWORD(0xe808e1531ef3a140), UWORD(0xba7510a93b4ede62), UWORD(0x7841aece8f9f71cc), UWORD(0x7a59b490758dcd0b), + UWORD(0xd140fe0577122f83), UWORD(0xeb03be903ddc5335), UWORD(0xf3db4e9a6f57aadb), UWORD(0x03f815161f807c79), + /* 7 */ + UWORD(0x6c15655aaf58fd5c), UWORD(0xc200063f6433c9e0), UWORD(0x31cd81611857063b), UWORD(0x52d919eca4a5b7cf), + UWORD(0xd4520ee290d465bf), UWORD(0xd99e1b469bb0f82e), UWORD(0xe4c4a44afacf68d7), UWORD(0xd122f7ff51bc9855), + UWORD(0x767027886b5788c3), UWORD(0xb3db2c3ef9a073a8), UWORD(0xc37690391dc282d2), UWORD(0x01fe02a6b106788f), + /* 8 */ + UWORD(0x07b4996dedf7e201), UWORD(0xd438bb960f632916), UWORD(0x82b09d3b430ecb69), UWORD(0x89db3dfeac79813d), + UWORD(0xb4baf715f0697e38), UWORD(0x699b47493a53ec28), UWORD(0xd137bfbccc95af15), UWORD(0x7ed64a064192b38f), + UWORD(0x91ba6e335a1a3322), UWORD(0x8ccd29dd6d725824), UWORD(0x50435ab4da6a5bb4), UWORD(0x00ff805515885e02), + /* 9 */ + UWORD(0xd932764447a4461c), UWORD(0x40cec96c5d80eb3a), UWORD(0x84a8727dc5592007), UWORD(0xafa9c34bd534ba9b), + UWORD(0x566babedd4bb4228), UWORD(0xd0d98ea24e690306), UWORD(0xdd3f9376492e4bba), UWORD(0x66e737d102ec7bf0), + UWORD(0xc73356cc12ace6e8), UWORD(0xcad8ec22e89a011e), UWORD(0xe29e3a153e3b1ab1), UWORD(0x007fe00aa6ac4399), + /* 10 */ + UWORD(0x1685f93038603bf8), UWORD(0x8a85d6da739145b4), UWORD(0x5634cb94d1f3a988), UWORD(0xa77480cffcd59644), + UWORD(0x2cbec25488455ab2), UWORD(0xac4e6045033072c1), UWORD(0xd3878b775f35b760), UWORD(0x453519e9f158acdd), + UWORD(0x6e34c564a6eb720c), UWORD(0x8e30d61762ef4eb0), UWORD(0x7809a0a32499268e), UWORD(0x003ff8015515621f), + /* 11 */ + UWORD(0xb8b7b869194f854b), UWORD(0x2ba4b19bcf194574), UWORD(0xc36a7cb9fb8750ca), UWORD(0x0b4a14bda5845964), + UWORD(0x2e395e6a71564207), UWORD(0xd4954a65648d0b5c), UWORD(0x2369cbdd10a5afda), UWORD(0x7dcd939ca5e80ec2), + UWORD(0x4e588a365c09d7ac), UWORD(0x545eb8e9e5e0fc9e), UWORD(0x06678ad8b318cb38), UWORD(0x001ffe002aa6ab11), + /* 12 */ + UWORD(0x127c75f7e3bbbd94), UWORD(0xb22f5b3ca5d65d6f), UWORD(0x3e861046dcd6c142), UWORD(0x56f744f1f9161bdf), + UWORD(0xbd5c2e9ee976cdd1), UWORD(0xf6577335116cb84f), UWORD(0xadd50321de7bf63a), UWORD(0x83c3767ce5b794e8), + UWORD(0xc9b2e9198222f25f), UWORD(0x49c8cd0b8002d083), UWORD(0x885de026e271ee05), UWORD(0x000fff8005551558), + /* 13 */ + UWORD(0x577a7e8ed86d76e4), UWORD(0xbb6e70bbd080c91a), UWORD(0x2ab3a2b3db99a46d), UWORD(0x02c4c3f9d70685de), + UWORD(0xeb8cb58a5d3950db), UWORD(0x2f9fd6b95bc2ca6a), UWORD(0xf576ffd9795b75ca), UWORD(0xf2added6c36eede8), + UWORD(0x46c08a95c4b26860), UWORD(0x6a90d794de4647be), UWORD(0xc443999e2bc2bf0f), UWORD(0x0007ffe000aaa6aa), + /* 14 */ + UWORD(0x7e72e5c8d11e6fff), UWORD(0x3e62f73781c9fcb2), UWORD(0x07d09372a5278fd7), UWORD(0xb8675c4e37d6789a), + UWORD(0x4b04b13f0c7e47ed), UWORD(0x7785988ee5e46287), UWORD(0x455be77fadc4b604), UWORD(0x9876d250db837e0c), + UWORD(0xf7b8170b3228bbda), UWORD(0x0dcf437a08a27c47), UWORD(0x56221f77809be9c1), UWORD(0x0003fff800155515), + /* 15 */ + UWORD(0x782acf581769a71e), UWORD(0x6e8201e8233d682d), UWORD(0x0c3cccfa69c6a85e), UWORD(0x5173d207859c9f6b), + UWORD(0x683e618849036e3a), UWORD(0x8c829de3945304c7), UWORD(0x8eb217a56ba06c4b), UWORD(0x04b508dbc15dcc65), + UWORD(0x582a09b18c5ae43b), UWORD(0xf8e86e2082c24db8), UWORD(0xaab111066678af6a), UWORD(0x0001fffe0002aaa6), + /* 16 */ + UWORD(0x63aa6a623fae28a4), UWORD(0xbda040577feee083), UWORD(0x52646935a7f6d8bc), UWORD(0xb57e55301adbec9f), + UWORD(0xa63fd0567033f832), UWORD(0xe5a8ece440ba526d), UWORD(0xe4d063bb32030263), UWORD(0xbd81443999513506), + UWORD(0xaf1e4b661876afc0), UWORD(0x07028c98aff31675), UWORD(0x155588885dde0270), UWORD(0x0000ffff80005555), + /* 17 */ + UWORD(0xb5e36fa8bc87eb95), UWORD(0x1945ad6b1ff410f6), UWORD(0x5f7e71dcd87786cf), UWORD(0x87bec673f57c21db), + UWORD(0x0b0424befa2ca18b), UWORD(0x1e3175a4aaf94067), UWORD(0x364468bc4d1f5496), UWORD(0x4e5f047cf23ee99e), + UWORD(0x489ed796d11b5438), UWORD(0xbe0be2cc64bfe601), UWORD(0xa6aaac44439999e2), UWORD(0x00007fffe0000aaa), + /* 18 */ + UWORD(0xe053a26541718675), UWORD(0x33c1f3b7915754a5), UWORD(0x54bdd4ef9993d295), UWORD(0x4c2aae9559bf52f4), + UWORD(0xee1cbdf0e06a8b6d), UWORD(0x0c89ce59e1ecfbe8), UWORD(0xf674e368b6fb251d), UWORD(0x7ef54ada6f1c529f), + UWORD(0x1f3f054f5e1212f1), UWORD(0x09c07c09c7b879ee), UWORD(0x55155562221f7778), UWORD(0x00003ffff8000155), + /* 19 */ + UWORD(0x68431396e1936c31), UWORD(0xd0d82501df066404), UWORD(0x12b5cb1088c0edbf), UWORD(0xc6912cc6491e5fc9), + UWORD(0xa0a8e3e2cfb0daed), UWORD(0x195e8e38bce66a17), UWORD(0xd0177f31628390ec), UWORD(0x0d7ad150864282b4), + UWORD(0x75d76918bec24f35), UWORD(0x678af88f8afc3dc3), UWORD(0xaaa6aaab11110666), UWORD(0x00001ffffe00002a), + /* 20 */ + UWORD(0x7116a99c89540c90), UWORD(0xd61ed95d047296b9), UWORD(0x532e049439c23922), UWORD(0x7c125acd03c72fac), + UWORD(0x6fbaa1f6a26f8cfb), UWORD(0x062ec61850699a0b), UWORD(0x8cbe29eac6518358), UWORD(0x1b03f5fe8c46ecb4), + UWORD(0x435a9dab4efaabcb), UWORD(0xdde0270250270437), UWORD(0x555515555888885d), UWORD(0x00000fffff800005), + /* 21 */ + UWORD(0xb9a10f7bc1ae4364), UWORD(0x65559e7673d3ee9d), UWORD(0x87f8a4671f94a884), UWORD(0x922f75d65d7ebb06), + UWORD(0xe1e6afd46eff2e3a), UWORD(0xb7cf71930f750114), UWORD(0xecd8ec1b65177c93), UWORD(0x48a036c42627ce21), + UWORD(0xa1ba153b53e3c6f5), UWORD(0x99999e2be29e2be3), UWORD(0xaaaaa6aaaac44443), UWORD(0x000007ffffe00000), + /* 22 */ + UWORD(0xdcdab39009ff7af9), UWORD(0xcc2f85ce1b16d9a0), UWORD(0x23089dfad5a3a2c5), UWORD(0x35f4e0c3432b92c8), + UWORD(0x4d3163cf517c4eb8), UWORD(0x3f0cfa25356fbbce), UWORD(0xbbed1a1762eb8c0c), UWORD(0x3dd45aba577cbc78), + UWORD(0x0a32632498c9926a), UWORD(0x1f7777809c09a09c), UWORD(0x1555551555562222), UWORD(0x000003fffff80000), + /* 23 */ + UWORD(0xf9955fdbc40ddf03), UWORD(0x4fc8680b645b7265), UWORD(0x7c952d403c973067), UWORD(0xd11b3d742ccde4ef), + UWORD(0xcfa25d62624cd1b6), UWORD(0x6ccda344c34b5a17), UWORD(0x5e426fd26de0fc9c), UWORD(0x855b3d18c01a1a8a), + UWORD(0xaf8b319318cb2cb3), UWORD(0x1106666678af8ad8), UWORD(0x02aaaaa6aaaab111), UWORD(0x000001fffffe0000), + /* 24 */ + UWORD(0x5e3a50e596f299d7), UWORD(0x9033632699862161), UWORD(0xf2e59f44b14ec72f), UWORD(0x21fef8d9ba77bb6c), + UWORD(0xc6add2db663aba2e), UWORD(0xc9b1f845d84d0913), UWORD(0x75ae8e725b93e083), UWORD(0x549c8e25e4501689), + UWORD(0xe270271ee1ee0548), UWORD(0x88885dddde027026), UWORD(0x0055555515555588), UWORD(0x000000ffffff8000), + /* 25 */ + UWORD(0xecfc3a47974d6a30), UWORD(0xfd80032eb9ce9b92), UWORD(0x8f54d09b59ffa862), UWORD(0x5d64aa10ce8cab24), + UWORD(0xf002f7fa6c4eef5d), UWORD(0x66c08a954c45058d), UWORD(0x9e930c1932a1b5e0), UWORD(0x90a90d796337c6f6), + UWORD(0x2bc2be2bf0f70f6a), UWORD(0x444443999999e2be), UWORD(0x000aaaaaa6aaaaac), UWORD(0x0000007fffffe000), + /* 26 */ + UWORD(0x8fc86bcad07c3366), UWORD(0x589cc2064b5b38e9), UWORD(0x653e786471f7b95f), UWORD(0x94a3b75e8fcc224b), + UWORD(0x038933945813100e), UWORD(0xaffc570b32287fa3), UWORD(0x933f0a51022643b5), UWORD(0xcf437437a08b7cff), + UWORD(0xc09be9c09c10dd0d), UWORD(0x6222221f77777809), UWORD(0x0001555555155555), UWORD(0x0000003ffffff800), + /* 27 */ + UWORD(0x358fe9e044d49b9d), UWORD(0x0fb448b0c2b2b869), UWORD(0x720ca4670687cffe), UWORD(0x192d2f63b4e6e14b), + UWORD(0xe6990582a7584202), UWORD(0x25e0ea3a0c5ae43a), UWORD(0xd95b3e6e3ccf6fdb), UWORD(0xe86e2082082c24f0), + UWORD(0x8af8af6af8af8e86), UWORD(0xab11111106666667), UWORD(0x00002aaaaaa6aaaa), UWORD(0x0000001ffffffe00), + /* 28 */ + UWORD(0xf0abdec571cec8d7), UWORD(0x6af1938977a89744), UWORD(0x90648c8b6e8841f0), UWORD(0x9020b6392948511b), + UWORD(0xbd81352b896becef), UWORD(0x204702419987afc0), UWORD(0x5c47237b4efbfc34), UWORD(0xc98c98aff2ff3167), + UWORD(0xe027027007027028), UWORD(0x55588888885ddddd), UWORD(0x0000055555551555), UWORD(0x0000000fffffff80), + /* 29 */ + UWORD(0x1e959035ec492369), UWORD(0x29f7c653c7bcf2dc), UWORD(0x0528f4309c103ed1), UWORD(0x5e66320e407b8c6a), + UWORD(0x4e5f047ceab7de1d), UWORD(0xcbe86125ac89565a), UWORD(0x148a02cd63391c55), UWORD(0x2cc64c64bfe5fe60), + UWORD(0x999e2be2be0be2be), UWORD(0xaaaac44444439999), UWORD(0x000000aaaaaaa6aa), UWORD(0x00000007ffffffe0), + /* 30 */ + UWORD(0xca4955eb0e7eb13a), UWORD(0x26f95f5e17ff5713), UWORD(0x459f0ab488bfcb14), UWORD(0x6fd0e9e34a72f11f), + UWORD(0x83394ada6f1c4edc), UWORD(0x3e3874fbabad80a9), UWORD(0xe1f3f0564b20d26a), UWORD(0xc09c7b87b879ee1e), + UWORD(0x7777809c09c07c09), UWORD(0x5555562222221f77), UWORD(0x0000001555555515), UWORD(0x00000003fffffff8), + /* 31 */ + UWORD(0x867a47d2e0ad19b9), UWORD(0x45114f5617e1dcd4), UWORD(0x733f7ee042befea3), UWORD(0xce35bba682471087), + UWORD(0xc45ad9d8864282b4), UWORD(0xdab096399224c0a2), UWORD(0xd75d76918c0178f0), UWORD(0x8af8afc3dc3dc375), + UWORD(0x66666678af8af88f), UWORD(0xaaaaaab111111106), UWORD(0x00000002aaaaaaa6), UWORD(0x00000001fffffffe), + /* 32 */ + UWORD(0x6f5f4c6b20efa59c), UWORD(0x2db4976a1412e4cf), UWORD(0xccb8bb1e3a9178d1), UWORD(0x8cbe28f9e47dda58), + UWORD(0xb8bad17e9d56ecb4), UWORD(0xf19c8e260d56a6de), UWORD(0x5a9da9dab4efac11), UWORD(0x5027027043743743), + UWORD(0x5dddddde02702702), UWORD(0x1555555588888888), UWORD(0x8000000055555555), UWORD(0x00000000ffffffff), +}; + +const ulong _fixed_atans_static[(32 + 1) * 12] = { + /* 0 */ + UWORD(0xee386bfb5a899fa5), UWORD(0x0bff5cb6f406b7ed), UWORD(0xf44c42e9a637ed6b), UWORD(0xe485b576625e7ec6), + UWORD(0x4fe1356d6d51c245), UWORD(0x302b0a6df25f1437), UWORD(0xef9519b3cd3a431b), UWORD(0x514a08798e3404dd), + UWORD(0x020bbea63b139b22), UWORD(0x29024e088a67cc74), UWORD(0xc4c6628b80dc1cd1), UWORD(0xc90fdaa22168c234), + /* 1 */ + UWORD(0x6fe9362df0aa3bc8), UWORD(0xa99b6e8483b4ba9c), UWORD(0xb75ff50104366583), UWORD(0xbc89a3a9a0d94db2), + UWORD(0xeb6c2f1b431146de), UWORD(0x6770f4077e9e0009), UWORD(0x43e4097c635230c1), UWORD(0xfd4e2c8bc495a8b6), + UWORD(0x71678b7374b12384), UWORD(0xb70a0ac3930e6f80), UWORD(0xb7f222f65e1d4681), UWORD(0x76b19c1586ed3da2), + /* 2 */ + UWORD(0xf848738fcf96f2cb), UWORD(0x023448d5eb389c81), UWORD(0xfda272315756f724), UWORD(0x67a2add0f8fca695), + UWORD(0x83d070eac0e660de), UWORD(0x8f0af089c1c793e6), UWORD(0xc16fa316e960c68f), UWORD(0x64ae49459a395d94), + UWORD(0xde8e9d9f251269d9), UWORD(0x6a9fea40e22ce0da), UWORD(0x5b71e7bd7de885f9), UWORD(0x3eb6ebf25901bac5), + /* 3 */ + UWORD(0x109e4ef809682ab4), UWORD(0xdc0194b4a13d17f0), UWORD(0xafab0859796a1d75), UWORD(0xbbee7451bfa82285), + UWORD(0xa54d9b1157f08ded), UWORD(0xabae79313e535f7f), UWORD(0xa7ff4baa3c469ae3), UWORD(0x1a9a0100403b384c), + UWORD(0x5d926aefbf6d82ed), UWORD(0xf1672afb2bb35b24), UWORD(0x5912f313e7d111de), UWORD(0x1fd5ba9aac2f6dc6), + /* 4 */ + UWORD(0x99fa18864f9fcfba), UWORD(0x272e54bdae1b4d65), UWORD(0x4e8952caeccad58c), UWORD(0xd5c04e8a5bddaffe), + UWORD(0x42521de94ef2a858), UWORD(0xb1404b3e625a4c0a), UWORD(0x493fe2926ac74803), UWORD(0xd0249009473e9b7d), + UWORD(0xc387a9f803c4b8ae), UWORD(0x1319c12cf59d4b2d), UWORD(0x6cb2792dc0e2e0d5), UWORD(0x0ffaaddb967ef4e3), + /* 5 */ + UWORD(0xe5bbc8bd737b4914), UWORD(0xaac8a1ba197ca05c), UWORD(0xa628e2f5fb5cb3d5), UWORD(0xa9d14ab589d759fc), + UWORD(0x1358c95c6f362901), UWORD(0x13c86927c276afc4), UWORD(0x0f282ff5f7a43e2d), UWORD(0x3b2a91f5897e5f9d), + UWORD(0xb2380beda26b0830), UWORD(0x9c036814a606dc40), UWORD(0x13bcebbb6ed46310), UWORD(0x07ff556eea5d892a), + /* 6 */ + UWORD(0x2ca728390e32643a), UWORD(0x14146677aeca2042), UWORD(0x729da52d76e749a9), UWORD(0x88b319a933f7b6b8), + UWORD(0x85a1b8fb4df9c218), UWORD(0xbcabe016797ad494), UWORD(0x68d09936286e9ba4), UWORD(0xa36a1de9858d1e57), + UWORD(0x81a57e00c9d5872e), UWORD(0x2083bd970437bbd1), UWORD(0xef9e31590057dd81), UWORD(0x03ffeaab776e5356), + /* 7 */ + UWORD(0xdb904cc4200683fb), UWORD(0xef3494c9058e5fb7), UWORD(0x913b2c3d6a4c8504), UWORD(0x0141ac6c76d996be), + UWORD(0x570f29f8d79ae2a7), UWORD(0xcc7790ed9717e1e5), UWORD(0x6f9cb7c5ac4a4e52), UWORD(0x2111d08128bc8f59), + UWORD(0x3507a2aecfdd9ca1), UWORD(0x8ed0ad402e345e00), UWORD(0xd00c46a3f77cc15e), UWORD(0x01fffd555bbba972), + /* 8 */ + UWORD(0x5b5bc45ccd97455b), UWORD(0x0c13211681eea5ec), UWORD(0x4346f7fe1e51bd83), UWORD(0x1191d908d429a60f), + UWORD(0x471a535c800f56c0), UWORD(0xd89ebbe467be3ae1), UWORD(0xd62450d66126f5a9), UWORD(0x6d688680f625cdfb), + UWORD(0x892248ad2682bef6), UWORD(0x167c18baeb9bc957), UWORD(0x4bb12afb6b6d4f7e), UWORD(0x00ffffaaaaddddb9), + /* 9 */ + UWORD(0x4d58fbf139aaef44), UWORD(0x4ee6d3d1c4aa0ac1), UWORD(0x230b23530fe0de91), UWORD(0x7c68126fc47d28b1), + UWORD(0xea07c22ba99a8d6d), UWORD(0xe21904e97ea6d959), UWORD(0x3c7b60c135b13873), UWORD(0x891139d53dd3db79), + UWORD(0xec67783a2d83ee26), UWORD(0xe8f2409fee23880b), UWORD(0xa5ca6adeab02251c), UWORD(0x007ffff55556eeee), + /* 10 */ + UWORD(0xfe89590f2e7623fa), UWORD(0x50993e96b553a459), UWORD(0x5f6fe436761acc15), UWORD(0xf9aad29d817c1b8a), + UWORD(0x9468017eb8be4cc8), UWORD(0xde2b462e67ffc46a), UWORD(0x3a2371fa19a9e4ad), UWORD(0xc07793647f3e659f), + UWORD(0x3ce835915d60ee69), UWORD(0x5f34dbc3aaca1311), UWORD(0x76e52e5a019fbcea), UWORD(0x003ffffeaaaab777), + /* 11 */ + UWORD(0xf31ad8f0c6a61598), UWORD(0x9d8e42c4bb4c9d1e), UWORD(0x34da6506ea6a14ad), UWORD(0x56b09cb3945677b1), + UWORD(0xb344b360c85c885b), UWORD(0x1b72a542634e362b), UWORD(0x8387ee2162185524), UWORD(0x3e58261b5a670d6f), + UWORD(0xa8558831856c8458), UWORD(0x8279c3594c521697), UWORD(0xbbba97297625624a), UWORD(0x001fffffd55555bb), + /* 12 */ + UWORD(0xe603e969da482f6b), UWORD(0x1148966f7de529fb), UWORD(0x3d528bb50ba00407), UWORD(0xfceeea1dac8a5970), + UWORD(0xe00d6ad50e15162e), UWORD(0x994513107be2eaaf), UWORD(0x0207abfad0df378a), UWORD(0xe06aedfe8f77e0a8), + UWORD(0xc118a554f087ef47), UWORD(0xbbe778a75117f928), UWORD(0xdddddb94b94d5bd5), UWORD(0x000ffffffaaaaaad), + /* 13 */ + UWORD(0x702bbbd246d16475), UWORD(0xbe52c1febe9e3a09), UWORD(0xbc0e64e116b2be24), UWORD(0x545a823d4432392b), + UWORD(0xf0d777952963c48b), UWORD(0xb5bbf4d14fd49766), UWORD(0xa5f57a6cab294c10), UWORD(0x4a6b20caea0510ff), + UWORD(0x3c4795ee346b7d5f), UWORD(0x340311a860615272), UWORD(0x6eeeeeea5ca5cb40), UWORD(0x0007ffffff555555), + /* 14 */ + UWORD(0xedadeb4c665ca779), UWORD(0x5248449fa801ca57), UWORD(0xb61c58889c1a4614), UWORD(0x0473484ed574f066), + UWORD(0x9ae0f50cb2ba23a0), UWORD(0x842cb34b0ed6caf1), UWORD(0xf8001a5926fc672a), UWORD(0x95ed04d0bbe868e8), + UWORD(0xc9df826e55713634), UWORD(0xc4ac4abedad667e3), UWORD(0xab7777776e52e52e), UWORD(0x0003ffffffeaaaaa), + /* 15 */ + UWORD(0xaefc57be2e3237ee), UWORD(0x384af2825f0d9864), UWORD(0xeb8f192b8dd22871), UWORD(0x62830cfbed831c31), + UWORD(0x65a64d589dc82378), UWORD(0x08a817b942082be3), UWORD(0xda0a040924cdaf09), UWORD(0x39e7207f4ccac1de), + UWORD(0xda4518ae99a76beb), UWORD(0x29ab7ab7aac0891f), UWORD(0x555bbbbbbba97297), UWORD(0x0001fffffffd5555), + /* 16 */ + UWORD(0x91370a04b0e1e99c), UWORD(0xb18c68a549b08589), UWORD(0x0558578ca4dc51b8), UWORD(0xb9dd1df310fb75e9), + UWORD(0x8c99a1d4647e218b), UWORD(0x002f07224008a6dd), UWORD(0x87d6bb52ef79230f), UWORD(0x6bc26e1dba283ecc), + UWORD(0x96923701fbc293d3), UWORD(0x94b968068067ef3a), UWORD(0xaaaaddddddddb94b), UWORD(0x0000ffffffffaaaa), + /* 17 */ + UWORD(0x9b0d31a7fa3f7186), UWORD(0x924f9b31499b6761), UWORD(0x2d0b2a6e1af38300), UWORD(0x7ab9e3ae19984883), + UWORD(0x0d4562099c7c9e3f), UWORD(0xc940f109912a2289), UWORD(0x9fa7134cd253c08a), UWORD(0x719ceb4318cfe1de), + UWORD(0xa09e66fe5336a7c7), UWORD(0xca5ca5d895895892), UWORD(0x555556eeeeeeeea5), UWORD(0x00007ffffffff555), + /* 18 */ + UWORD(0xa55c99e4f7356eaf), UWORD(0xbdb98bbaee9538e1), UWORD(0x78ac7438e6517d4e), UWORD(0x9081585f01ae6835), + UWORD(0x8e8fc780e084102e), UWORD(0xb790c83badc0d4cd), UWORD(0xc822951de07094b3), UWORD(0x7489ed19001ba866), + UWORD(0x6ef9de298585fe8e), UWORD(0xe52e52e5356f56f5), UWORD(0xaaaaaab777777776), UWORD(0x00003ffffffffeaa), + /* 19 */ + UWORD(0x7359cc078334c0dd), UWORD(0x9bbdab2d185f18a6), UWORD(0x02c08f49976f612f), UWORD(0x2a95b71640fca6f5), + UWORD(0x6bca9253077e7133), UWORD(0x449f09e849c8fcc3), UWORD(0x73003998d1a73947), UWORD(0x8f9a6e036efcc140), + UWORD(0x0d00c46a1815de9c), UWORD(0xba972972972d00d0), UWORD(0x55555555bbbbbbbb), UWORD(0x00001fffffffffd5), + /* 20 */ + UWORD(0xdfb6a59a1255664d), UWORD(0xc6688d3b94ae9fe6), UWORD(0x62d9803e86df4194), UWORD(0x49a4cbd43806f8b5), + UWORD(0xd02e94bd9933c3b9), UWORD(0x802d56a1407937d9), UWORD(0x166d0aaa46b4d8d1), UWORD(0x4277e1aca66c3e7e), + UWORD(0xb12b12afb6b599e5), UWORD(0xdddb94b94b94bb12), UWORD(0xaaaaaaaaaddddddd), UWORD(0x00000ffffffffffa), + /* 21 */ + UWORD(0x115cb0b44d24b390), UWORD(0x70acdbe91368f892), UWORD(0x09bbaa9258f93b70), UWORD(0xe2c030470da7ebe4), + UWORD(0x5aaeccdc83c35421), UWORD(0xe79c670c5b96d196), UWORD(0xc6f2409867162824), UWORD(0xf5f3c62ba88bfd1a), + UWORD(0xadeadeadeab02247), UWORD(0xeeeeea5ca5ca5ca6), UWORD(0x55555555556eeeee), UWORD(0x000007ffffffffff), + /* 22 */ + UWORD(0x46d0c642fafb433f), UWORD(0x1cb537f9f03a6088), UWORD(0x6c9ef9a7702c04d2), UWORD(0xd68e4212f2c074c3), + UWORD(0x989e4b9c9578eea3), UWORD(0xeb45b876e542e03f), UWORD(0x1f3097c3aac64f89), UWORD(0xa5a488d47ef0a939), + UWORD(0xe5a01a01a019fbce), UWORD(0x7777776e52e52e52), UWORD(0xeaaaaaaaaaab7777), UWORD(0x000003ffffffffff), + /* 23 */ + UWORD(0x963e88744fc20bc7), UWORD(0x52d844f31d808eab), UWORD(0x4d78f09a49d427cb), UWORD(0xac196b661f161c26), + UWORD(0xbb3dfc5bb5191d1d), UWORD(0xc691cb2a633f870e), UWORD(0xe4efc350c4521695), UWORD(0xa82799bf6d6da9f1), + UWORD(0x7297625625625624), UWORD(0xbbbbbbbba9729729), UWORD(0xfd55555555555bbb), UWORD(0x000001ffffffffff), + /* 24 */ + UWORD(0x4438e63760727ef3), UWORD(0xadfd9b9092fb93e7), UWORD(0xd5ca9c6cd6e55984), UWORD(0x21a171c6fd4459a7), + UWORD(0x207cdc0bb7a76048), UWORD(0xc117b4730f7da19b), UWORD(0x9d228c575106e928), UWORD(0x5bbe778a616044a3), + UWORD(0xb94b94d5bd5bd5bd), UWORD(0xddddddddddb94b94), UWORD(0xffaaaaaaaaaaaadd), UWORD(0x000000ffffffffff), + /* 25 */ + UWORD(0xcdb773b13995f60b), UWORD(0xfccde80f42135347), UWORD(0x820ec0c8762cf71e), UWORD(0xb42b500cf13ee6f8), + UWORD(0x4c00e661978f6bd9), UWORD(0x1c4795edbbfa8c89), UWORD(0x4be69b80fde15250), UWORD(0x0340311a8605779d), + UWORD(0x5ca5ca5cb4034034), UWORD(0xeeeeeeeeeeeea5ca), UWORD(0xfff5555555555556), UWORD(0x0000007fffffffff), + /* 26 */ + UWORD(0x2cc017fb31b680ea), UWORD(0x509372cf8e2025b6), UWORD(0x9f0df2b72044cbe5), UWORD(0x518ceebae6f258aa), + UWORD(0x1d77eaa91ad32d64), UWORD(0xc99b426e5570f9fc), UWORD(0x504f386b299b53e3), UWORD(0xac4ac4abedad6679), + UWORD(0x2e52e52e52ec4ac4), UWORD(0xb7777777777776e5), UWORD(0xfffeaaaaaaaaaaaa), UWORD(0x0000003fffffffff), + /* 27 */ + UWORD(0x52c927dfef00a1b0), UWORD(0x4e12ce5f7e398572), UWORD(0x19e39f3c2f90304f), UWORD(0x97b5664c60e202fb), + UWORD(0x1bcae4437c58a093), UWORD(0x3a45182619a76beb), UWORD(0xfd7cef14ea22ff47), UWORD(0xab7ab7ab7aac0891), + UWORD(0x9729729729729ab7), UWORD(0x55bbbbbbbbbbbbba), UWORD(0xffffd55555555555), UWORD(0x0000001fffffffff), + /* 28 */ + UWORD(0xdb5fb3a4517099ef), UWORD(0xcbc2b2c477c48068), UWORD(0x3a0d6457635cfdf8), UWORD(0xad16e1040e4edf4f), + UWORD(0x6bc25f0f9c0a2e27), UWORD(0x47cd3701fab193d3), UWORD(0xa96922350c0c2a4e), UWORD(0x8068068068067ef3), + UWORD(0xdb94b94b94b94b96), UWORD(0xaaaddddddddddddd), UWORD(0xfffffaaaaaaaaaaa), UWORD(0x0000000fffffffff), + /* 29 */ + UWORD(0x4536ec2ece82a9e9), UWORD(0xd8d18df4d30bb2cb), UWORD(0x58c20c615b211625), UWORD(0x92472ca98ce32b63), + UWORD(0x719ceb431148d2cf), UWORD(0x793bf0d65336a5a5), UWORD(0x2a09e66fdb5accfc), UWORD(0x5d89589589589589), + UWORD(0xeeea5ca5ca5ca5ca), UWORD(0x55556eeeeeeeeeee), UWORD(0xffffff5555555555), UWORD(0x00000007ffffffff), + /* 30 */ + UWORD(0x87c9c8d39fd1d3bc), UWORD(0x1abbac296e8147b8), UWORD(0x23d87da821fbd1f8), UWORD(0x409b0dcc3df68669), + UWORD(0x7045ed19001ba4a3), UWORD(0xfb48a315d445fe8e), UWORD(0x56ef9de298581123), UWORD(0x2e5356f56f56f56f), + UWORD(0x77776e52e52e52e5), UWORD(0xaaaaab7777777777), UWORD(0xffffffeaaaaaaaaa), UWORD(0x00000003ffffffff), + /* 31 */ + UWORD(0xcd0aaec675b33209), UWORD(0x9ac832ba819d4b0a), UWORD(0x30032dc2ffecd7d2), UWORD(0x711e75d50dea3225), + UWORD(0x8f9a657b6efcc140), UWORD(0x52d246e03f78549c), UWORD(0x00d00c46a1815de7), UWORD(0x972972d00d00d00d), + UWORD(0xbbbbbba972972972), UWORD(0x5555555bbbbbbbbb), UWORD(0xfffffffd55555555), UWORD(0x00000001ffffffff), + /* 32 */ + UWORD(0xb21cd907cf92e499), UWORD(0x2b41342efa1a3a3e), UWORD(0x75dfaaa45dd449c7), UWORD(0x166d09b964d2f6f0), + UWORD(0xf277e1ac955c3e7e), UWORD(0x5413ccdfca66d4f8), UWORD(0x2b12b12afb6b599e), UWORD(0x4b94b94bb12b12b1), + UWORD(0xddddddddb94b94b9), UWORD(0xaaaaaaaadddddddd), UWORD(0xffffffffaaaaaaaa), UWORD(0x00000000ffffffff), +}; + +#endif +#if FLINT_BITS == 32 + +const ulong _fixed_exp_logs_static[(32 + 1) * 12] = { + /* 0 */ + UWORD(0x4afa1b10), UWORD(0x559552fb), UWORD(0x6debac98), UWORD(0xe7b87620), + UWORD(0x8baafa2b), UWORD(0x8a0d175b), UWORD(0x7298b62d), UWORD(0x40f34326), + UWORD(0x03f2f6af), UWORD(0xc9e3b398), UWORD(0xd1cf79ab), UWORD(0xb17217f7), + /* 1 */ + UWORD(0xb8a073c4), UWORD(0xfadca437), UWORD(0x9edaebb7), UWORD(0xc2df0cb1), + UWORD(0xc59e3b60), UWORD(0xb38ad78e), UWORD(0x4547d7c2), UWORD(0x7d20ffb3), + UWORD(0x01488606), UWORD(0xda35d9bd), UWORD(0xfe612fca), UWORD(0x67cc8fb2), + /* 2 */ + UWORD(0xd08de46d), UWORD(0x3103360e), UWORD(0x7cdd3baa), UWORD(0xa192a8fd), + UWORD(0x64bfc746), UWORD(0x70f133f5), UWORD(0x11adc1b1), UWORD(0xc765ea74), + UWORD(0xff734495), UWORD(0x4bb03de5), UWORD(0x35344358), UWORD(0x391fef8f), + /* 3 */ + UWORD(0x2646cc77), UWORD(0xa023f574), UWORD(0xcfca2ad7), UWORD(0x9e05a342), + UWORD(0xff917c95), UWORD(0xdd0897c1), UWORD(0x17f6f957), UWORD(0xb94ebc40), + UWORD(0xfe9e155d), UWORD(0xea87ffe1), UWORD(0x2af2e5e9), UWORD(0x1e27076e), + /* 4 */ + UWORD(0x85badcca), UWORD(0x908722d9), UWORD(0xde861006), UWORD(0x6172da6b), + UWORD(0x96f69849), UWORD(0x71851f0a), UWORD(0x75b52596), UWORD(0xd3474d33), + UWORD(0x75997898), UWORD(0xbe64b8b7), UWORD(0x08b15330), UWORD(0x0f851860), + /* 5 */ + UWORD(0xe0a5d2e3), UWORD(0x55d9a7ba), UWORD(0xec979e9d), UWORD(0x8a38a3ff), + UWORD(0xe3673dcd), UWORD(0xdf6c758f), UWORD(0xddf35ad1), UWORD(0xaefae14c), + UWORD(0xef229fae), UWORD(0x3e3f04f1), UWORD(0x9e0cc013), UWORD(0x07e0a6c3), + /* 6 */ + UWORD(0x8f9f71cc), UWORD(0x7841aece), UWORD(0x758dcd0b), UWORD(0x7a59b490), + UWORD(0x77122f83), UWORD(0xd140fe05), UWORD(0x3ddc5335), UWORD(0xeb03be90), + UWORD(0x6f57aadb), UWORD(0xf3db4e9a), UWORD(0x1f807c79), UWORD(0x03f81516), + /* 7 */ + UWORD(0xfacf68d7), UWORD(0xe4c4a44a), UWORD(0x51bc9855), UWORD(0xd122f7ff), + UWORD(0x6b5788c3), UWORD(0x76702788), UWORD(0xf9a073a8), UWORD(0xb3db2c3e), + UWORD(0x1dc282d2), UWORD(0xc3769039), UWORD(0xb106788f), UWORD(0x01fe02a6), + /* 8 */ + UWORD(0xcc95af15), UWORD(0xd137bfbc), UWORD(0x4192b38f), UWORD(0x7ed64a06), + UWORD(0x5a1a3322), UWORD(0x91ba6e33), UWORD(0x6d725824), UWORD(0x8ccd29dd), + UWORD(0xda6a5bb4), UWORD(0x50435ab4), UWORD(0x15885e02), UWORD(0x00ff8055), + /* 9 */ + UWORD(0x492e4bba), UWORD(0xdd3f9376), UWORD(0x02ec7bf0), UWORD(0x66e737d1), + UWORD(0x12ace6e8), UWORD(0xc73356cc), UWORD(0xe89a011e), UWORD(0xcad8ec22), + UWORD(0x3e3b1ab1), UWORD(0xe29e3a15), UWORD(0xa6ac4399), UWORD(0x007fe00a), + /* 10 */ + UWORD(0x5f35b760), UWORD(0xd3878b77), UWORD(0xf158acdd), UWORD(0x453519e9), + UWORD(0xa6eb720c), UWORD(0x6e34c564), UWORD(0x62ef4eb0), UWORD(0x8e30d617), + UWORD(0x2499268e), UWORD(0x7809a0a3), UWORD(0x5515621f), UWORD(0x003ff801), + /* 11 */ + UWORD(0x10a5afda), UWORD(0x2369cbdd), UWORD(0xa5e80ec2), UWORD(0x7dcd939c), + UWORD(0x5c09d7ac), UWORD(0x4e588a36), UWORD(0xe5e0fc9e), UWORD(0x545eb8e9), + UWORD(0xb318cb38), UWORD(0x06678ad8), UWORD(0x2aa6ab11), UWORD(0x001ffe00), + /* 12 */ + UWORD(0xde7bf63a), UWORD(0xadd50321), UWORD(0xe5b794e8), UWORD(0x83c3767c), + UWORD(0x8222f25f), UWORD(0xc9b2e919), UWORD(0x8002d083), UWORD(0x49c8cd0b), + UWORD(0xe271ee05), UWORD(0x885de026), UWORD(0x05551558), UWORD(0x000fff80), + /* 13 */ + UWORD(0x795b75ca), UWORD(0xf576ffd9), UWORD(0xc36eede8), UWORD(0xf2added6), + UWORD(0xc4b26860), UWORD(0x46c08a95), UWORD(0xde4647be), UWORD(0x6a90d794), + UWORD(0x2bc2bf0f), UWORD(0xc443999e), UWORD(0x00aaa6aa), UWORD(0x0007ffe0), + /* 14 */ + UWORD(0xadc4b604), UWORD(0x455be77f), UWORD(0xdb837e0c), UWORD(0x9876d250), + UWORD(0x3228bbda), UWORD(0xf7b8170b), UWORD(0x08a27c47), UWORD(0x0dcf437a), + UWORD(0x809be9c1), UWORD(0x56221f77), UWORD(0x00155515), UWORD(0x0003fff8), + /* 15 */ + UWORD(0x6ba06c4b), UWORD(0x8eb217a5), UWORD(0xc15dcc65), UWORD(0x04b508db), + UWORD(0x8c5ae43b), UWORD(0x582a09b1), UWORD(0x82c24db8), UWORD(0xf8e86e20), + UWORD(0x6678af6a), UWORD(0xaab11106), UWORD(0x0002aaa6), UWORD(0x0001fffe), + /* 16 */ + UWORD(0x32030263), UWORD(0xe4d063bb), UWORD(0x99513506), UWORD(0xbd814439), + UWORD(0x1876afc0), UWORD(0xaf1e4b66), UWORD(0xaff31675), UWORD(0x07028c98), + UWORD(0x5dde0270), UWORD(0x15558888), UWORD(0x80005555), UWORD(0x0000ffff), + /* 17 */ + UWORD(0x4d1f5496), UWORD(0x364468bc), UWORD(0xf23ee99e), UWORD(0x4e5f047c), + UWORD(0xd11b5438), UWORD(0x489ed796), UWORD(0x64bfe601), UWORD(0xbe0be2cc), + UWORD(0x439999e2), UWORD(0xa6aaac44), UWORD(0xe0000aaa), UWORD(0x00007fff), + /* 18 */ + UWORD(0xb6fb251d), UWORD(0xf674e368), UWORD(0x6f1c529f), UWORD(0x7ef54ada), + UWORD(0x5e1212f1), UWORD(0x1f3f054f), UWORD(0xc7b879ee), UWORD(0x09c07c09), + UWORD(0x221f7778), UWORD(0x55155562), UWORD(0xf8000155), UWORD(0x00003fff), + /* 19 */ + UWORD(0x628390ec), UWORD(0xd0177f31), UWORD(0x864282b4), UWORD(0x0d7ad150), + UWORD(0xbec24f35), UWORD(0x75d76918), UWORD(0x8afc3dc3), UWORD(0x678af88f), + UWORD(0x11110666), UWORD(0xaaa6aaab), UWORD(0xfe00002a), UWORD(0x00001fff), + /* 20 */ + UWORD(0xc6518358), UWORD(0x8cbe29ea), UWORD(0x8c46ecb4), UWORD(0x1b03f5fe), + UWORD(0x4efaabcb), UWORD(0x435a9dab), UWORD(0x50270437), UWORD(0xdde02702), + UWORD(0x5888885d), UWORD(0x55551555), UWORD(0xff800005), UWORD(0x00000fff), + /* 21 */ + UWORD(0x65177c93), UWORD(0xecd8ec1b), UWORD(0x2627ce21), UWORD(0x48a036c4), + UWORD(0x53e3c6f5), UWORD(0xa1ba153b), UWORD(0xe29e2be3), UWORD(0x99999e2b), + UWORD(0xaac44443), UWORD(0xaaaaa6aa), UWORD(0xffe00000), UWORD(0x000007ff), + /* 22 */ + UWORD(0x62eb8c0c), UWORD(0xbbed1a17), UWORD(0x577cbc78), UWORD(0x3dd45aba), + UWORD(0x98c9926a), UWORD(0x0a326324), UWORD(0x9c09a09c), UWORD(0x1f777780), + UWORD(0x55562222), UWORD(0x15555515), UWORD(0xfff80000), UWORD(0x000003ff), + /* 23 */ + UWORD(0x6de0fc9c), UWORD(0x5e426fd2), UWORD(0xc01a1a8a), UWORD(0x855b3d18), + UWORD(0x18cb2cb3), UWORD(0xaf8b3193), UWORD(0x78af8ad8), UWORD(0x11066666), + UWORD(0xaaaab111), UWORD(0x02aaaaa6), UWORD(0xfffe0000), UWORD(0x000001ff), + /* 24 */ + UWORD(0x5b93e083), UWORD(0x75ae8e72), UWORD(0xe4501689), UWORD(0x549c8e25), + UWORD(0xe1ee0548), UWORD(0xe270271e), UWORD(0xde027026), UWORD(0x88885ddd), + UWORD(0x15555588), UWORD(0x00555555), UWORD(0xffff8000), UWORD(0x000000ff), + /* 25 */ + UWORD(0x32a1b5e0), UWORD(0x9e930c19), UWORD(0x6337c6f6), UWORD(0x90a90d79), + UWORD(0xf0f70f6a), UWORD(0x2bc2be2b), UWORD(0x9999e2be), UWORD(0x44444399), + UWORD(0xa6aaaaac), UWORD(0x000aaaaa), UWORD(0xffffe000), UWORD(0x0000007f), + /* 26 */ + UWORD(0x022643b5), UWORD(0x933f0a51), UWORD(0xa08b7cff), UWORD(0xcf437437), + UWORD(0x9c10dd0d), UWORD(0xc09be9c0), UWORD(0x77777809), UWORD(0x6222221f), + UWORD(0x55155555), UWORD(0x00015555), UWORD(0xfffff800), UWORD(0x0000003f), + /* 27 */ + UWORD(0x3ccf6fdb), UWORD(0xd95b3e6e), UWORD(0x082c24f0), UWORD(0xe86e2082), + UWORD(0xf8af8e86), UWORD(0x8af8af6a), UWORD(0x06666667), UWORD(0xab111111), + UWORD(0xaaa6aaaa), UWORD(0x00002aaa), UWORD(0xfffffe00), UWORD(0x0000001f), + /* 28 */ + UWORD(0x4efbfc34), UWORD(0x5c47237b), UWORD(0xf2ff3167), UWORD(0xc98c98af), + UWORD(0x07027028), UWORD(0xe0270270), UWORD(0x885ddddd), UWORD(0x55588888), + UWORD(0x55551555), UWORD(0x00000555), UWORD(0xffffff80), UWORD(0x0000000f), + /* 29 */ + UWORD(0x63391c55), UWORD(0x148a02cd), UWORD(0xbfe5fe60), UWORD(0x2cc64c64), + UWORD(0xbe0be2be), UWORD(0x999e2be2), UWORD(0x44439999), UWORD(0xaaaac444), + UWORD(0xaaaaa6aa), UWORD(0x000000aa), UWORD(0xffffffe0), UWORD(0x00000007), + /* 30 */ + UWORD(0x4b20d26a), UWORD(0xe1f3f056), UWORD(0xb879ee1e), UWORD(0xc09c7b87), + UWORD(0x09c07c09), UWORD(0x7777809c), UWORD(0x22221f77), UWORD(0x55555622), + UWORD(0x55555515), UWORD(0x00000015), UWORD(0xfffffff8), UWORD(0x00000003), + /* 31 */ + UWORD(0x8c0178f0), UWORD(0xd75d7691), UWORD(0xdc3dc375), UWORD(0x8af8afc3), + UWORD(0xaf8af88f), UWORD(0x66666678), UWORD(0x11111106), UWORD(0xaaaaaab1), + UWORD(0xaaaaaaa6), UWORD(0x00000002), UWORD(0xfffffffe), UWORD(0x00000001), + /* 32 */ + UWORD(0xb4efac11), UWORD(0x5a9da9da), UWORD(0x43743743), UWORD(0x50270270), + UWORD(0x02702702), UWORD(0x5dddddde), UWORD(0x88888888), UWORD(0x15555555), + UWORD(0x55555555), UWORD(0x80000000), UWORD(0xffffffff), UWORD(0x00000000), +}; + +const ulong _fixed_atans_static[(32 + 1) * 12] = { + /* 0 */ + UWORD(0xcd3a431b), UWORD(0xef9519b3), UWORD(0x8e3404dd), UWORD(0x514a0879), + UWORD(0x3b139b22), UWORD(0x020bbea6), UWORD(0x8a67cc74), UWORD(0x29024e08), + UWORD(0x80dc1cd1), UWORD(0xc4c6628b), UWORD(0x2168c234), UWORD(0xc90fdaa2), + /* 1 */ + UWORD(0x635230c1), UWORD(0x43e4097c), UWORD(0xc495a8b6), UWORD(0xfd4e2c8b), + UWORD(0x74b12384), UWORD(0x71678b73), UWORD(0x930e6f80), UWORD(0xb70a0ac3), + UWORD(0x5e1d4681), UWORD(0xb7f222f6), UWORD(0x86ed3da2), UWORD(0x76b19c15), + /* 2 */ + UWORD(0xe960c68f), UWORD(0xc16fa316), UWORD(0x9a395d94), UWORD(0x64ae4945), + UWORD(0x251269d9), UWORD(0xde8e9d9f), UWORD(0xe22ce0da), UWORD(0x6a9fea40), + UWORD(0x7de885f9), UWORD(0x5b71e7bd), UWORD(0x5901bac5), UWORD(0x3eb6ebf2), + /* 3 */ + UWORD(0x3c469ae3), UWORD(0xa7ff4baa), UWORD(0x403b384c), UWORD(0x1a9a0100), + UWORD(0xbf6d82ed), UWORD(0x5d926aef), UWORD(0x2bb35b24), UWORD(0xf1672afb), + UWORD(0xe7d111de), UWORD(0x5912f313), UWORD(0xac2f6dc6), UWORD(0x1fd5ba9a), + /* 4 */ + UWORD(0x6ac74803), UWORD(0x493fe292), UWORD(0x473e9b7d), UWORD(0xd0249009), + UWORD(0x03c4b8ae), UWORD(0xc387a9f8), UWORD(0xf59d4b2d), UWORD(0x1319c12c), + UWORD(0xc0e2e0d5), UWORD(0x6cb2792d), UWORD(0x967ef4e3), UWORD(0x0ffaaddb), + /* 5 */ + UWORD(0xf7a43e2d), UWORD(0x0f282ff5), UWORD(0x897e5f9d), UWORD(0x3b2a91f5), + UWORD(0xa26b0830), UWORD(0xb2380bed), UWORD(0xa606dc40), UWORD(0x9c036814), + UWORD(0x6ed46310), UWORD(0x13bcebbb), UWORD(0xea5d892a), UWORD(0x07ff556e), + /* 6 */ + UWORD(0x286e9ba4), UWORD(0x68d09936), UWORD(0x858d1e57), UWORD(0xa36a1de9), + UWORD(0xc9d5872e), UWORD(0x81a57e00), UWORD(0x0437bbd1), UWORD(0x2083bd97), + UWORD(0x0057dd81), UWORD(0xef9e3159), UWORD(0x776e5356), UWORD(0x03ffeaab), + /* 7 */ + UWORD(0xac4a4e52), UWORD(0x6f9cb7c5), UWORD(0x28bc8f59), UWORD(0x2111d081), + UWORD(0xcfdd9ca1), UWORD(0x3507a2ae), UWORD(0x2e345e00), UWORD(0x8ed0ad40), + UWORD(0xf77cc15e), UWORD(0xd00c46a3), UWORD(0x5bbba972), UWORD(0x01fffd55), + /* 8 */ + UWORD(0x6126f5a9), UWORD(0xd62450d6), UWORD(0xf625cdfb), UWORD(0x6d688680), + UWORD(0x2682bef6), UWORD(0x892248ad), UWORD(0xeb9bc957), UWORD(0x167c18ba), + UWORD(0x6b6d4f7e), UWORD(0x4bb12afb), UWORD(0xaaddddb9), UWORD(0x00ffffaa), + /* 9 */ + UWORD(0x35b13873), UWORD(0x3c7b60c1), UWORD(0x3dd3db79), UWORD(0x891139d5), + UWORD(0x2d83ee26), UWORD(0xec67783a), UWORD(0xee23880b), UWORD(0xe8f2409f), + UWORD(0xab02251c), UWORD(0xa5ca6ade), UWORD(0x5556eeee), UWORD(0x007ffff5), + /* 10 */ + UWORD(0x19a9e4ad), UWORD(0x3a2371fa), UWORD(0x7f3e659f), UWORD(0xc0779364), + UWORD(0x5d60ee69), UWORD(0x3ce83591), UWORD(0xaaca1311), UWORD(0x5f34dbc3), + UWORD(0x019fbcea), UWORD(0x76e52e5a), UWORD(0xaaaab777), UWORD(0x003ffffe), + /* 11 */ + UWORD(0x62185524), UWORD(0x8387ee21), UWORD(0x5a670d6f), UWORD(0x3e58261b), + UWORD(0x856c8458), UWORD(0xa8558831), UWORD(0x4c521697), UWORD(0x8279c359), + UWORD(0x7625624a), UWORD(0xbbba9729), UWORD(0xd55555bb), UWORD(0x001fffff), + /* 12 */ + UWORD(0xd0df378a), UWORD(0x0207abfa), UWORD(0x8f77e0a8), UWORD(0xe06aedfe), + UWORD(0xf087ef47), UWORD(0xc118a554), UWORD(0x5117f928), UWORD(0xbbe778a7), + UWORD(0xb94d5bd5), UWORD(0xdddddb94), UWORD(0xfaaaaaad), UWORD(0x000fffff), + /* 13 */ + UWORD(0xab294c10), UWORD(0xa5f57a6c), UWORD(0xea0510ff), UWORD(0x4a6b20ca), + UWORD(0x346b7d5f), UWORD(0x3c4795ee), UWORD(0x60615272), UWORD(0x340311a8), + UWORD(0x5ca5cb40), UWORD(0x6eeeeeea), UWORD(0xff555555), UWORD(0x0007ffff), + /* 14 */ + UWORD(0x26fc672a), UWORD(0xf8001a59), UWORD(0xbbe868e8), UWORD(0x95ed04d0), + UWORD(0x55713634), UWORD(0xc9df826e), UWORD(0xdad667e3), UWORD(0xc4ac4abe), + UWORD(0x6e52e52e), UWORD(0xab777777), UWORD(0xffeaaaaa), UWORD(0x0003ffff), + /* 15 */ + UWORD(0x24cdaf09), UWORD(0xda0a0409), UWORD(0x4ccac1de), UWORD(0x39e7207f), + UWORD(0x99a76beb), UWORD(0xda4518ae), UWORD(0xaac0891f), UWORD(0x29ab7ab7), + UWORD(0xbba97297), UWORD(0x555bbbbb), UWORD(0xfffd5555), UWORD(0x0001ffff), + /* 16 */ + UWORD(0xef79230f), UWORD(0x87d6bb52), UWORD(0xba283ecc), UWORD(0x6bc26e1d), + UWORD(0xfbc293d3), UWORD(0x96923701), UWORD(0x8067ef3a), UWORD(0x94b96806), + UWORD(0xddddb94b), UWORD(0xaaaadddd), UWORD(0xffffaaaa), UWORD(0x0000ffff), + /* 17 */ + UWORD(0xd253c08a), UWORD(0x9fa7134c), UWORD(0x18cfe1de), UWORD(0x719ceb43), + UWORD(0x5336a7c7), UWORD(0xa09e66fe), UWORD(0x95895892), UWORD(0xca5ca5d8), + UWORD(0xeeeeeea5), UWORD(0x555556ee), UWORD(0xfffff555), UWORD(0x00007fff), + /* 18 */ + UWORD(0xe07094b3), UWORD(0xc822951d), UWORD(0x001ba866), UWORD(0x7489ed19), + UWORD(0x8585fe8e), UWORD(0x6ef9de29), UWORD(0x356f56f5), UWORD(0xe52e52e5), + UWORD(0x77777776), UWORD(0xaaaaaab7), UWORD(0xfffffeaa), UWORD(0x00003fff), + /* 19 */ + UWORD(0xd1a73947), UWORD(0x73003998), UWORD(0x6efcc140), UWORD(0x8f9a6e03), + UWORD(0x1815de9c), UWORD(0x0d00c46a), UWORD(0x972d00d0), UWORD(0xba972972), + UWORD(0xbbbbbbbb), UWORD(0x55555555), UWORD(0xffffffd5), UWORD(0x00001fff), + /* 20 */ + UWORD(0x46b4d8d1), UWORD(0x166d0aaa), UWORD(0xa66c3e7e), UWORD(0x4277e1ac), + UWORD(0xb6b599e5), UWORD(0xb12b12af), UWORD(0x4b94bb12), UWORD(0xdddb94b9), + UWORD(0xaddddddd), UWORD(0xaaaaaaaa), UWORD(0xfffffffa), UWORD(0x00000fff), + /* 21 */ + UWORD(0x67162824), UWORD(0xc6f24098), UWORD(0xa88bfd1a), UWORD(0xf5f3c62b), + UWORD(0xeab02247), UWORD(0xadeadead), UWORD(0xa5ca5ca6), UWORD(0xeeeeea5c), + UWORD(0x556eeeee), UWORD(0x55555555), UWORD(0xffffffff), UWORD(0x000007ff), + /* 22 */ + UWORD(0xaac64f89), UWORD(0x1f3097c3), UWORD(0x7ef0a939), UWORD(0xa5a488d4), + UWORD(0xa019fbce), UWORD(0xe5a01a01), UWORD(0x52e52e52), UWORD(0x7777776e), + UWORD(0xaaab7777), UWORD(0xeaaaaaaa), UWORD(0xffffffff), UWORD(0x000003ff), + /* 23 */ + UWORD(0xc4521695), UWORD(0xe4efc350), UWORD(0x6d6da9f1), UWORD(0xa82799bf), + UWORD(0x25625624), UWORD(0x72976256), UWORD(0xa9729729), UWORD(0xbbbbbbbb), + UWORD(0x55555bbb), UWORD(0xfd555555), UWORD(0xffffffff), UWORD(0x000001ff), + /* 24 */ + UWORD(0x5106e928), UWORD(0x9d228c57), UWORD(0x616044a3), UWORD(0x5bbe778a), + UWORD(0xbd5bd5bd), UWORD(0xb94b94d5), UWORD(0xddb94b94), UWORD(0xdddddddd), + UWORD(0xaaaaaadd), UWORD(0xffaaaaaa), UWORD(0xffffffff), UWORD(0x000000ff), + /* 25 */ + UWORD(0xfde15250), UWORD(0x4be69b80), UWORD(0x8605779d), UWORD(0x0340311a), + UWORD(0xb4034034), UWORD(0x5ca5ca5c), UWORD(0xeeeea5ca), UWORD(0xeeeeeeee), + UWORD(0x55555556), UWORD(0xfff55555), UWORD(0xffffffff), UWORD(0x0000007f), + /* 26 */ + UWORD(0x299b53e3), UWORD(0x504f386b), UWORD(0xedad6679), UWORD(0xac4ac4ab), + UWORD(0x52ec4ac4), UWORD(0x2e52e52e), UWORD(0x777776e5), UWORD(0xb7777777), + UWORD(0xaaaaaaaa), UWORD(0xfffeaaaa), UWORD(0xffffffff), UWORD(0x0000003f), + /* 27 */ + UWORD(0xea22ff47), UWORD(0xfd7cef14), UWORD(0x7aac0891), UWORD(0xab7ab7ab), + UWORD(0x29729ab7), UWORD(0x97297297), UWORD(0xbbbbbbba), UWORD(0x55bbbbbb), + UWORD(0x55555555), UWORD(0xffffd555), UWORD(0xffffffff), UWORD(0x0000001f), + /* 28 */ + UWORD(0x0c0c2a4e), UWORD(0xa9692235), UWORD(0x68067ef3), UWORD(0x80680680), + UWORD(0x94b94b96), UWORD(0xdb94b94b), UWORD(0xdddddddd), UWORD(0xaaaddddd), + UWORD(0xaaaaaaaa), UWORD(0xfffffaaa), UWORD(0xffffffff), UWORD(0x0000000f), + /* 29 */ + UWORD(0xdb5accfc), UWORD(0x2a09e66f), UWORD(0x89589589), UWORD(0x5d895895), + UWORD(0xca5ca5ca), UWORD(0xeeea5ca5), UWORD(0xeeeeeeee), UWORD(0x55556eee), + UWORD(0x55555555), UWORD(0xffffff55), UWORD(0xffffffff), UWORD(0x00000007), + /* 30 */ + UWORD(0x98581123), UWORD(0x56ef9de2), UWORD(0x6f56f56f), UWORD(0x2e5356f5), + UWORD(0xe52e52e5), UWORD(0x77776e52), UWORD(0x77777777), UWORD(0xaaaaab77), + UWORD(0xaaaaaaaa), UWORD(0xffffffea), UWORD(0xffffffff), UWORD(0x00000003), + /* 31 */ + UWORD(0xa1815de7), UWORD(0x00d00c46), UWORD(0x0d00d00d), UWORD(0x972972d0), + UWORD(0x72972972), UWORD(0xbbbbbba9), UWORD(0xbbbbbbbb), UWORD(0x5555555b), + UWORD(0x55555555), UWORD(0xfffffffd), UWORD(0xffffffff), UWORD(0x00000001), + /* 32 */ + UWORD(0xfb6b599e), UWORD(0x2b12b12a), UWORD(0xb12b12b1), UWORD(0x4b94b94b), + UWORD(0xb94b94b9), UWORD(0xdddddddd), UWORD(0xdddddddd), UWORD(0xaaaaaaaa), + UWORD(0xaaaaaaaa), UWORD(0xffffffff), UWORD(0xffffffff), UWORD(0x00000000), +}; + +#endif diff --git a/src/fixed/tan_bitwise_rs.c b/src/fixed/tan_bitwise_rs.c index bb370d15b1..a98203fe13 100644 --- a/src/fixed/tan_bitwise_rs.c +++ b/src/fixed/tan_bitwise_rs.c @@ -1,3 +1,4 @@ + /* Copyright (C) 2026 Fredrik Johansson @@ -13,10 +14,88 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "tan_rotate.inc" +/* R = floor-or-slightly-below of (2^(128n-1) - 1) / S for S with the + top bit set (S' = S 2^-64n in [1/2, 1)), i.e. R ~ 1/(2 S') scaled + to n limbs, R < 2^(64n). N holds the all-ones numerator and h is + n limbs of scratch; both callers pass them. Above the cutoff this + runs fixed_inv_newton with one guard limb; its two-sided error + 4 B^(-n-1) / S' <= 8 B^(-n-1), shifted into R's frame, stays below + one R ulp, so truncating leaves R within 1 + eps ulps of + floor(N / S) -- the callers' mulhigh truncation budgets absorb + that like the tdiv floor. When S' = 1/2 exactly the + contract value 1/S' = 2 would overflow R; the compensated result + clamps to all ones, matching the tdiv path. The cutoff comes + from in-context measurements (see dev/notes): in a tight loop the + Newton inversion wins from a few dozen limbs, but embedded in the + evaluation it loses 10-15% of the whole call at n = 512-1024 and + only wins (3-6%) from about 1536 limbs, where the FFT range + starts. */ +/* Evaluate only the sine series and recover the cosine through a + squaring and a square root once the series gets long enough, + mirroring EXP_USE_SINH: the sine series of the reduced argument + t' < 2^-r drops one term per 2r bits, so FLINT_BITS n / (2 r) + terms in all, and skipping the cosine pass saves one of the two + rectangular-splitting sums against the fixed cost of the squaring + and the square root. 50 terms matches the crossover measured on + the reference machine (n = 150 with the default ladder, which + puts n = 150 in the r = 192 band: 64 * 150 = 50 * 192); the + development VM measured a much later crossover in context, in + line with its inflated Newton cutoffs, and the exp analogue uses + 128 terms. Retune with the Newton cutoffs. */ +#ifndef FIXED_TRIG_SIN_SQRT_TERMS +#define FIXED_TRIG_SIN_SQRT_TERMS 50 +#endif +#define TRIG_USE_SIN_SQRT(n, r) \ + (FLINT_BITS * (n) >= FIXED_TRIG_SIN_SQRT_TERMS * (slong) (r)) + +/* working precision from which that square root runs through + fixed_sqrt_newton instead of mpn_sqrtrem (same shape as the exp + sinh-path square root) */ +#ifndef FIXED_TRIG_SQRT_NEWTON_CUTOFF +#define FIXED_TRIG_SQRT_NEWTON_CUTOFF 2000 +#endif + +#ifndef FIXED_TRIG_RECIP_NEWTON_CUTOFF +#define FIXED_TRIG_RECIP_NEWTON_CUTOFF 40 +#endif + +static void +_fixed_recip_bn(nn_ptr R, nn_ptr h, nn_srcptr N, nn_srcptr S, slong n) +{ + if (n < FIXED_TRIG_RECIP_NEWTON_CUTOFF) + { + mpn_tdiv_qr(R, h, 0, N, 2 * n, S, n); + } + else + { + nn_ptr q; + TMP_INIT; + TMP_START; + + /* (q, n+3) ~ 1/S' with n+1 fraction limbs, 2 integral */ + q = TMP_ALLOC((n + 3) * sizeof(ulong)); + fixed_inv_newton(q, S, n, n + 1); + + /* R = (1/S') 2^(64n-1): shift q down 65 bits and truncate + the guard limb; the two-sided error (< 8 half-ulps of q's + bottom limb) leaves R within 1 + eps ulps of floor(N / S), + which the callers' mulhigh truncation budgets absorb like + the tdiv floor */ + mpn_rshift(q, q + 1, n + 2, 1); + flint_mpn_copyi(R, q, n); + + /* q's remaining top limb is 1 iff 1/S' reached 2 (S' = 1/2): + clamp to all ones as the all-ones numerator does */ + if (q[n] != 0) + flint_mpn_store(R, n, ~UWORD(0)); + + TMP_END; + } +} + /* the sizes for which the rotation is emitted in registers */ #define FIXED_TAN_ROTATE_NMAX 7 @@ -96,8 +175,8 @@ _fixed_tan_halfangle_mid(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, slong * used; TMP_INIT; - _fixed_atans_ensure(n, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(n, r, &nc); TMP_START; t = TMP_ALLOC((n + n + wn + wn + n + n + wn + wn @@ -120,7 +199,7 @@ _fixed_tan_halfangle_mid(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, /* reduce x/2; the residual t' is wanted as it stands, so unlike the sin/cos reduction there is no doubling and no extra index */ mpn_rshift(t, x, n, 1); - num = _fixed_bitwise_reduce(t, n, r, 1, _fixed_atans, nc, used); + num = _fixed_bitwise_reduce(t, n, r, 1, tab, nc, used); /* W = prod (1 + i 2^-i). For the small sizes this runs in registers (tan_rotate.inc): the generic loop below materializes @@ -134,32 +213,161 @@ _fixed_tan_halfangle_mid(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, } else { - flint_mpn_zero(wx, n); - wx[n] = 1; - flint_mpn_zero(wy, wn); + /* Growing-extent form of the rotation product, the trig analogue + of the exp product reconstruction: with the used indices + ascending write W = (1 - alpha) + i b, alpha, b >= 0 + (alpha starts at 0 and only grows; b stays below 1 since + the total angle is under one radian), both lsb-anchored as + integers at a common extent B that deepens by i per factor: + + alpha' = (alpha << i) + b + b' = (b << i) + 2^B - alpha + + using the old values on the right. The extent grows only + quadratically (about the sum of the used indices), so + several factors fit in one limb, then two, three, ...; + limbs deeper than 64 (wn + 1) bits are dropped as the + buffers grow, each drop losing under one bit two guard + limbs down. Unlike exp there is no final multiplication + at all: wx = 1 - alpha and wy = b are expanded into their + dense frames in one pass each. */ + slong capd = FLINT_BITS * (wn + 1); + slong B = 0, al = 1, bl = 1, alloc; + nn_ptr aa, at, ba, bt; + TMP_INIT; + + TMP_START; + /* extent below capd + 63 + max_index bits; a fixed "+ 16" + slack overflowed on 32-bit limbs at large r, exactly as + in the exp product reconstruction */ + alloc = wn + 4 + ((num > 0) ? used[num - 1] : 0) / FLINT_BITS; + aa = TMP_ALLOC(4 * alloc * sizeof(ulong)); + at = aa + alloc; + ba = at + alloc; + bt = ba + alloc; + aa[0] = 0; + ba[0] = 0; for (j = 0; j < num; j++) { slong ii = used[j], q = ii / FLINT_BITS; - int b = (int) (ii - q * FLINT_BITS); + int sb = (int) (ii - q * FLINT_BITS); + slong la, lb, d; + ulong cy; + + /* at = (aa << ii) + ba */ + la = al + q + (sb != 0); + flint_mpn_zero(at, q); + if (sb) + at[al + q] = mpn_lshift(at + q, aa, al, sb); + else + flint_mpn_copyi(at + q, aa, al); + while (la > 1 && at[la - 1] == 0) + la--; + if (la >= bl) + { + cy = mpn_add(at, at, la, ba, bl); + at[la] = cy; + la += (cy != 0); + } + else + { + cy = mpn_add(at, ba, bl, at, la); + at[bl] = cy; + la = bl + (cy != 0); + } + + /* bt = (ba << ii) + 2^B - aa */ + lb = bl + q + (sb != 0); + flint_mpn_zero(bt, q); + if (sb) + bt[bl + q] = mpn_lshift(bt + q, ba, bl, sb); + else + flint_mpn_copyi(bt + q, ba, bl); + while (lb > 1 && bt[lb - 1] == 0) + lb--; + { + slong pb = B / FLINT_BITS; + if (pb >= lb) + { + flint_mpn_zero(bt + lb, pb - lb + 1); + lb = pb + 1; + } + cy = mpn_add_1(bt + pb, bt + pb, lb - pb, + UWORD(1) << (B % FLINT_BITS)); + bt[lb] = cy; + lb += (cy != 0); + } + if (al > 0 && !(al == 1 && aa[0] == 0)) + mpn_sub(bt, bt, lb, aa, al); + while (lb > 1 && bt[lb - 1] == 0) + lb--; + + { nn_ptr u_; u_ = aa; aa = at; at = u_; + u_ = ba; ba = bt; bt = u_; } + al = la; + bl = lb; + B += ii; + + d = (B - capd) / FLINT_BITS; + if (d > 0) + { + if (d < al) { flint_mpn_copyi(aa, aa + d, al - d); al -= d; } + else { aa[0] = 0; al = 1; } + if (d < bl) { flint_mpn_copyi(ba, ba + d, bl - d); bl -= d; } + else { ba[0] = 0; bl = 1; } + B -= FLINT_BITS * d; + } + } + + /* expand: wy = b 2^(64 n), wx = 2^(64 n) - alpha 2^(64 n) */ + { + slong sh = FLINT_BITS * n - B; - flint_mpn_zero(va + (wn - q), q); - flint_mpn_zero(vb + (n - q), q); - if (b != 0) + flint_mpn_zero(wy, wn); + flint_mpn_zero(va, wn + 1); + if (sh >= 0) { - mpn_rshift(va, wx + q, wn - q, b); - if (n - q > 0) - mpn_rshift(vb, wy + q, n - q, b); + slong q = sh / FLINT_BITS; + int b2 = (int) (sh % FLINT_BITS); + if (b2) + { + if (bl + q < wn + 1) + wy[bl + q] = mpn_lshift(wy + q, ba, bl, b2); + else + mpn_lshift(wy + q, ba, bl, b2); + va[al + q] = mpn_lshift(va + q, aa, al, b2); + } + else + { + flint_mpn_copyi(wy + q, ba, bl); + flint_mpn_copyi(va + q, aa, al); + } } else { - flint_mpn_copyi(va, wx + q, wn - q); - if (n - q > 0) - flint_mpn_copyi(vb, wy + q, n - q); + slong q = (-sh) / FLINT_BITS; + int b2 = (int) ((-sh) % FLINT_BITS); + if (b2) + { + if (bl - q > 0) + mpn_rshift(wy, ba + q, bl - q, b2); + if (al - q > 0) + mpn_rshift(va, aa + q, al - q, b2); + } + else + { + if (bl - q > 0) + flint_mpn_copyi(wy, ba + q, bl - q); + if (al - q > 0) + flint_mpn_copyi(va, aa + q, al - q); + } } - mpn_sub(wx, wx, wn, vb, n); - mpn_add(wy, wy, wn, va, n); + flint_mpn_zero(wx, n); + wx[n] = 1; + mpn_sub(wx, wx, wn, va, wn); } + TMP_END; } /* TT and DE, the imaginary and real parts of W (cos t' + i sin t') @@ -195,12 +403,61 @@ _fixed_tan_halfangle_mid(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, } else { - fixed_sin_cos_rs(ss, cc, t, n); - - if (cc[n]) - flint_mpn_zero(cc, n); /* cos t' = 1: g = 0 */ + if (TRIG_USE_SIN_SQRT(n, r)) + { + /* Evaluate only the sine series -- the analogue of the + exp sinh path -- and recover + + g = 1 - cos t' = 1 - sqrt(1 - sin^2 t') + + by a squaring and a square root, saving the cosine + pass of fixed_sin_cos_rs (the powers of t'^2 are + shared between the two sums, but each sum is a full + rectangular-splitting pass). The square root sits + just below 1, so its derivative is ~1/2 and nothing + is amplified: sqrhigh's few-ulp deficit on sin^2 + (which can only raise 1 - sin^2, lowering g), the + square root's floor (raising g by at most one ulp) + and the Newton route's ~2 compensated ulps leave g + within a few 2^-64n ulps either way, well inside the + budget the direct cosine's truncation already used. */ + nn_ptr u2 = N, rt = Q; /* free until the divisions */ + + fixed_sin_rs(ss, t, n); + + flint_mpn_sqrhigh(rt, ss, n); + if (flint_mpn_zero_p(rt, n)) + { + flint_mpn_zero(cc, n); /* sin^2 below 2^-64n: g = 0 */ + } + else if (n < FIXED_TRIG_SQRT_NEWTON_CUTOFF) + { + flint_mpn_zero(u2, n); + mpn_neg(u2 + n, rt, n); /* (1 - sin^2) 2^(128n) */ + mpn_sqrtrem(rt, sc, u2, 2 * n); + mpn_neg(cc, rt, n); /* g 2^(64n) */ + } + else + { + /* the Newton square root reads short input directly: + (1 - sin^2) as an n-limb fraction, no zero padding */ + mpn_neg(u2, rt, n); + fixed_sqrt_newton(rt, u2, n, n + 2); + if (rt[n + 2]) + flint_mpn_zero(cc, n); /* cos rounded to 1 */ + else + mpn_neg(cc, rt + 2, n); + } + } else - mpn_neg(cc, cc, n); /* g = 1 - cos t' */ + { + fixed_sin_cos_rs(ss, cc, t, n); + + if (cc[n]) + flint_mpn_zero(cc, n); /* cos t' = 1: g = 0 */ + else + mpn_neg(cc, cc, n); /* g = 1 - cos t' */ + } flint_mpn_mulhigh_n(va, wx, ss, n); /* wx ss */ if (wx[n]) @@ -278,7 +535,7 @@ _fixed_tan_halfangle_mid(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, /* R = 1/(2 S'): the all-ones numerator keeps R below 2^(64n) even when S' = 1/2 exactly */ - mpn_tdiv_qr(R, h, 0, N, 2 * n, S, n); + _fixed_recip_bn(R, h, N, S, n); if (ysin != NULL) { @@ -313,7 +570,7 @@ _fixed_tan_halfangle_mid(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, if (e) mpn_lshift(W, W, n, e); - mpn_tdiv_qr(R, h, 0, N, 2 * n, W, n); + _fixed_recip_bn(R, h, N, W, n); /* tan = 2A/(B - C) = 2^(2+e) A R < 1.56 */ flint_mpn_mulhigh_n(ytan, A, R, n); diff --git a/src/fixed/test/main.c b/src/fixed/test/main.c index 4f5817754f..7f7aed235d 100644 --- a/src/fixed/test/main.c +++ b/src/fixed/test/main.c @@ -17,6 +17,13 @@ #include "t-exp_rs.c" #include "t-log1p_bitwise_rs.c" #include "t-sin_cos_bitwise_rs.c" +#include "t-div_newton.c" +#include "t-sqrt_newton.c" +#include "t-tab_bsplit.c" +#include "t-exp_reduced.c" +#include "t-exp_sum_bs.c" +#include "t-tab_floor.c" +#include "t-tab_static.c" #include "t-tan_bitwise_rs.c" #include "t-trig_rs.c" @@ -30,6 +37,13 @@ test_struct tests[] = TEST_FUNCTION(fixed_exp_rs), TEST_FUNCTION(fixed_log1p_bitwise_rs), TEST_FUNCTION(fixed_sin_cos_bitwise_rs), + TEST_FUNCTION(fixed_div_newton), + TEST_FUNCTION(fixed_sqrt_newton), + TEST_FUNCTION(fixed_tab_bsplit), + TEST_FUNCTION(fixed_exp_reduced), + TEST_FUNCTION(fixed_exp_sum_bs), + TEST_FUNCTION(fixed_tab_floor), + TEST_FUNCTION(fixed_tab_static), TEST_FUNCTION(fixed_tan_bitwise_rs), TEST_FUNCTION(fixed_trig_rs) }; diff --git a/src/fixed/test/t-atan_bitwise_rs.c b/src/fixed/test/t-atan_bitwise_rs.c index 115cfda6bb..8ee6a264e9 100644 --- a/src/fixed/test/t-atan_bitwise_rs.c +++ b/src/fixed/test/t-atan_bitwise_rs.c @@ -36,19 +36,23 @@ TEST_FUNCTION_START(fixed_atan_bitwise_rs, state) the series contract) */ slong nmin = (FLINT_BITS == 64) ? 1 : 2; slong n = det ? FLINT_MAX(det_n, nmin) - : nmin + n_randint(state, (iter % 5 == 0) ? 80 : 16); + : nmin + n_randint(state, (iter % 97 == 1) ? 1800 + : (iter % 5 == 0) ? 80 : 16); int r = det ? FLINT_MAX(det_r, ((det_r == 0) ? 0 : 32)) : (iter % 4 == 0) ? 0 : (iter % 9 == 3) ? 32 + (int) n_randint(state, 17) : (iter % 7 == 2) ? FLINT_BITS * (1 + (int) n_randint(state, 7)) : 32 + (int) n_randint(state, 401); - ulong x[81], res[81]; + nn_ptr x, res; arb_t xa, e, va, dd; fmpz_t f; mag_t mb; slong prec = FLINT_BITS * n + 128; double u; + x = flint_malloc((n + 1) * sizeof(ulong)); + res = flint_malloc((n + 1) * sizeof(ulong)); + flint_mpn_rrandom(x, state, n); if (iter % 13 == 3) flint_mpn_zero(x, n); @@ -86,6 +90,8 @@ TEST_FUNCTION_START(fixed_atan_bitwise_rs, state) arb_clear(va); arb_clear(dd); fmpz_clear(f); + flint_free(x); + flint_free(res); mag_clear(mb); } diff --git a/src/fixed/test/t-div_newton.c b/src/fixed/test/t-div_newton.c new file mode 100644 index 0000000000..8deefa9f0b --- /dev/null +++ b/src/fixed/test/t-div_newton.c @@ -0,0 +1,104 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "mpn_extras.h" +#include "fmpz.h" +#include "fixed.h" + +/* Check fixed_inv_newton and fixed_div_newton against basecase + references carrying XTRA extra limbs: with the documented error + bound 4 B^-n / a and the reference's own few ulps at B^-(n+XTRA), + the difference scaled by B^XTRA must stay below 5 B^(XTRA+1) + divided by the denominator's top limb (1/a < B / a_{an-1}). */ + +#define XTRA 8 +#define check newton_div_check + +static int +newton_div_check(nn_srcptr got, slong gn, nn_srcptr ref, const fmpz_t bound) +{ + fmpz_t g, r; + int ok; + fmpz_init(g); + fmpz_init(r); + fmpz_set_ui_array(g, got, gn); + fmpz_mul_2exp(g, g, FLINT_BITS * XTRA); + fmpz_set_ui_array(r, ref, gn + XTRA); + fmpz_sub(g, g, r); + fmpz_abs(g, g); + ok = (fmpz_cmp(g, bound) <= 0); + fmpz_clear(g); + fmpz_clear(r); + return ok; +} + +TEST_FUNCTION_START(fixed_div_newton, state) +{ + slong iter; + + for (iter = 0; iter < 300 * flint_test_multiplier(); iter++) + { + slong n, an, bn; + nn_ptr A, Q, R; + fmpz_t bound; + + if (n_randint(state, 8)) + { + n = 1 + n_randint(state, 50); + an = 1 + n_randint(state, 50); + } + else + { + n = 1 + n_randint(state, 600); + an = 1 + n_randint(state, 600); + } + bn = 1 + n_randint(state, an); + + A = flint_malloc(an * sizeof(ulong)); + Q = flint_malloc((n + 2) * sizeof(ulong)); + R = flint_malloc((n + XTRA + 2) * sizeof(ulong)); + fmpz_init(bound); + + flint_mpn_rrandom(A, state, an); + if (n_randint(state, 5) == 0) + A[an - 1] = 1 + n_randint(state, 100); + if (A[an - 1] == 0) + A[an - 1] = 1; + + fmpz_set_ui(bound, 5); + fmpz_mul_2exp(bound, bound, FLINT_BITS * (XTRA + 1)); + fmpz_tdiv_q_ui(bound, bound, A[an - 1]); + fmpz_add_ui(bound, bound, 8); + + fixed_inv_newton(Q, A, an, n); + fixed_inv_newton_basecase(R, A, an, n + XTRA); + + if (!check(Q, n + 2, R, bound)) + TEST_FUNCTION_FAIL("inv: n = %wd, an = %wd\n", n, an); + + fixed_div_newton(Q, A, bn, A, an, n); + fixed_div_newton_invmul(R, A, bn, A, an, n + XTRA); + + /* both sides approximate: double the allowance */ + fmpz_mul_ui(bound, bound, 2); + if (!check(Q, n + 2, R, bound)) + TEST_FUNCTION_FAIL("div: n = %wd, an = %wd, bn = %wd\n", + n, an, bn); + + flint_free(A); + flint_free(Q); + flint_free(R); + fmpz_clear(bound); + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-exp_bitwise_rs.c b/src/fixed/test/t-exp_bitwise_rs.c index 8ab6cd861f..00779f8b0c 100644 --- a/src/fixed/test/t-exp_bitwise_rs.c +++ b/src/fixed/test/t-exp_bitwise_rs.c @@ -37,18 +37,21 @@ TEST_FUNCTION_START(fixed_exp_bitwise_rs, state) the series contract) */ slong nmin = (FLINT_BITS == 64) ? 1 : 2; slong n = det ? FLINT_MAX(det_n, nmin) - : nmin + n_randint(state, (iter % 5 == 0) ? 80 : 16); + : nmin + n_randint(state, (iter % 97 == 1) ? 2400 + : (iter % 5 == 0) ? 80 : 16); int r = det ? FLINT_MAX(det_r, ((det_r == 0) ? 0 : 32)) : (iter % 4 == 0) ? 0 : (iter % 7 == 2) ? FLINT_BITS * (1 + (int) n_randint(state, 7)) : 32 + (int) n_randint(state, 417); - ulong x[90], res[91]; + nn_ptr x, res; arb_t xa, e, va, delta; fmpz_t t; mag_t mb; slong prec = FLINT_BITS * n + 128; double u; + x = flint_malloc(n * sizeof(ulong)); + res = flint_malloc((n + 2) * sizeof(ulong)); flint_mpn_rrandom(x, state, n); if (iter % 13 == 3) flint_mpn_zero(x, n); @@ -80,6 +83,8 @@ TEST_FUNCTION_START(fixed_exp_bitwise_rs, state) if (u > (double) FIXED_EXP_BITWISE_RS_MAX_ERR(n, r)) TEST_FUNCTION_FAIL("n = %wd, r = %d, ulp = %f\n", n, r, u); + flint_free(x); + flint_free(res); } TEST_FUNCTION_END(state); diff --git a/src/fixed/test/t-exp_reduced.c b/src/fixed/test/t-exp_reduced.c new file mode 100644 index 0000000000..55f400b570 --- /dev/null +++ b/src/fixed/test/t-exp_reduced.c @@ -0,0 +1,152 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "arb.h" +#include "fixed.h" + +/* every algorithm of fixed_exp_reduced agrees with arb within the + documented budget, for random (wn, r) with t < 2^-r */ + +TEST_FUNCTION_START(fixed_exp_reduced, state) +{ + slong iter; + + for (iter = 0; iter < 20 + 20 * flint_test_multiplier(); iter++) + { + slong wn = 1 + n_randint(state, (iter % 17 == 0) ? 700 : 60); + flint_bitcnt_t r = 16 + n_randint(state, + FLINT_MIN(FLINT_BITS * (ulong) wn, 3000)); + slong i, alg; + nn_ptr t, y; + arb_t ta, ref, got; + fmpz_t f; + + t = flint_malloc((wn + 2) * sizeof(ulong)); + y = flint_malloc((wn + 2) * sizeof(ulong)); + arb_init(ta); arb_init(ref); arb_init(got); fmpz_init(f); + + flint_mpn_urandomb(t, state, FLINT_BITS * wn); + /* clear the top r bits so t < 2^-r */ + for (i = 0; i < (slong) (r / FLINT_BITS) && i < wn; i++) + t[wn - 1 - i] = 0; + if ((r % FLINT_BITS) && (slong) (r / FLINT_BITS) < wn) + t[wn - 1 - r / FLINT_BITS] >>= (r % FLINT_BITS); + + fmpz_set_ui_array(f, t, wn); + arb_set_fmpz(ta, f); + arb_mul_2exp_si(ta, ta, -FLINT_BITS * wn); + arb_exp(ref, ta, FLINT_BITS * wn + 128); + + for (alg = 0; alg <= 4; alg++) + { + if (alg == 1 || alg == 2) + if (r < 32) + continue; + fixed_exp_reduced(y, t, wn, r, (int) alg); + + fmpz_set_ui_array(f, y, wn + 1); + arb_set_fmpz(got, f); + arb_mul_2exp_si(got, got, -FLINT_BITS * wn); + arb_sub(got, got, ref, FLINT_BITS * wn + 128); + arb_mul_2exp_si(got, got, FLINT_BITS * wn); + arb_abs(got, got); + { + arb_t bnd; + arb_init(bnd); + arb_set_ui(bnd, FIXED_EXP_REDUCED_MAX_ERR); + if (!arb_le(got, bnd)) + { + arb_clear(bnd); + TEST_FUNCTION_FAIL("alg = %wd, wn = %wd, " + "r = %wd\n", alg, wn, (slong) r); + } + arb_clear(bnd); + } + } + + arb_clear(ta); arb_clear(ref); arb_clear(got); fmpz_clear(f); + flint_free(t); flint_free(y); + } + + /* targeted precision regimes that random sampling misses */ + { + struct { slong wn; flint_bitcnt_t r; int alg; int fill; } + cases[] = { + { 64, 16, 0, 0 }, /* r < 32 forces a burst in auto */ + { 48, 16, 4, 1 }, /* extraction masks, all-ones */ + { 48, 63, 3, 1 }, + { 48, 64, 4, 1 }, + { 48, 65, 3, 1 }, + { 48, 127, 4, 1 }, + { 48, 128, 3, 1 }, + { 48, 129, 4, 1 }, + { 80, 34, 3, 1 }, /* the span-carry hard shape */ + { 512, 16, 4, 0 }, /* Qexp < wp up-shift branch */ + { 1100, 16, 0, 0 }, /* FULLBURST gate in auto */ + { 2100, 128, 2, 0 }, /* sinh square root above the + Newton cutoff */ + { 40, 256, 0, 2 }, /* t = 0: y = 1 on every path */ + }; + slong c, i; + + for (c = 0; c < (slong) (sizeof(cases) / sizeof(cases[0])); + c++) + { + slong wn = cases[c].wn; + flint_bitcnt_t r = cases[c].r; + nn_ptr t, y; + arb_t ta, ref, got, bnd; + fmpz_t f; + + t = flint_malloc((wn + 2) * sizeof(ulong)); + y = flint_malloc((wn + 2) * sizeof(ulong)); + arb_init(ta); arb_init(ref); arb_init(got); + arb_init(bnd); fmpz_init(f); + + if (cases[c].fill == 2) + flint_mpn_zero(t, wn); + else if (cases[c].fill == 1) + flint_mpn_store(t, wn, ~UWORD(0)); + else + flint_mpn_urandomb(t, state, FLINT_BITS * wn); + for (i = 0; i < (slong) (r / FLINT_BITS) && i < wn; i++) + t[wn - 1 - i] = 0; + if ((r % FLINT_BITS) && (slong) (r / FLINT_BITS) < wn) + t[wn - 1 - r / FLINT_BITS] >>= (r % FLINT_BITS); + + fmpz_set_ui_array(f, t, wn); + arb_set_fmpz(ta, f); + arb_mul_2exp_si(ta, ta, -FLINT_BITS * wn); + arb_exp(ref, ta, FLINT_BITS * wn + 128); + + fixed_exp_reduced(y, t, wn, r, cases[c].alg); + + fmpz_set_ui_array(f, y, wn + 1); + arb_set_fmpz(got, f); + arb_mul_2exp_si(got, got, -FLINT_BITS * wn); + arb_sub(got, got, ref, FLINT_BITS * wn + 128); + arb_mul_2exp_si(got, got, FLINT_BITS * wn); + arb_abs(got, got); + arb_set_ui(bnd, FIXED_EXP_REDUCED_MAX_ERR); + if (!arb_le(got, bnd)) + TEST_FUNCTION_FAIL("targeted case %wd: wn = %wd, " + "r = %wd, alg = %d\n", c, wn, (slong) r, + cases[c].alg); + + arb_clear(ta); arb_clear(ref); arb_clear(got); + arb_clear(bnd); fmpz_clear(f); + flint_free(t); flint_free(y); + } + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-exp_sum_bs.c b/src/fixed/test/t-exp_sum_bs.c new file mode 100644 index 0000000000..aff33c60ab --- /dev/null +++ b/src/fixed/test/t-exp_sum_bs.c @@ -0,0 +1,151 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "arb.h" +#include "fixed.h" + +/* the mpn port must reproduce _arb_exp_sum_bs_powtab bit for bit */ + +TEST_FUNCTION_START(fixed_exp_sum_bs, state) +{ + slong iter; + + for (iter = 0; iter < 40 + 40 * flint_test_multiplier(); iter++) + { + slong xn = 1 + n_randint(state, 6); + flint_bitcnt_t r = FLINT_BITS * (xn + n_randint(state, 4)); + slong N = 1 + n_randint(state, + (iter % 13 == 0) ? 1600 : 300); + slong tn, qn, i; + flint_bitcnt_t Qexp; + nn_ptr xp, T, Q; + fmpz_t x, Ta, Qa, Tm, Qm; + flint_bitcnt_t Qexpa; + + xp = flint_malloc(xn * sizeof(ulong)); + T = flint_malloc(((N * ((slong) r + 128)) / FLINT_BITS + 4) + * sizeof(ulong)); + Q = flint_malloc(((N * FLINT_BIT_COUNT((ulong) N + 1)) + / FLINT_BITS + 3) * sizeof(ulong)); + + flint_mpn_urandomb(xp, state, FLINT_BITS * xn); + xp[xn - 1] |= UWORD(1); /* nonzero */ + while (xp[xn - 1] == 0) + xn--; + + fmpz_init(x); fmpz_init(Ta); fmpz_init(Qa); + fmpz_init(Tm); fmpz_init(Qm); + fmpz_set_ui_array(x, xp, xn); + + _arb_exp_sum_bs_powtab(Ta, Qa, &Qexpa, x, r, N); + _fixed_exp_sum_bs_powtab(T, &tn, Q, &qn, &Qexp, xp, xn, r, N); + + fmpz_set_ui_array(Tm, T, FLINT_MAX(tn, 1)); + fmpz_set_ui_array(Qm, Q, FLINT_MAX(qn, 1)); + + if (!fmpz_equal(Tm, Ta) || !fmpz_equal(Qm, Qa) + || Qexp != Qexpa) + TEST_FUNCTION_FAIL("xn = %wd, r = %wd, N = %wd " + "(Qexp %wd vs %wd)\n", xn, (slong) r, N, + (slong) Qexp, (slong) Qexpa); + + /* lengths must be normalized */ + if ((tn > 0 && T[tn - 1] == 0) || (qn > 0 && Q[qn - 1] == 0)) + TEST_FUNCTION_FAIL("unnormalized output\n"); + (void) i; + + fmpz_clear(x); fmpz_clear(Ta); fmpz_clear(Qa); + fmpz_clear(Tm); fmpz_clear(Qm); + flint_free(xp); flint_free(T); flint_free(Q); + } + + /* the factor assembly reproduces Q 2^Qexp + T on both its + branches: exactly below the cap window, and as a top window + with the dropped bits (under one at the limb-aligned bottom) + in the exponent */ + for (iter = 0; iter < 50 + 50 * flint_test_multiplier(); iter++) + { + slong cap = 2 + n_randint(state, 8); + /* the helper requires Q inside the window: qb < 64 (cap+1) */ + slong qn = 1 + n_randint(state, cap); + slong tn = n_randint(state, cap + 2); + flint_bitcnt_t Qexp = n_randint(state, + FLINT_BITS * (cap + 4)); + slong fn, fexp; + nn_ptr Q, T, F; + fmpz_t exact, got, low; + + Q = flint_malloc(qn * sizeof(ulong)); + T = flint_malloc(FLINT_MAX(tn, 1) * sizeof(ulong)); + F = flint_malloc((cap + 3) * sizeof(ulong)); + flint_mpn_urandomb(Q, state, FLINT_BITS * qn); + Q[qn - 1] |= UWORD(1) << (FLINT_BITS - 1 + - n_randint(state, FLINT_BITS)); + while (qn > 1 && Q[qn - 1] == 0) + qn--; + if (tn > 0) + flint_mpn_urandomb(T, state, FLINT_BITS * tn); + while (tn > 0 && T[tn - 1] == 0) + tn--; + /* keep the invariant T < Q 2^Qexp (F < 1.5 Q 2^Qexp): drop + T when it would dominate */ + if (FLINT_BITS * tn > (slong) Qexp) + tn = 0; + + fmpz_init(exact); fmpz_init(got); fmpz_init(low); + fmpz_set_ui_array(exact, Q, qn); + fmpz_mul_2exp(exact, exact, Qexp); + if (tn > 0) + { + fmpz_set_ui_array(low, T, tn); + fmpz_add(exact, exact, low); + } + + _fixed_exp_burst_factor(F, &fn, &fexp, T, tn, Q, qn, + Qexp, cap); + + fmpz_set_ui_array(got, F, fn); + fmpz_mul_2exp(got, got, fexp); + fmpz_sub(low, exact, got); + /* 0 <= exact - got < 2^fexp, and exact when fexp = 0 */ + if (fmpz_sgn(low) < 0 + || fmpz_sizeinbase(low, 2) > (ulong) FLINT_MAX(fexp, 1) + || (fexp == 0 && !fmpz_is_zero(low))) + TEST_FUNCTION_FAIL("factor: cap = %wd, qn = %wd, " + "tn = %wd, Qexp = %wd\n", cap, qn, tn, + (slong) Qexp); + + fmpz_clear(exact); fmpz_clear(got); fmpz_clear(low); + flint_free(Q); flint_free(T); flint_free(F); + } + + /* the terms helper agrees with the arb pair it ports, up to the + safe floor-log continuation beyond the table */ + { + slong r2, p2; + for (r2 = 16; r2 <= 512; r2 *= 2) + for (p2 = 128; p2 <= 65536; p2 *= 4) + { + slong Nf = _fixed_exp_bs_num_terms( + (flint_bitcnt_t) r2, p2); + slong Na = _arb_exp_taylor_bound(-r2, p2) - 1; + if (Na > 10000) while (Na % 128) Na++; + if (Na > 1000) while (Na % 16) Na++; + if (Na > 100) while (Na % 2) Na++; + if (Nf < Na || Nf > Na + Na / 8 + 16) + TEST_FUNCTION_FAIL("terms: r = %wd, prec = %wd: " + "%wd vs arb %wd\n", r2, p2, Nf, Na); + } + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-log1p_bitwise_rs.c b/src/fixed/test/t-log1p_bitwise_rs.c index 263c4adf47..baf323c69d 100644 --- a/src/fixed/test/t-log1p_bitwise_rs.c +++ b/src/fixed/test/t-log1p_bitwise_rs.c @@ -37,18 +37,22 @@ TEST_FUNCTION_START(fixed_log1p_bitwise_rs, state) the series contract) */ slong nmin = (FLINT_BITS == 64) ? 1 : 2; slong n = det ? FLINT_MAX(det_n, nmin) - : nmin + n_randint(state, (iter % 5 == 0) ? 80 : 16); + : nmin + n_randint(state, (iter % 97 == 1) ? 1800 + : (iter % 5 == 0) ? 80 : 16); int r = det ? FLINT_MAX(det_r, ((det_r == 0) ? 0 : 32)) : (iter % 4 == 0) ? 0 : (iter % 7 == 2) ? FLINT_BITS * (1 + (int) n_randint(state, 7)) : 32 + (int) n_randint(state, 401); - ulong x[90], res[90]; + nn_ptr x, res; arb_t xa, e, va, delta; fmpz_t t; mag_t mb; slong prec = FLINT_BITS * n + 128; double u; + x = flint_malloc((n + 1) * sizeof(ulong)); + res = flint_malloc((n + 1) * sizeof(ulong)); + flint_mpn_rrandom(x, state, n); if (iter % 13 == 3) flint_mpn_zero(x, n); @@ -78,6 +82,8 @@ TEST_FUNCTION_START(fixed_log1p_bitwise_rs, state) arb_clear(xa); arb_clear(e); arb_clear(va); arb_clear(delta); fmpz_clear(t); mag_clear(mb); + flint_free(x); + flint_free(res); if (u > (double) FIXED_LOG1P_BITWISE_RS_MAX_ERR(n, r)) TEST_FUNCTION_FAIL("n = %wd, r = %d, ulp = %f\n", n, r, u); diff --git a/src/fixed/test/t-sin_cos_bitwise_rs.c b/src/fixed/test/t-sin_cos_bitwise_rs.c index 31b7496e7e..269211ea38 100644 --- a/src/fixed/test/t-sin_cos_bitwise_rs.c +++ b/src/fixed/test/t-sin_cos_bitwise_rs.c @@ -35,13 +35,14 @@ TEST_FUNCTION_START(fixed_sin_cos_bitwise_rs, state) /* n = 1 is unsupported on 32-bit limbs */ slong nmin = (FLINT_BITS == 64) ? 1 : 2; slong n = det ? FLINT_MAX(det_n, nmin) - : nmin + n_randint(state, (iter % 5 == 0) ? 80 : 16); + : nmin + n_randint(state, (iter % 97 == 1) ? 1800 + : (iter % 5 == 0) ? 80 : 16); int r = det ? FLINT_MAX(det_r, ((det_r == 0) ? 0 : 32)) : (iter % 4 == 0) ? 0 : (iter % 9 == 3) ? 32 + (int) n_randint(state, 17) : (iter % 7 == 2) ? FLINT_BITS * (1 + (int) n_randint(state, 7)) : 32 + (int) n_randint(state, 401); - ulong x[82], ysin[83], ycos[83]; + nn_ptr x, ysin, ycos; arb_t xa, e, va, dd; fmpz_t f; mag_t mb; @@ -49,6 +50,10 @@ TEST_FUNCTION_START(fixed_sin_cos_bitwise_rs, state) double u; int which; + x = flint_malloc((n + 2) * sizeof(ulong)); + ysin = flint_malloc((n + 2) * sizeof(ulong)); + ycos = flint_malloc((n + 2) * sizeof(ulong)); + flint_mpn_rrandom(x, state, n); if (iter % 13 == 3) flint_mpn_zero(x, n); @@ -100,6 +105,9 @@ TEST_FUNCTION_START(fixed_sin_cos_bitwise_rs, state) arb_clear(dd); fmpz_clear(f); mag_clear(mb); + flint_free(x); + flint_free(ysin); + flint_free(ycos); } TEST_FUNCTION_END(state); diff --git a/src/fixed/test/t-sqrt_newton.c b/src/fixed/test/t-sqrt_newton.c new file mode 100644 index 0000000000..f02376e201 --- /dev/null +++ b/src/fixed/test/t-sqrt_newton.c @@ -0,0 +1,118 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "mpn_extras.h" +#include "fmpz.h" +#include "fixed.h" + +/* Check fixed_rsqrt_ui_newton, fixed_rsqrt_newton and + fixed_sqrt_newton against basecase references carrying XTRA extra + limbs; the error bounds 4 B^-n / sqrt(a) translate, with + a >= a_{an-1} / B, to 5 B^(XTRA+1) / sqrt(a_{an-1}). */ + +#undef XTRA +#undef check +#define XTRA 8 +#define check newton_sqrt_check + +static int +newton_sqrt_check(nn_srcptr got, slong gn, nn_srcptr ref, const fmpz_t bound) +{ + fmpz_t g, r; + int ok; + fmpz_init(g); + fmpz_init(r); + fmpz_set_ui_array(g, got, gn); + fmpz_mul_2exp(g, g, FLINT_BITS * XTRA); + fmpz_set_ui_array(r, ref, gn + XTRA); + fmpz_sub(g, g, r); + fmpz_abs(g, g); + ok = (fmpz_cmp(g, bound) <= 0); + fmpz_clear(g); + fmpz_clear(r); + return ok; +} + +TEST_FUNCTION_START(fixed_sqrt_newton, state) +{ + slong iter; + + for (iter = 0; iter < 300 * flint_test_multiplier(); iter++) + { + slong n, an; + nn_ptr A, Q, R; + ulong a1; + fmpz_t bound, t; + + if (n_randint(state, 8)) + { + n = 1 + n_randint(state, 50); + an = 1 + n_randint(state, 50); + } + else + { + n = 1 + n_randint(state, 600); + an = 1 + n_randint(state, 600); + } + + A = flint_malloc(an * sizeof(ulong)); + Q = flint_malloc((n + 2) * sizeof(ulong)); + R = flint_malloc((n + XTRA + 2) * sizeof(ulong)); + fmpz_init(bound); + fmpz_init(t); + + flint_mpn_rrandom(A, state, an); + if (n_randint(state, 5) == 0) + A[an - 1] = 1 + n_randint(state, 100); + if (A[an - 1] == 0) + A[an - 1] = 1; + + fmpz_set_ui(t, A[an - 1]); + fmpz_sqrt(t, t); + fmpz_add_ui(t, t, 1); + fmpz_set_ui(bound, 5); + fmpz_mul_2exp(bound, bound, FLINT_BITS * (XTRA + 1)); + fmpz_tdiv_q(bound, bound, t); + fmpz_add_ui(bound, bound, 8); + + fixed_rsqrt_newton(Q, A, an, n); + fixed_rsqrt_newton_basecase(R, A, an, n + XTRA); + + if (!check(Q, n + 2, R, bound)) + TEST_FUNCTION_FAIL("rsqrt: n = %wd, an = %wd\n", n, an); + + fixed_sqrt_newton(Q, A, an, n); + fixed_sqrt_newton_rsqrtmul(R, A, an, n + XTRA); + + fmpz_mul_ui(bound, bound, 2); + if (!check(Q, n + 2, R, bound)) + TEST_FUNCTION_FAIL("sqrt: n = %wd, an = %wd\n", n, an); + + a1 = 2 + n_randint(state, UWORD_MAX - 2); + fmpz_set_ui(bound, 3); + fmpz_mul_2exp(bound, bound, FLINT_BITS * XTRA); + + fixed_rsqrt_ui_newton(Q, a1, n); + fixed_rsqrt_ui_newton_basecase(R, a1, n + XTRA); + + if (!check(Q, n, R, bound)) + TEST_FUNCTION_FAIL("rsqrt_ui: n = %wd, a = %wu\n", n, a1); + + flint_free(A); + flint_free(Q); + flint_free(R); + fmpz_clear(bound); + fmpz_clear(t); + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-tab_bsplit.c b/src/fixed/test/t-tab_bsplit.c new file mode 100644 index 0000000000..14a14d633a --- /dev/null +++ b/src/fixed/test/t-tab_bsplit.c @@ -0,0 +1,72 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "fixed.h" +#include "arb.h" + +TEST_FUNCTION_START(fixed_tab_bsplit, state) +{ + slong iter; + + for (iter = 0; iter < 100 * flint_test_multiplier(); iter++) + { + /* every fourth iteration goes big to reach the direct-log + regime (prec >= 6000, i >= 30) */ + slong n = 1 + n_randint(state, (iter % 4 == 0) ? 160 : 12); + slong i = 1 + n_randint(state, (iter % 3 == 0) ? 700 : 40); + int is_log = iter % 2; + nn_ptr got, ref; + arb_t v, t; + arf_t lb; + fmpz_t f; + slong j; + int ok; + + got = flint_malloc(n * sizeof(ulong)); + ref = flint_malloc(n * sizeof(ulong)); + arb_init(v); arb_init(t); arf_init(lb); fmpz_init(f); + + if (is_log) + fixed_log1p_2mexp_ui_bs(got, (ulong) i, n); + else + fixed_atan_2mexp_ui_bs(got, (ulong) i, n); + + arb_one(v); + arb_mul_2exp_si(v, v, -i); + if (is_log) + arb_log1p(t, v, FLINT_BITS * n + 64); + else + arb_atan(t, v, FLINT_BITS * n + 64); + arb_get_lbound_arf(lb, t, FLINT_BITS * n + 64); + arf_mul_2exp_si(lb, lb, FLINT_BITS * n); + arf_get_fmpz(f, lb, ARF_RND_FLOOR); + fmpz_get_ui_array(ref, n, f); + + /* one-sided: the exact floor or up to 3 ulps below, never + above */ + ok = 0; + for (j = 0; j <= 3 && !ok; j++) + { + ok = (mpn_cmp(got, ref, n) == 0); + mpn_sub_1(ref, ref, n, 1); + } + + if (!ok) + TEST_FUNCTION_FAIL("%s: i = %wd, n = %wd\n", + is_log ? "log1p" : "atan", i, n); + + arb_clear(v); arb_clear(t); arf_clear(lb); fmpz_clear(f); + flint_free(got); flint_free(ref); + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-tab_floor.c b/src/fixed/test/t-tab_floor.c new file mode 100644 index 0000000000..073435e742 --- /dev/null +++ b/src/fixed/test/t-tab_floor.c @@ -0,0 +1,62 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "arb.h" +#include "fixed.h" + +/* every dynamic table entry's value limbs (all limbs above the + guard) must equal the exact floor of the infinite-precision value + at that precision -- hence at every shorter truncation too, floors + being nested; the guard limb itself is one-sided but not exact + (e.g. the multi-summation tier biases it down deliberately) */ + +TEST_FUNCTION_START(fixed_tab_floor, state) +{ + slong iter; + + for (iter = 0; iter < 2 + flint_test_multiplier() / 3; iter++) + { + slong nv = 1 + n_randint(state, 40); + slong rc = 8 + n_randint(state, 120); + slong i, nc; + nn_ptr exact; + + _fixed_exp_logs_ensure(nv, rc); + _fixed_atans_ensure(nv, rc); + + /* compare at the requested width nv through the exported + entry accessors (the thread-local data is not + DLL-exported): exact floors truncate to exact floors, so + this checks the guarantee at every ensure width */ + nc = nv; + exact = flint_malloc(nc * sizeof(ulong)); + + for (i = 0; i <= rc; i++) + { + _fixed_tab_entry_exact(exact, 0, (ulong) i, nc); + if (mpn_cmp(exact, _fixed_exp_logs_entry(i, nc), nc) != 0) + TEST_FUNCTION_FAIL("logs: nv = %wd, rc = %wd, " + "i = %wd\n", nv, rc, i); + } + + for (i = 0; i <= rc; i++) + { + _fixed_tab_entry_exact(exact, 1, (ulong) i, nc); + if (mpn_cmp(exact, _fixed_atans_entry(i, nc), nc) != 0) + TEST_FUNCTION_FAIL("atans: nv = %wd, rc = %wd, " + "i = %wd\n", nv, rc, i); + } + flint_free(exact); + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-tab_static.c b/src/fixed/test/t-tab_static.c new file mode 100644 index 0000000000..b4060a5d85 --- /dev/null +++ b/src/fixed/test/t-tab_static.c @@ -0,0 +1,45 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +#include "test_helpers.h" +#include "fixed.h" + +/* the static tables hold only value limbs: the top + FIXED_STATIC_TAB_N limbs of the dynamic entries built one limb + deeper (whose own bottom limb is the generation guard and is not + stored); enforce that identity bit for bit */ + +TEST_FUNCTION_START(fixed_tab_static, state) +{ + slong i, j; + + /* go through the exported entry accessors: the thread-local + table data itself is not DLL-exported on Windows */ + _fixed_exp_logs_ensure(FIXED_STATIC_TAB_N, FIXED_STATIC_TAB_R); + _fixed_atans_ensure(FIXED_STATIC_TAB_N, FIXED_STATIC_TAB_R); + + for (i = 0; i <= FIXED_STATIC_TAB_R; i++) + { + nn_srcptr el = _fixed_exp_logs_entry(i, FIXED_STATIC_TAB_N); + nn_srcptr ea = _fixed_atans_entry(i, FIXED_STATIC_TAB_N); + for (j = 0; j < FIXED_STATIC_TAB_N; j++) + { + if (_fixed_exp_logs_static[i * FIXED_STATIC_TAB_N + j] + != el[j]) + TEST_FUNCTION_FAIL("logs entry %wd limb %wd\n", i, j); + if (_fixed_atans_static[i * FIXED_STATIC_TAB_N + j] + != ea[j]) + TEST_FUNCTION_FAIL("atans entry %wd limb %wd\n", i, j); + } + } + + TEST_FUNCTION_END(state); +} diff --git a/src/fixed/test/t-tan_bitwise_rs.c b/src/fixed/test/t-tan_bitwise_rs.c index 101ab92812..17a69755aa 100644 --- a/src/fixed/test/t-tan_bitwise_rs.c +++ b/src/fixed/test/t-tan_bitwise_rs.c @@ -23,11 +23,12 @@ TEST_FUNCTION_START(fixed_tan_bitwise_rs, state) /* the specialized sizes take r = 0; larger ones divide sin by cos and so accept any legal r */ slong nmin = (FLINT_BITS == 64) ? 1 : 2; - slong n = nmin + n_randint(state, (iter % 5 == 0) ? 40 : 9); + slong n = nmin + n_randint(state, (iter % 97 == 1) ? 1800 + : (iter % 5 == 0) ? 40 : 9); int r = (iter % 3 == 0) ? 0 : (iter % 3 == 1) ? 32 + (int) n_randint(state, 200) : 32 + (int) n_randint(state, 17); - ulong x[64], res[66]; + nn_ptr x, res; arb_t xa, e, va, d; fmpz_t f; mag_t mb; @@ -37,7 +38,10 @@ TEST_FUNCTION_START(fixed_tan_bitwise_rs, state) if (n > 40) n = 40; - flint_mpn_rrandom(x, state, n); +x = flint_malloc((n + 2) * sizeof(ulong)); + res = flint_malloc((n + 2) * sizeof(ulong)); + + flint_mpn_rrandom(x, state, n); if (iter % 13 == 3) flint_mpn_zero(x, n); if (iter % 7 == 1) @@ -74,6 +78,8 @@ TEST_FUNCTION_START(fixed_tan_bitwise_rs, state) arb_clear(d); fmpz_clear(f); mag_clear(mb); + flint_free(x); + flint_free(res); } TEST_FUNCTION_END(state); diff --git a/src/fixed/trig_opt_1.c b/src/fixed/trig_opt_1.c index 0ef91a6658..b68a2d02f2 100644 --- a/src/fixed/trig_opt_1.c +++ b/src/fixed/trig_opt_1.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #include "tan_rotate.inc" @@ -87,14 +86,14 @@ _fixed_trig_opt_1(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, slong * used; TMP_INIT; - _fixed_atans_ensure(1, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(1, r, &nc); TMP_START; used = TMP_ALLOC(FIXED_BITWISE_REDUCE_USED_ALLOC(r) * sizeof(slong)); t[0] = x[0] >> 1; - num = _fixed_bitwise_reduce(t, 1, r, 1, _fixed_atans, nc, used); + num = _fixed_bitwise_reduce(t, 1, r, 1, tab, nc, used); _fixed_tan_rotate_tab[1](wx, wy, used, num); _fixed_tan_series_1(t, t); /* u = tan(t') */ diff --git a/src/fixed/trig_opt_10.c b/src/fixed/trig_opt_10.c index 3c3f8c1045..5989574d08 100644 --- a/src/fixed/trig_opt_10.c +++ b/src/fixed/trig_opt_10.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_11.c b/src/fixed/trig_opt_11.c index 3ec8183507..6526a7ac79 100644 --- a/src/fixed/trig_opt_11.c +++ b/src/fixed/trig_opt_11.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_12.c b/src/fixed/trig_opt_12.c index a7ccf4fb6c..54f628f335 100644 --- a/src/fixed/trig_opt_12.c +++ b/src/fixed/trig_opt_12.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_2.c b/src/fixed/trig_opt_2.c index 37af228946..0c57c4336d 100644 --- a/src/fixed/trig_opt_2.c +++ b/src/fixed/trig_opt_2.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #include "tan_rotate.inc" @@ -94,15 +93,15 @@ _fixed_trig_opt_2(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, slong * used; TMP_INIT; - _fixed_atans_ensure(2, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(2, r, &nc); TMP_START; used = TMP_ALLOC(FIXED_BITWISE_REDUCE_USED_ALLOC(r) * sizeof(slong)); t[0] = (x[0] >> 1) | (x[1] << (FLINT_BITS - 1)); t[1] = x[1] >> 1; - num = _fixed_bitwise_reduce(t, 2, r, 1, _fixed_atans, nc, used); + num = _fixed_bitwise_reduce(t, 2, r, 1, tab, nc, used); _fixed_tan_rotate_2(wx, wy, used, num); _fixed_tan_series_2(u, t); /* u = tan(t') */ diff --git a/src/fixed/trig_opt_3.c b/src/fixed/trig_opt_3.c index b55b9b98bf..6580e85974 100644 --- a/src/fixed/trig_opt_3.c +++ b/src/fixed/trig_opt_3.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #include "tan_rotate.inc" @@ -103,8 +102,8 @@ _fixed_trig_opt_3(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, slong * used; TMP_INIT; - _fixed_atans_ensure(3, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(3, r, &nc); TMP_START; used = TMP_ALLOC(FIXED_BITWISE_REDUCE_USED_ALLOC(r) * sizeof(slong)); @@ -112,7 +111,7 @@ _fixed_trig_opt_3(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, t[0] = (x[0] >> 1) | (x[1] << (FLINT_BITS - 1)); t[1] = (x[1] >> 1) | (x[2] << (FLINT_BITS - 1)); t[2] = x[2] >> 1; - num = _fixed_bitwise_reduce(t, 3, r, 1, _fixed_atans, nc, used); + num = _fixed_bitwise_reduce(t, 3, r, 1, tab, nc, used); _fixed_tan_rotate_3(wx, wy, used, num); _fixed_tan_series_3(u, t); /* u = tan(t') */ diff --git a/src/fixed/trig_opt_4.c b/src/fixed/trig_opt_4.c index 89a00f9c19..8b5dbffa22 100644 --- a/src/fixed/trig_opt_4.c +++ b/src/fixed/trig_opt_4.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #include "tan_rotate.inc" @@ -108,8 +107,8 @@ _fixed_trig_opt_4(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, slong * used; TMP_INIT; - _fixed_atans_ensure(4, r); - nc = _fixed_atans_n; + nn_srcptr tab; + tab = _fixed_atans_tab(4, r, &nc); TMP_START; used = TMP_ALLOC(FIXED_BITWISE_REDUCE_USED_ALLOC(r) * sizeof(slong)); @@ -118,7 +117,7 @@ _fixed_trig_opt_4(nn_ptr ysin, nn_ptr ycos, nn_ptr ytan, t[1] = (x[1] >> 1) | (x[2] << (FLINT_BITS - 1)); t[2] = (x[2] >> 1) | (x[3] << (FLINT_BITS - 1)); t[3] = x[3] >> 1; - num = _fixed_bitwise_reduce(t, 4, r, 1, _fixed_atans, nc, used); + num = _fixed_bitwise_reduce(t, 4, r, 1, tab, nc, used); _fixed_tan_rotate_4(wx, wy, used, num); _fixed_tan_series_4(u, t); /* u = tan(t') */ diff --git a/src/fixed/trig_opt_5.c b/src/fixed/trig_opt_5.c index 1d4db33503..4648a42669 100644 --- a/src/fixed/trig_opt_5.c +++ b/src/fixed/trig_opt_5.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_6.c b/src/fixed/trig_opt_6.c index 766f79f7a3..f0a88112be 100644 --- a/src/fixed/trig_opt_6.c +++ b/src/fixed/trig_opt_6.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_7.c b/src/fixed/trig_opt_7.c index ef52f8a8ec..0000c9887f 100644 --- a/src/fixed/trig_opt_7.c +++ b/src/fixed/trig_opt_7.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_8.c b/src/fixed/trig_opt_8.c index 111d6866af..657f6a4424 100644 --- a/src/fixed/trig_opt_8.c +++ b/src/fixed/trig_opt_8.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/trig_opt_9.c b/src/fixed/trig_opt_9.c index 329fcf34d6..bceafe0a3b 100644 --- a/src/fixed/trig_opt_9.c +++ b/src/fixed/trig_opt_9.c @@ -17,7 +17,6 @@ #include "longlong.h" #include "mpn_extras.h" #include "fixed.h" -#include "impl.h" #include "hand_mulhi.inc" #if FLINT_BITS == 64 diff --git a/src/fixed/tune/tune-exp-reduced.c b/src/fixed/tune/tune-exp-reduced.c new file mode 100644 index 0000000000..413c29c6ca --- /dev/null +++ b/src/fixed/tune/tune-exp-reduced.c @@ -0,0 +1,116 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +/* Tune the algorithm choice of fixed_exp_reduced over a grid of + (r, wn): time algorithms 1..4 for every pair, print the winner + grid, and suggest the terms-coordinate thresholds + (terms = 64 wn / r) that the automatic selection uses: + FIXED_EXP_BURST_TERMS between sinh and one burst, and + FIXED_EXP_FULLBURST_TERMS between one burst and the full + bit-burst. The sinh/direct threshold is the established + EXP_USE_SINH crossover. + + Usage: tune-exp-reduced [wnmax] */ + +#include +#include "profiler.h" +#include "flint.h" +#include "mpn_extras.h" +#include "fixed.h" + +static double +time_alg(nn_ptr y, nn_srcptr t, slong wn, flint_bitcnt_t r, int alg) +{ + double best = 1e300; + int pass; + for (pass = 0; pass < 3; pass++) + { + timeit_t tm; + slong reps = 1, k; + for (;;) + { + timeit_start(tm); + for (k = 0; k < reps; k++) + fixed_exp_reduced(y, t, wn, r, alg); + timeit_stop(tm); + if (tm->wall >= 20 || reps >= 1000000) + break; + reps *= 4; + } + best = FLINT_MIN(best, tm->wall * 1e-3 / reps); + } + return best; +} + +int main(int argc, char ** argv) +{ + slong wnmax = (argc > 1) ? atol(argv[1]) : 8192; + slong wns[64], nw = 0, i, j; + flint_bitcnt_t rs[] = {16, 32, 64, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096}; + slong nr = sizeof(rs) / sizeof(rs[0]); + flint_rand_t state; + + flint_rand_init(state); + + for (i = 16; i <= wnmax; i *= 2) + wns[nw++] = i; + + flint_printf("winner (alg 1 direct / 2 sinh / 3 burst+sinh / " + "4 full burst); terms = 64 wn / r\n\n"); + flint_printf(" wn:"); + for (j = 0; j < nw; j++) + flint_printf("%7wd", wns[j]); + flint_printf("\n"); + + for (i = 0; i < nr; i++) + { + flint_printf("r=%6wd:", (slong) rs[i]); + for (j = 0; j < nw; j++) + { + slong wn = wns[j]; + nn_ptr t, y; + double ta[5]; + int alg, bestalg = 1; + slong k; + + if (FLINT_BITS * (ulong) wn <= rs[i]) + { + flint_printf(" -"); + continue; + } + + t = flint_malloc((wn + 2) * sizeof(ulong)); + y = flint_malloc((wn + 2) * sizeof(ulong)); + flint_mpn_urandomb(t, state, FLINT_BITS * wn); + for (k = 0; k < (slong) (rs[i] / FLINT_BITS) && k < wn; k++) + t[wn - 1 - k] = 0; + if ((rs[i] % FLINT_BITS) + && (slong) (rs[i] / FLINT_BITS) < wn) + t[wn - 1 - rs[i] / FLINT_BITS] >>= rs[i] % FLINT_BITS; + + for (alg = 1; alg <= 4; alg++) + { + ta[alg] = time_alg(y, t, wn, rs[i], alg); + if (ta[alg] < ta[bestalg]) + bestalg = alg; + } + flint_printf(" %d", bestalg); + flint_free(t); flint_free(y); + } + flint_printf("\n"); + } + + flint_printf("\npaste thresholds after inspecting the grid:\n" + " -D FIXED_EXP_BURST_TERMS=... (alg 2 -> 3 boundary)\n" + " -D FIXED_EXP_FULLBURST_TERMS=... (alg 3 -> 4 boundary)\n"); + return 0; +} diff --git a/src/fixed/tune/tune-r-amortized.c b/src/fixed/tune/tune-r-amortized.c new file mode 100644 index 0000000000..2c396d9501 --- /dev/null +++ b/src/fixed/tune/tune-r-amortized.c @@ -0,0 +1,268 @@ +/* + Copyright (C) 2026 Fredrik Johansson + + This file is part of FLINT. + + FLINT is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. See . +*/ + +/* Choose the reduction parameter r as a function of the number of + intended evaluations, at a fixed precision. + + The bitwise functions amortize an r-dependent precomputation: the + angle/log tables hold r + 1 entries of n + 1 limbs, and building + them costs the more the larger r is, while each evaluation costs + the less (fewer series terms). tune-bitwise-r picks the r that + minimizes the pure evaluation time -- the right choice when the + tables are reused many times. For a caller who will evaluate the + function N times at precision n, the right objective is instead + + total(r) = P(r) + N E(r), + + with P the precomputation and E the per-evaluation time. This + program measures P(r) and E(r) for every ladder parameter + r in {64, 128, ..., rmax} at the given n (resetting the cached + tables between candidates so that P is measured cold), discards + dominated candidates, and prints the lower envelope of the lines + total_r(N): the breakpoints N* = (P2 - P1) / (E1 - E2) at which + the next larger r starts paying for its precomputation, both as a + human-readable table and as a paste-able C initializer + + static const struct { double n_evals; int r; } ...[] = + { { 1.0, 64 }, { 12.3, 192 }, ... }; + + where each entry's r applies from n_evals evaluations upward. + + Usage: tune-r-amortized [rmax] + (rmax defaults to 768 and is clamped to FLINT_BITS n - 16). */ + +#include +#include +#include "profiler.h" +#include "flint.h" +#include "mpn_extras.h" +#include "fixed.h" + +void _fixed_exp_logs_ensure(slong nv, slong rc); +void _fixed_atans_ensure(slong nv, slong rc); + +typedef enum { F_EXP, F_LOG1P, F_ATAN, F_SINCOS, F_TAN } which_t; + +static void +tables_reset(which_t w) +{ + if (w == F_EXP || w == F_LOG1P) + _fixed_exp_logs_clear(); + else + _fixed_atans_clear(); +} + +static void +eval_once(which_t w, nn_ptr y, nn_ptr y2, nn_srcptr x, slong n, int r) +{ + switch (w) + { + case F_EXP: fixed_exp_bitwise_rs(y, x, n, r); break; + case F_LOG1P: fixed_log1p_bitwise_rs(y, x, n, r); break; + case F_ATAN: fixed_atan_bitwise_rs(y, x, n, r); break; + case F_SINCOS: fixed_sin_cos_bitwise_rs(y, y2, x, n, r); break; + case F_TAN: fixed_tan_bitwise_rs(y, x, n, r); break; + } +} + +/* seconds for one precomputation at (n, r), tables reset first */ +static double +time_precomp(which_t w, slong n, int r) +{ + double best = 1e300; + int pass, passes = (n <= 2000) ? 2 : 1; + for (pass = 0; pass < passes; pass++) + { + timeit_t tm; + tables_reset(w); + timeit_start(tm); + if (w == F_EXP || w == F_LOG1P) + _fixed_exp_logs_ensure(n, r); + else + _fixed_atans_ensure(n, r); + timeit_stop(tm); + best = FLINT_MIN(best, tm->wall * 1e-3); + } + return best; +} + +/* seconds per evaluation, tables assumed hot for (n, r) */ +static double +time_eval(which_t w, nn_ptr y, nn_ptr y2, nn_srcptr x, slong n, int r) +{ + double best = 1e300; + int pass; + + eval_once(w, y, y2, x, n, r); /* safety: tables surely hot */ + + for (pass = 0; pass < 3; pass++) + { + timeit_t tm; + slong reps = 1, k; + double dt; + + for (;;) + { + timeit_start(tm); + for (k = 0; k < reps; k++) + eval_once(w, y, y2, x, n, r); + timeit_stop(tm); + if (tm->wall >= 50 || reps >= 1000000) + break; + reps *= 4; + } + dt = tm->wall * 1e-3 / reps; + best = FLINT_MIN(best, dt); + } + return best; +} + +int main(int argc, char ** argv) +{ + which_t w; + const char * names[] = {"exp", "log1p", "atan", "sincos", "tan"}; + slong n, rmax = 768, nr = 0, i, j; + int rs[64]; + double P[64], E[64]; + int keep[64]; + nn_ptr x, y, y2; + flint_rand_t state; + + if (argc < 3) + { + flint_printf("usage: tune-r-amortized " + " [rmax]\n"); + return 1; + } + for (i = 0; i < 5; i++) + if (!strcmp(argv[1], names[i])) + break; + if (i == 5) + { + flint_printf("unknown function %s\n", argv[1]); + return 1; + } + w = (which_t) i; + n = atol(argv[2]); + if (argc > 3) + rmax = atol(argv[3]); + rmax = FLINT_MIN(rmax, FLINT_BITS * n - 16); + + flint_rand_init(state); + x = flint_malloc(n * sizeof(ulong)); + y = flint_malloc((n + 2) * sizeof(ulong)); + y2 = flint_malloc((n + 2) * sizeof(ulong)); + flint_mpn_urandomb(x, state, FLINT_BITS * n); + x[n - 1] |= (UWORD(1) << (FLINT_BITS - 2)); + x[n - 1] &= ~(UWORD(1) << (FLINT_BITS - 1)); + + flint_printf("fixed_%s_bitwise_rs, n = %wd\n\n", names[w], n); + flint_printf(" r precomp(us) eval(us)\n"); + + for (i = 0; 64 * (i + 1) <= rmax; i++) + { + rs[nr] = 64 * (int) (i + 1); + P[nr] = time_precomp(w, n, rs[nr]); + E[nr] = time_eval(w, y, y2, x, n, rs[nr]); + flint_printf("%5d %14.0f %13.1f\n", + rs[nr], P[nr] * 1e6, E[nr] * 1e6); + nr++; + } + + /* drop dominated candidates (both P and E no better than some + other candidate's) */ + for (i = 0; i < nr; i++) + { + keep[i] = 1; + for (j = 0; j < nr; j++) + if (j != i && P[j] <= P[i] && E[j] <= E[i] + && (P[j] < P[i] || E[j] < E[i])) + keep[i] = 0; + } + + /* lower envelope of total_r(N) = P + N E over N >= 1, walking + from the smallest-total-at-N=1 candidate toward smaller E */ + { + double breaks[64]; + int er[64]; + slong ne = 0; + double Ncur = 1.0; + int cur = -1; + + /* best candidate at N = 1 */ + { + double bestv = 1e300; + for (i = 0; i < nr; i++) + if (keep[i] && P[i] + E[i] < bestv) + { + bestv = P[i] + E[i]; + cur = (int) i; + } + } + breaks[ne] = 1.0; + er[ne] = rs[cur]; + ne++; + + /* walk the envelope: from the current line, the earliest + crossing against a strictly smaller-E candidate is the + next envelope member */ + while (1) + { + double bestN = 1e300; + int nexti = -1; + + for (i = 0; i < nr; i++) + if (keep[i] && E[i] < E[cur]) + { + double Nc = (P[i] - P[cur]) / (E[cur] - E[i]); + if (Nc >= Ncur && Nc < bestN) + { + bestN = Nc; + nexti = (int) i; + } + } + if (nexti < 0) + break; + cur = nexti; + Ncur = bestN; + breaks[ne] = Ncur; + er[ne] = rs[cur]; + ne++; + } + + flint_printf("\noptimal r by evaluation count:\n"); + for (i = 0; i < ne; i++) + { + if (i + 1 < ne) + flint_printf(" %.4g <= N < %.4g evals: r = %d\n", + breaks[i], breaks[i + 1], er[i]); + else + flint_printf(" N >= %.4g evals: r = %d\n", + breaks[i], er[i]); + } + + flint_printf("\n/* fixed_%s_bitwise_rs, n = %wd: smallest " + "total-time r for N\n evaluations (P + N E, measured); " + "each entry applies from\n n_evals upward */\n", + names[w], n); + flint_printf("static const struct { double n_evals; int r; }\n" + " %s_r_amortized_%wd[] = {\n ", names[w], n); + for (i = 0; i < ne; i++) + flint_printf("{ %.4g, %d }, ", breaks[i], er[i]); + flint_printf("\n};\n"); + } + + flint_free(x); + flint_free(y); + flint_free(y2); + flint_rand_clear(state); + return 0; +}