FLINT3.7.0-dev
Algorithm

Elementary functions in double-word to quad-double precision

Cost
A constant number of NN-term expansion operations per function value: one or two table lookups, a polynomial of degree 3 to 15 with tapered precision, and a few full-precision products.

The dfloat module evaluates exp⁡\exp, expm1⁡\operatorname{expm1}, log⁡\log, log1p⁡\operatorname{log1p}, sin⁡\sin, cos⁡\cos and atan⁡\operatorname{atan} on expansions of N=1,…,4N = 1, \ldots, 4 doubles (see Double-word and multi-double arithmetic) as pure floating-point kernels with static error bounds: each kernel is a fixed sequence of expansion operations whose total error has been bounded once and for all, over the whole argument range, by a model of every operation in dev/dfloat_exp_bound.py. The ball versions then add the static bound to the radius instead of tracking the rounding errors operation by operation, and propagate the input radius with a bound on the derivative. The other functions (tan, sinh, cosh, tanh, asin, acos, atan2, pow, and the complex functions) are compositions of these kernels with the arithmetic.

The design follows the classical table-driven methods for double precision [Tan1989; Mul2016], adapted to expansions in three ways:

  • Exact argument reduction. The reductions are arranged so that the cancelling operations on the head are exact (by Sterbenz's lemma and ulp counting), and the reduced argument keeps relative accuracy.
  • Tapered precision. In Horner's rule for a polynomial in a small argument ww, the inner coefficients are multiplied by powers of ww, so their errors are scaled down; the inner steps are therefore done with fewer components (for example the schedule [4,3,3,2,1,1][4, 3, 3, 2, 1, 1] for the exponential at N=4N = 4). The schedules are chosen by the bound script.
  • Several chains. A polynomial is split into its even and odd parts, and these again in even and odd parts, so that four independent chains in w2w^2 (or w4w^4) run in the four lanes of a SIMD register; the final combination is a single five-term sum. This shortens the dependent chain of expansion operations, which determines the latency of a scalar call. The vector functions (_dN_vec_exp, ...) instead use the lanes for four different arguments.

Since tapered chains make the degree of a polynomial almost irrelevant to the latency, what matters is the number of full-precision operations, which a table lookup keeps at one or two. For N=1N = 1 the kernels are ordinary double code (one table level, polynomials by FMA) and about as fast as the C library.

Exponential#

Let x=x0+x1+⋯x = x_0 + x_1 + \cdots be canonicalised. With k=round⁡(x0/log⁡2)k = \operatorname{round}(x_0/\log 2), L0L_0 the 41-bit truncation of log⁡2\log 2 and L1,L2,…L_1, L_2, \ldots the following terms of its expansion, the head is reduced to h=x0−kL0h = x_0 - kL_0, then to s0=h−i/256−j/65536s_0 = h - i/256 - j/65536 with ∣i∣≤89|i| \le 89, ∣j∣≤128|j| \le 128 and ∣s0∣≤2−17|s_0| \le 2^{-17}.

Lemma 1. For ∣x0∣≤746|x_0| \le 746, the three subtractions computing hh, d=h−i/256d = h - i/256 and s0=d−j/65536s_0 = d - j/65536 are exact.

Proof. kL0kL_0 is exact since L0L_0 has 41 bits and ∣k∣≤1078<211|k| \le 1078 < 2^{11}. For ∣k∣≥2|k| \ge 2, x0x_0 and kL0kL_0 are within a factor 2 of each other (∣x0−klog⁡2∣≤0.35|x_0 - k\log 2| \le 0.35 and ∣k∣log⁡2≥1.38|k| \log 2 \ge 1.38), so the difference is exact by Sterbenz's lemma; for ∣k∣=1|k| = 1 both are multiples of 2−542^{-54} and their difference is below 1/21/2, hence fits in 53 bits; for k=0k = 0 there is nothing to subtract. Similarly, dd and s0s_0 are differences of multiples of ulp⁡(h)\operatorname{ulp}(h) and ulp⁡(d)\operatorname{ulp}(d) of magnitude at most 1/5121/512 and 2−172^{-17}, which is at most 2532^{53} ulps when ∣h∣≥2−10|h| \ge 2^{-10} and ∣d∣≥2−18|d| \ge 2^{-18}, while i=0i = 0 and j=0j = 0 otherwise.

The reduced argument s=s0+(x1+⋯+xN−1)−k(L1+⋯+LN)s = s_0 + (x_1 + \cdots + x_{N-1}) - k(L_1 + \cdots + L_N) is assembled with the expansion kernels in an order that keeps every term significant. With T=ei/256ej/65536T = e^{i/256} e^{j/65536} (the product of two table entries, 179 and 257 entries of 5-term expansions) and w=s2w = s^2,

ex=2k(T+(Ts)E(w)+(Tw)O(w)),E(w)=∑jwj(2j+1)!,O(w)=∑jwj(2j+2)!,e^x = 2^k\bigl(T + (Ts) E(w) + (Tw) O(w)\bigr), \quad E(w) = \sum_j \frac{w^j}{(2j+1)!},\quad O(w) = \sum_j \frac{w^j}{(2j+2)!},

where EE and OO are the even and odd parts of the Taylor polynomial of expm1⁡\operatorname{expm1}, of degree 3, 6, 8 and 11 for N=1,…,4N = 1, \ldots, 4. The scalar kernel splits EE and OO once more in v=w2v = w^2, so that the four chains E0,E1,O0,O1E_0, E_1, O_0, O_1 run in parallel and (Ts)E0+(Tsw)E1+(Tw)O0+(Tw2)O1(Ts)E_0 + (Tsw)E_1 + (Tw)O_0 + (Tw^2)O_1 is one lane-wise product and one five-term sum. The result has relative error at most DFLOAT_EXP_EPS_N (about 2−512^{-51}, 2−100.22^{-100.2}, 2−1512^{-151} and 2−201.62^{-201.6}) for −746≤x0≤709.9-746 \le x_0 \le 709.9.

For expm1 with ∣x∣≤0.34|x| \le 0.34 (where k=0k = 0), the constant term TT is replaced by T−1=E1+T1E2T - 1 = E_1 + T_1 E_2 from tables of ei/256−1e^{i/256} - 1 and ej/65536−1e^{j/65536} - 1, so that the result is relative when T−1T - 1 is small or zero (T−1=0T - 1 = 0 and s=xs = x exactly for ∣x∣<2−17|x| < 2^{-17}); beyond 0.34 it is ex−1e^x - 1, which loses nothing since ∣ex−1∣≥0.29|e^x - 1| \ge 0.29 there.

Sine and cosine#

With k=round⁡(2x0/π)k = \operatorname{round}(2x_0/\pi) and P0P_0 the 33-bit truncation of π/2\pi/2, the head is reduced to h=x0−kP0h = x_0 - kP_0; this is exact for ∣k∣≤220|k| \le 2^{20} by the argument of Lemma 1, since kP0kP_0 then has at most 53 bits. The reduced argument t=h+(x1+⋯ )−k(P1+⋯+PN)t = h + (x_1 + \cdots) - k(P_1 + \cdots + P_N) is assembled and canonicalised (so that its tail is bounded by u∣t∣u|t| rather than u∣x∣u|x|, which matters for large xx) and split as t=i/64+j/213+st = i/64 + j/2^{13} + s with ∣s∣≤2−14|s| \le 2^{-14}. With A=i/64+j/213A = i/64 + j/2^{13}, SA=sin⁡AS_A = \sin A and CA=cos⁡AC_A = \cos A from the products of two table levels (51 and 65 entries, using the sign symmetry) and w=s2w = s^2,

sin⁡t=SA+(SAw)Q(w)+(CAs)P(w),cos⁡t=CA+(CAw)Q(w)−(SAs)P(w),\sin t = S_A + (S_A w) Q(w) + (C_A s) P(w), \qquad \cos t = C_A + (C_A w) Q(w) - (S_A s) P(w),

where sin⁡s=sP(s2)\sin s = sP(s^2) and cos⁡s=1+s2Q(s2)\cos s = 1 + s^2 Q(s^2) are Taylor polynomials. The results for xx are ±sin⁡t\pm\sin t and ±cos⁡t\pm\cos t according to k mod 4k \bmod 4. As for the exponential, the four chains P0,P1,Q0,Q1P_0, P_1, Q_0, Q_1 in w2w^2 run in the SIMD lanes, as do the table products and the multipliers.

Since the argument is reduced with π/2\pi/2 to about 53N+4853N + 48 bits and the reduced argument has the absolute accuracy uN∣x∣u^N|x| of the input, the error bounds DFLOAT_TRIG_EPS_N are absolute and scaled by ∣x∣|x|: ε(∣x∣+min⁡(∣x∣,1))\varepsilon(|x| + \min(|x|, 1)) for the sine and ε(∣x∣+1)\varepsilon(|x| + 1) for the cosine. Relative accuracy near the zeros at multiples of π/2\pi/2 (other than 0) is not attempted. Arguments with ∣x∣>220π/2−1|x| > 2^{20}\pi/2 - 1, where kP0kP_0 would no longer be exact, are evaluated through arb_t.

Logarithm#

Write x=2emx = 2^e m with m∈[M0,2M0)m \in [M_0, 2M_0), M0=0.70703125M_0 = 0.70703125, the exponent and table index read from the bits of the head. The reduction uses two tabulated reciprocals: r≈1/mr \approx 1/m from 128 entries indexed by the leading bits of mm, giving ∣mr−1∣≤2−7|mr - 1| \le 2^{-7} (with r=1r = 1 on the two intervals adjacent to 1, which keeps log⁡x\log x relative near x=1x = 1), and r′≈1/(1+j/213)r' \approx 1/(1 + j/2^{13}) with j=round⁡(213(mr−1))j = \operatorname{round}(2^{13}(mr - 1)), giving z=m(rr′)−1z = m(rr') - 1 with ∣z∣≤2−14(1+2−7)|z| \le 2^{-14}(1 + 2^{-7}). The product rr′rr' is exact as a double-word Rh+RlR_h + R_l, and z=(h0−1)+l0+(m1+⋯ )Rh+mRlz = (h_0 - 1) + l_0 + (m_1 + \cdots)R_h + mR_l with m0Rh=h0+l0m_0 R_h = h_0 + l_0 is computed with the head cancelled exactly, so that its error is uN∣z∣u^N|z| rather than uNu^N; no division is needed. The tables hold L=−log⁡rL = -\log r for the rounded entries, so that

log⁡x=elog⁡2+L+L′+log1p⁡(z)\log x = e\log 2 + L + L' + \operatorname{log1p}(z)

exactly. With w=z2w = z^2, log1p⁡(z)=z+wE(w)+zwO(w)\operatorname{log1p}(z) = z + wE(w) + zwO(w) with E(w)=−1/2−w/4−⋯E(w) = -1/2 - w/4 - \cdots and O(w)=1/3+w/5+⋯O(w) = 1/3 + w/5 + \cdots (degree 8, 8, 12 and 15 in zz), evaluated as four chains in w2w^2. The relative error bound DFLOAT_LOG_EPS_N holds for 2−1022≤x<210222^{-1022} \le x < 2^{1022}.

For log1p with ∣x∣≤1/2|x| \le 1/2, the argument 1+x1 + x is carried exactly into the reduction: the head RN⁡(1+x0)\operatorname{RN}(1 + x_0) with its rounding error and the tail of xx form an (N+1)(N + 1)-term argument, whose reduction is computed to uN∣z∣u^N|z| as above. For ∣x∣>1/2|x| > 1/2, 1+x1 + x is rounded to NN terms, and ∣log⁡(1+x)∣≥0.4|\log(1 + x)| \ge 0.4 absorbs the rounding.

A reduction by division, z=(m−c)/(m+c)z = (m - c)/(m + c) with log⁡m=log⁡c+2atanh⁡z\log m = \log c + 2\operatorname{atanh} z, would halve the number of coefficients by using only odd powers; but the even and odd parts of the polynomial already run in parallel lanes, so nothing would be gained for the price of a division, which costs much more than a reduction level by multiplication.

Arctangent#

For x≥0x \ge 0 (odd symmetry) and c=i/64c = i/64: if x≤1x \le 1, with i=round⁡(64x)i = \operatorname{round}(64x), atan⁡x=atan⁡c+atan⁡z\operatorname{atan} x = \operatorname{atan} c + \operatorname{atan} z, z=(x−c)/(1+xc)z = (x - c)/(1 + xc); if x>1x > 1, with i=round⁡(64/x)i = \operatorname{round}(64/x), atan⁡x=(π/2−atan⁡c)−atan⁡z\operatorname{atan} x = (\pi/2 - \operatorname{atan} c) - \operatorname{atan} z, z=(1−cx)/(x+c)z = (1 - cx)/(x + c). In both cases ∣z∣≤2−7|z| \le 2^{-7}, the head of the numerator is exact (Sterbenz), and the division is the long division kernel; for x≤1/128x \le 1/128 no division is done. With w=z2w = z^2, atan⁡z=z+zwE(w)\operatorname{atan} z = z + zwE(w), E(w)=−1/3+w/5−⋯E(w) = -1/3 + w/5 - \cdots with 3, 7, 11 and 14 coefficients, evaluated as four chains in w4w^4. One division is unavoidable here; a second reduction level would save only two tapered steps at the cost of a second division. The relative error bound DFLOAT_ATAN_EPS_N holds for all finite xx.

Balls#

For a ball [m±r][m \pm r] and the kernel value yy at the midpoint with static relative bound ε\varepsilon:

  • exp⁡\exp: radius ∣y∣(1+2ε)((er−1)+2ε)|y|(1 + 2\varepsilon)\bigl((e^r - 1) + 2\varepsilon\bigr), since ∣ex′−em∣≤em(er−1)|e^{x'} - e^m| \le e^m(e^r - 1) for ∣x′−m∣≤r|x' - m| \le r and em≤∣y∣/(1−ε)e^m \le |y|/(1 - \varepsilon); er−1e^r - 1 is bounded by r+r2r + r^2 for r≤1r \le 1 and by a power of two otherwise.
  • log⁡\log: radius ε∣y∣+r/(m−r)\varepsilon|y| + r/(m - r), the derivative 1/t1/t being largest at the left end of the ball (a lower bound for m−rm - r is computed from the head, the tail and the radius).
  • atan⁡\operatorname{atan}: radius ε∣y∣+r\varepsilon|y| + r (the derivative is at most 1).
  • sin⁡\sin, cos⁡\cos: radius ε(∣m∣+a)+r\varepsilon(|m| + a) + r with a=min⁡(∣m∣,1)a = \min(|m|, 1) for the sine and a=1a = 1 for the cosine; a radius of 1 or more is replaced by the enclosure [0±1][0 \pm 1].

All radii are rounded up with the slack factor of Radius arithmetic and bumped by 2−10692^{-1069} when tiny. Arguments outside the kernel ranges, infinite radii and nonfinite midpoints are handled through arb_t, so the ball functions are rigorous for every input.

Use in FLINT#

The kernels are dN_exp, dN_expm1, dN_sin, dN_cos, dN_sin_cos, dN_log, dN_log1p and dN_atan for N=1,…,4N = 1, \ldots, 4, their ball versions dNb_exp, etc., and the vector versions _dN_vec_exp, _dN_vec_sin_cos, _dN_vec_log, _dN_vec_atan, which run the same kernels on four arguments at a time (the static bounds apply to every element). The tables are in src/dfloat/exp_tables.c, generated by dev/gen_dfloat_exp_tables.py. The vector functions are used by gr_powsum_sieved for the prime terms of the Riemann–Siegel main sum (Sieved power sums with dfloat balls).

void d2_exp(d2_t res, d2_srcptr x)
void d2_sin_cos(d2_t sn, d2_t cs, d2_srcptr x)
void d2_log(d2_t res, d2_srcptr x)
void d2_atan(d2_t res, d2_srcptr x)
void d2b_exp(d2b_t res, d2b_srcptr x)
void _d2_vec_exp(d2_ptr res, d2_srcptr x, slong len)

Referenced from#

References#

Cite this page

The FLINT team, “Elementary functions in double-word to quad-double precision,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/floating-point/dfloat-elementary.html (generated 2026-11-02).