Correct rounding¶
Float transcendental functions and constants make the same promise as +
and sqrt: the exact value rounded once, in every rounding mode. Their Ball
counterparts enclose the function's value at every point of the input. Both
use the same point kernels; one rounding driver turns their certified bounds
into Float results. The algorithms and proofs below explain why this works.
Three layers¶
Every function f is built in three layers:
- Point kernel.
_kernel_f(x, w)returns a ball containingf(x)at an exact argumentx, with at leastw - c_fbits of relative accuracy wheneverf(x) != 0. Every kernel here hasc_f <= 1; zeros offare exact cases and never reach a kernel. - Ball function.
apn_mojo.ball.fevaluates the kernel at the ends of its input ball (the endpoint method, for monotone functions) or at its midpoint, widened by a bound of the derivative (sinandcos). - Correctly rounded function.
apn_mojo.float.fruns the Ziv driver over the kernel at the exact argument, never over the ball function, which would add the radius growth of a whole ball.
A few kernels compose others through ball arithmetic: pow is
exp(y log x), tan divides the balls of sin and cos, and the inverse
hyperbolic functions take logarithms of exact expressions. Ball arithmetic is
rigorous, so the compositions are too.
Fixed-point kernels¶
Kernels compute in fixed point. _Fix represents the interval
(mid +/- err) * 2**-scale, with an Integer midpoint and an error counted in
units of the last place. The error uses Ball's 30-bit radius type. Each
operation rounds its midpoint once and adds the rounding error to the
propagated input errors:
| Operation | Error bound in units |
|---|---|
x + y, x - y |
e_x + e_y |
x * y |
(abs(X) e_y + abs(Y) e_x + e_x e_y) / 2**scale + 2: the product keeps only the 64-bit digit diagonals from two below the scale up (Integer._high_product), at most one unit below the floor |
x / y |
(abs(X) e_y + abs(Y) e_x) 2**scale / (abs(Y) (abs(Y) - e_y)) + 1, infinite unless abs(Y) > e_y |
sqrt(x) |
e 2**scale / sqrt(X) + 1 in scaled integers, at most twice the bound e 2**scale / (sqrt(X - e) + sqrt(X)) and one integer square root cheaper; infinite unless X > e |
x * k, x / k for an integer k |
e abs(k), e / abs(k) + 1, the quotient truncated in the midpoint's own words |
These bounds account for arithmetic error, leaving the kernel to bound the
tails of truncated series. Reducing arguments to magnitudes near 1 keeps
midpoints at about scale bits. For a small result, such as sin near zero
or log near 1, the kernel adjusts the scale by the result's binary exponent
to preserve relative accuracy.
The series cores are written once, over the _FixedPoint trait, and run on
one of two representations with the same bounds. _Fix has an Integer
midpoint, at any scale. _Wide[n] keeps the midpoint in n native words, two's
complement, and the error as a count of units in one word. The count saturates
at 2**64 - 1, which stands for an unbounded error, so no bound is lost. A core
runs on _Wide[4] while its working scale stays below 256 bits, on _Wide[8]
below 512, and on _Fix above that or when its argument's error reaches
2**32 units. On _Wide, a product is a word-by-word multiplication with no
allocation, and its error bound takes a few word operations. The few divisions
and square roots of a core go through _Fix.
Each core sums its series with _sum_series. It bounds the terms c_k z**k
from a bound of abs(z) in radius arithmetic, and takes terms up to the first
whose bound is below half a unit. Twice that bound covers the tail, since each
later term is at most half the one before; the function checks this
condition. The sum runs by rectangular splitting (Smith). It forms the powers
z**i for i <= m, with m = ceil(sqrt(N)), then applies Horner's rule in
z**m over blocks of m terms. N terms cost about 2 sqrt(N) full
products, and each term one division by a small integer and one sum. Since
terms are cheap, the cores reduce their arguments less than a term-by-term sum
would. At 1024 bits (scale 1105), exp squares 22 times instead of 33, sin
and cos double 8 times instead of 16, and atan and log take 6 halvings or
square roots instead of 16 and 11.
| Function | Reduction | Core |
|---|---|---|
exp, exp2 |
x = k ln 2 + r (exp2: x = k + f, r = f ln 2); below abs(x) = 2**40, k from x's top 64 bits over ln 2's, within 2**-20 of x / ln 2 |
Taylor series of exp(r / 2**s), s = 2 isqrt(scale) / 3, then s squarings |
expm1 |
below abs(x) = 1/2: none |
y (1 + y/2! + y**2/3! + ...) for y = x / 2**s, s <= isqrt(scale) / 3, then expm1(2a) = expm1(a)(expm1(a) + 2), which keeps relative accuracy |
log |
x = 2**k m, m in [1/sqrt 2, sqrt 2) |
r <= isqrt(scale) / 5 square roots of m, then log m_r = 2 atanh((m_r - 1)/(m_r + 1)) |
sin, cos |
x = k pi/2 + r, quadrant k mod 4, with more bits of pi while r lacks accuracy |
series of sin(r / 2**s), s <= isqrt(scale) / 4 + E(r) + 2, cos = sqrt(1 - sin**2), then sin 2a = 2 sin a cos a, cos 2a = 1 - 2 sin**2 a |
atan |
atan x = pi/2 - atan(1/x) above 1 |
s <= isqrt(scale) / 5 + E(y) + 2 halvings atan y = 2 atan(y / (1 + sqrt(1 + y**2))), then the alternating series |
The general kernels support every precision, including the _Fix arguments
used by asin, acos, atan2, log1p, and inverse hyperbolic functions.
For Float inputs below 4608 bits, exp, expm1, log, atan, sin, and
cos first try the medium-precision kernels.
Medium-precision kernels¶
ball/_medium.mojo implements the method of F. Johansson, "Efficient
implementation of elementary functions in the medium-precision range"
(ARITH 22, 2015), up to 4608 bits:
- Fixed point on limbs. Values are fractions of
wnwhole 64-bit limbs in one stack block, at the target precision plus 4 to 8 bits (plus the argument's-E(x)where a small result needs relative accuracy). The limb operations are Integer's word-span kernels (integer/_limbs.mojo). - Reduction against stored constants.
expreduces modulo ln 2 andsin/cosmodulo pi/4 by one long division of the argument's limbs by a 4608-bit table of the constant, within 2 or 3 units;logreads2m - 1forx = m 2**e;atantakes1/xabove 1. - Table steps. Up to 512 bits one table step on the top 8 bits (7 for
log), up to 4608 bits two steps on 5 bits each.exp,sinandcossubtract the index bits and multiply by, or combine with,exp(i/2**b)/2,sin(i/2**b)andcos(i/2**b);atantakesatan w = atan(p/q) + atan((q w - p)/(q + p w)), one division;logdivides by the wordp + q, then fuses the second step withlog(1 + v) = 2 atanh(v/(2 + v)), one division. The remainder is below2**-8or2**-10. - Taylor sums. By rectangular splitting (Paterson and Stockmeyer, 1973;
Smith, 1989) with the coefficients
1/k!and1/(2k+1)as integers over denominators shared by groups of terms: a term is one word multiply-add, a word division falls between groups, and the sum is within 2 units (3 for the two-termatansum). One evaluator,_series_sum, takes the exponential's, the sine's and cosine's (over powers ofx**2, alternating) and the arctangent's sums. Longexpandsinsums give way tocosh = sqrt(1 + sinh**2)andcos = sqrt(1 - sin**2). - One error count. The error is a count of units in the last limb,
raised by the bound each step adds, derived at that step: 3 times the
reduction's for
exp(sinceexp' < 3), 4 or 6 for a table product, 1 per table value added,2 e_left + 2 e_right + 3for thesin/cosaddition formulas, plus the series' truncation. The midpoint is rounded once to the precision, and the count, scaled, is the radius.
The midpoint is rounded once, to nearest at the target precision, straight
from the limbs: the top bits, then the half
bit and the sticky bits below; the rounding goes into the radius. expm1
subtracts the one on the limbs before that rounding. Products of up to 16
limbs take rows of word multiply-adds (_multiply_limbs), unrolled for equal
sizes up to 4 limbs, since the word kernels' dispatch costs more than such a
product.
A ball function of a narrow ball takes the same kernels at the ball's own
precision (_medium_ball): the
kernel at the midpoint, rounded once, widened by the function's derivative
over the radius r, in radius arithmetic: e**m r (1 + r) for exp and
expm1, (r/m)(1 + 2**-16) for log, r/(1 + d**2) with
d = |m|(1 - 2**-17) for atan, and, inside the kernel, by Taylor's
theorem, min(r, A r + r**2/2) for sin and cos, with A
above the other function's absolute value from that value's top limb and
the error count. sin and cos round only the result asked for; tan
takes both at 4 more bits and divides the two balls, which is indeterminate
when the cosine ball reaches 0. A ball is narrow when its radius
is below 2**-17 of its midpoint; wider balls, exact points such as 0, and
what a kernel declines take the endpoint and midpoint methods above.
The general kernels handle tiny and huge arguments, |x| = 1 in atan,
and x within 2**-(prec/2) of 1 in log. They also take over when a sine
or cosine of an x above 1 falls below 2**-10. In that case, reduction
preserves absolute accuracy, but correct rounding needs relative accuracy.
Tables¶
scripts/generate_function_tables.py writes ball/_tables.mojo: the
function tables (exp(i/2**b)/2, log(1 + i/2**b), atan(i/2**b), sin and
cos(i/2**b), at 512 and 4608 bits), ln 2, pi/4 and pi/2 - 1 at 4608 bits,
and the coefficient tables. Each entry is floor(f 2**(64 L)), kept only
when every point of an interval enclosure (mpmath's interval arithmetic,
a development tool, with outward rounding) has the same floor. mpmath has no
interval atan, so the script halves the argument with
atan x = 2 atan(x / (1 + sqrt(1 + x**2))) to below 2**-16 and sums the
alternating series, whose next term bounds the rest. Generated this way, the
tables matched those of an earlier generator, which used another
interval-arithmetic library, byte for byte. The tables are named by width and
level: _SHORT up to 512 bits, _LONG_COARSE and _LONG_FINE the two
lookups up to 4608 bits. test_medium_tables checks every entry against the
general series kernels, and test_constant_tables ln 2 and pi against binary
splitting. An entry is hex digits, most significant limb first, in one string
literal per table: static data, read in place, 16 digits to a limb. String
literals encode bytes above 0x7f as UTF-8, so raw bytes would not survive; a
comptime SIMD table, the other option, was rebuilt on every read (about 1.1 µs
for a 37 KB table) and took 1.4 GB to compile. The constants ln 2, pi and Euler's
gamma come from the same tables for every precision up to 4608 bits. Brent
and McMillan's series for gamma had been 22% of hyp2f1's digamma path at
53 bits.
scripts/generate_bernoulli_table.py writes ball/_bernoulli_table.mojo:
the tangent numbers T_1 ... T_256, from which Stirling's series takes
B_2k = (-1)**(k-1) 2k T_k / (4**k (4**k - 1)). Its coefficients are then
integers: B_2k / (2k (2k-1)) is (-1)**(k-1) T_k / (4**k (4**k - 1) (2k-1)).
The script computes the numbers by Brent and Harvey's recurrence ("Fast
computation of Bernoulli, tangent and secant numbers", 2011), the one the
library runs past the table's 1,920 bits of working precision. It checks
them against the exact Bernoulli recurrence for k <= 64, and against the
rounding of 2 (2k)! zeta(2k) / (2 pi)**2k for every k. Recomputing the
numbers on every call had been a third of gammainc's time at 53 bits.
For a ball without a pole, Gamma, log |Gamma| and digamma take the
midpoint's value and widen it over the radius r. With M = max |psi| over
the ball, psi being log |Gamma|'s derivative,
|log |Gamma(t)| - log |Gamma(m)|| <= r M and
|Gamma(t) - Gamma(m)| <= r M |Gamma(m)| e**(r M), where e**(r M) < 65/64.
Above 0, 0 < psi'(t) < 1/t + 1/t**2 bounds digamma's change, so
M <= |psi(m)| + r (1/low + 1/low**2). Here low <= m - r comes from radius
arithmetic, so a ball above 0 needs neither of its exact ends, and the
bounds are radius operations. Gamma and psi at m come from one pass of the
Taylor method below. Below 0, psi increases between the poles, so M is at an
end, both computed at 32 bits. One evaluation replaces the two ends, or five
evaluations below 0, while r M < 2**-8; a wider ball keeps the ends, which
are tighter.
Measured by the report against its HEAD baseline (pinned, case by case, drift 0.9%), the time over the old one at 53, 256 and 1024 bits was: - Float gamma, gammaln, digamma: 0.23, 0.38, 0.26 at 53 bits; 0.37, 0.41, 0.49 at 256; 0.28, 0.30, 0.42 at 1024; - Ball versions: 0.29, 0.33, 0.15 at 53 bits; 0.25, 0.24, 0.28 at 256; 0.16, 0.18, 0.21 at 1024.
The Float gamma went from 28 to 6.6 times MPFR's time at 53 bits, and the ball gamma from 271 to 58 times Arb's.
scripts/generate_rgamma_table.py writes ball/_rgamma_table.mojo: the
Taylor coefficients of R(u) = 1/Gamma(1 + u) = sum_n b_n u**n, b_0 ...
b_273, each |b_n| 2**1280 rounded toward zero (85 KB of hex). It computes
them by the recurrence (n-1) a_n = gamma a_{n-1} - zeta(2) a_{n-2} + ... +
(-1)**n zeta(n-1) a_1 (Wrench 1968), b_n = a_{n+1}, at two working
precisions far above the recurrence's cancellation, and the entries must
agree between them. It also checks the truncated series against rgamma at
sample points. Johansson ("Arbitrary-precision computation of the gamma
function", 2021, section 5.1) gives the method; its Theorem 5.3 bounds the
truncation: for complex |z| <= 20 and N <= 1000,
|R(z) - sum_{n<N} b_n z**n| <= 8 max(1/2, |z|) |b_N| |z|**N when that is
below 2**-8.
ball/_rgamma.mojo evaluates R and R'(u) = sum n b_n u**(n-1) by Horner's
rule in fixed point, at F = 64 wn >= w + 24 fraction bits, for
x = 1 + u + m, |u| <= 1/2. Then:
- Gamma is (1+u) ... (m+u) / R(u);
- log Gamma is its logarithm, or that of Gamma(1 + x)/x below 1/2;
- digamma is -R'(u)/R(u) + sum_{k<m} 1/(1+u+k).
The product and the sum are exact in integers for a short dyadic x. The
bounds:
- Coefficients and products. Each coefficient is the entry's top wn
limbs, below the true one by less than a unit (n units for n b_n). Each
product is truncated, below the true one by less than a unit, and later
steps scale both by |u| <= 1/2. So R is within sum_n 2 |u|**n <= 4
units and R' within sum_n (n+1) 2**(1-n) <= 6.
- A u with bits below the fixed point is truncated, which moves R by at
most 3 units and R' by at most 6. On t = 1 + u in [1/2, 3/2],
R' = -psi(t)/Gamma(t) and R'' = (psi(t)**2 - psi'(t))/Gamma(t). psi
rises from psi(1/2) > -1.97 to psi(3/2) < 1, psi' falls from
pi**2/2 < 4.94 to above 0, and Gamma stays above 0.88.
- R's tail at |u| <= 1/2 is at most 4 |b_N| |u|**N, by Theorem 5.3.
- R''s tail. Theorem 5.3 bounds the series' absolute terms, so it holds
on the circle of radius 1/8 about u, where |z| <= rho = |u| + 1/8.
Cauchy's estimate then bounds the tail by
8 (5/8) |b_N| rho**N / (1/8) < 2**6 |b_N| rho**N.
N is the first index where both bounds fall below 2**-(F+2), with
|b_N| < 2**(bits_N - 1280) from the entries' lengths. The table serves
working precisions up to 1256 bits, which a 1024-bit Float reaches with its
guard bits, a reflection and a Ziv retry. Stirling's series serves larger x
and higher precisions.
A u with zero low limbs, such as the 1/2 of a half-integer x, skips them in each product, so its step is one row of word products.
Shorter steps. A step whose result later steps scale by |u|**n (for R)
or |u|**(n-1) (for R') ignores the limbs of the partial sum and of u below
2**(-n log2 |u| - 66) units, and cuts those of its result. That is less
than 2**-64 units a step and under one in all, so R is within 5 units and
R' within 7. Products also skip a partial sum's leading zero limbs: late in
Horner's rule the sum is near its coefficient |b_n|. The generator writes
each entry's bit length beside the table, so the term count reads one value
a term.
One pass for Gamma and psi. With x = 1 + u + m, the product
P = (1+u) ... (m+u) and its derivative P' = P sum_k 1/(k+u) come from one
fixed-point pass (_shift_factors). Then Gamma = P / R and
psi = P'/P - R'/R, and the exact Integer product and reciprocal sum serve
only past 8 limbs or for a u inexact at the fixed point. The bounds:
- The factors f_k = (k + u) 2**F are exact integers.
- P is an integer of n = wn + 2 limbs times 2**e. Each step multiplies
by f_k and keeps the top n limbs, cutting the rest. After a cut,
P >= 2**(64 (n-1)), so each cut loses less than eps = 2**-(64 (n-1)) of
P. The true P lies within a factor (1 + eps)**m above the kept one, and a
radius of 2 m 2**(64 + e) covers it.
- P' steps as P' f_k + P 2**F at the same scale and cut. Every term is
positive, and P'/P = sum 1/(j+u) stays between 2/3 and 16 for
m <= 4096, so P' loses less than 1.5 eps of itself a step and fits
n + 1 limbs. A radius of 4 m 2**(128 + e) covers it.
Balls take few guard bits. A ball's radius carries the kernel's error,
so these passes run at F = 64 wn >= w + 8 for a ball, not a Float's
w + 24, which keeps a Ziv step's error small. The joint pass runs 8 bits past the
ball's precision.
One word. Where w <= 62 (F = 64 >= w + 2), _gamma_word runs the
pass in native 128-bit integers. It serves narrow balls and exact non-integer
points; integers keep their exact values:
- x splits exactly from its significand: x 2**64 is an integer below
2**84 for x < 2**20, so n is it rounded and u = x - n.
- R and R' take Horner's rule as magnitudes and signs. The partial sums
stay below 2 (|b_1| < 0.58, |b_2| < 0.66, and
sum_{j>=3} |b_j| 2**(3-j) < 0.25), so s u < 2**128. The error bounds are
those of the limb version.
- P and P' are 124-bit mantissas at a shared exponent, cut each step,
within a factor (1 + 2**-122)**m above. P is normalized to 124 bits
before the division, exactly.
- Gamma is P's top 64 bits over R, one 128-by-65-bit division. Its
relative error adds P's, R's ((4 + tail)/R), the cut of P (below
2**-63) and the quotient's cut (below 2**-62), each bounded from above.
- psi only bounds the radius for Gamma and log Gamma, so doubles serve,
with margins far above their roundings. For digamma itself, psi is
floor(P' 2**64 / P) - floor(R' 2**64 / R) in units of 2**-64, within
those cuts and the relative errors of P', P, R' and R.
The result is rounded once into the result's format, and only log Gamma takes a ball operation after it. Past one word, Gamma and log Gamma take the value-only pass. psi's bound there comes from the one-word pass at the midpoint rounded to 53 bits, its slope term widened by the rounding's distance. Digamma takes the derivative in full.
Ball containment held for 1,397 random balls at 20 to 200 bits and for 17,415
sample points (each ball's ends and midpoint) aimed at the one-word path,
with x near 1/2, at and near integers, at u = +-1/2 and at the top of its
range, against Floats at twice the precision. Ball gamma, gammaln and digamma
took, pinned, in µs:
| 53 bits: before → after | Arb | 256 bits: before → after | Arb | |
|---|---|---|---|---|
| gamma | 8.8 → 0.85 | 0.57 | 11.0 → 5.8 | 3.14 |
| gammaln | 8.5 → 1.3 | 1.00 | 11.2 → 6.6 | 5.2 |
| digamma | 6.0 → 1.1 | 1.63 | 9.3 → 8.2 | 13.1 |
At 53 bits the radii were 0.03 to 0.75 of Arb's on five inputs.
Against MPFR, 468 values of Gamma, log Gamma and digamma at 24 to 1240 bits were all correctly rounded, besides the suites. With the narrow-ball rule above, the report against its dad8583 baseline (pinned, case by case, drift 3.5%) gave, as the time over the Stirling version at 53, 256 and 1024 bits: - Float gamma, gammaln, digamma: 0.19, 0.53, 0.59 at 53 bits; 0.075, 0.39, 0.37 at 256; 0.11, 0.24, 0.20 at 1024; - Ball versions: 0.24, 0.53, 0.44 at 53 bits; 0.20, 0.47, 0.13 at 256; 0.12, 0.078, 0.20 at 1024.
The Float gamma took 1.26 times MPFR's time at 53 bits and 0.40 and 0.39 at 256 and 1024. The ball gamma took 15.6, 7.8 and 2.8 times Arb's time; at 53 bits Arb's takes 0.6 µs, and the remaining cost is the ball operations around the two kernel calls.
The remaining kernels derive log1p, log2, log10, asin, acos,
atan2, and the hyperbolic functions and their inverses from these functions.
Each kernel documents how it avoids cancellation. Like sqrt, rootn
rounds from an exact integer root: q = floor(abs(x)**(1/n) 2**t)
has at least p + 2 bits, and 2q + 1, when the root is inexact, lies
strictly between the same two rounding boundaries as the exact root.
The Ziv driver¶
The driver evaluates the kernel at working precision w, then rounds both
ends of its enclosure to the target format. If both give the same Float and
status, monotonicity ensures that every point between them, including the
exact value, rounds the same way. Otherwise, it doubles the guard bits
w - p and tries again. The first w is p + 32 + bit_length(p).
The loop terminates for every argument that is not an exact case. Every rounding boundary is a binary fraction, a Float of the target precision or the midpoint of two neighbours. By the Lindemann-Weierstrass and Gelfond-Schneider theorems, the functions' values at binary fractions other than the exact cases of Appendix D.1 of the requirements are transcendental, so the exact value is never a boundary, and a narrow enough enclosure excludes every boundary.
The same step is public as apn_mojo.ball.to_float_if_certain. Requiring
equal statuses as well as equal values costs at most a little precision when
the enclosure contains the rounded value itself, and it gives the correct
rounding direction.
Values near a Float. For a small x, sin(x) = x - x**3/6 + ... lies
within 2**(3E - 1) of x, where E is x's exponent; a directed rounding
then hinges on that tiny difference, and Ziv would need about 3E bits to
see it. Instead, _round_near decides such a value directly. Every rounding
boundary and every Float of precision p, other than the base x itself, lies
at least 2**(E - max(p + 1, q) - 2) from a base of precision q, so when the
distance bound is at most 2**(E - max(p + 1, q) - 3), no boundary separates
the value from a point between it and the base, and rounding that point gives
the answer and its status. The same rule serves exp, exp2, cos and
cosh near 1, expm1 and log1p near x, log near 1 through
x - 1, expm1 near -1 and tanh near +-1 for large arguments.
Exact cases are found before the driver and rounded once: zeros of the
functions, exp(0) = 1, log(1) = 0, exp2 of an integer, log2(2**k) = k,
log10(10**k) = k, rootn of a perfect power, and pow(x, y) for a
binary-fraction y = c / 2**k when x = a 2**b (odd a) has
a = a1**(2**k), 2**k divides b, and a1 = 1 for a negative c. A
Rational exponent c/d gives an exact power exactly when the d-th root of
x is a binary fraction. An exact Rational argument that is not a binary
fraction has no Float point, so its kernel encloses the ball function of a
tight ball around it at each working precision.
Complex functions¶
A Complex function rounds each part in its own context. The pair driver
encloses both parts, keeps either result once certified, and raises precision
for the other. Some parts are always zero for a class of arguments: the
imaginary part of exp(x + 0i) or of sqrt on the positive real axis, for
example. An enclosure cannot decide that zero's sign, so the function handles
it before calling the driver. If the budget runs out while a part's enclosure
still contains 0, the error reports a missing structural rule.
Special values follow C99 Annex G as MPC implements it, including MPC's
choices where the annex leaves a sign unspecified: cosh(NaN + 0i) is
NaN - 0i, while cos(+0 + NaN i) is NaN + 0i. The trigonometric functions
are their hyperbolic counterparts at iz, but MPC's special values of
asin, atan and cos are not always the hyperbolic ones', so those cases
have their own rules. Powers of 1, -1, i and -i with a complex exponent
w = u + iv follow MPC's pow: the power is real when u t is an integer
for the argument t pi, with an imaginary zero that is -0 when rounding
toward -inf or when the signs of Im z and Re w differ, and imaginary,
with a real part of +0, when u t is an odd multiple of 1/2.
tanh(x + iy) for x >= 1 has a real part 1 - d with
|d| <= (1 + e**-2) / (e**(2x)/2 - 1) < 3.12 e**(-2x). An enclosure of it
contains 1 until it is narrower than d, which for x = 10**8 would take
about 3 * 10**8 bits, so _round_near decides it from the sign of d, that
of cos 2y + e**(-2x); the imaginary part, of the size of e**(-2x), keeps
its relative accuracy and rounds by its own iteration. tan inherits the rule.
Special functions¶
Special functions use the same three layers. Their series kernels account for each rounding in the radius and add a remainder bound whenever they truncate a series:
| Function | Method | Remainder |
|---|---|---|
| erf | (2/sqrt pi) e**(-x**2) sum 2**n x**(2n+1) / (2n+1)!!, positive terms |
once a term ratio is at most 1/2, the tail is below the last term |
| erfc | 1 + erf|x| for x < 0; 1 - erf x with 1.443 x**2 extra bits; for large x, e**(-x**2)/(x sqrt pi) sum (-1)**k (2k-1)!!/(2x**2)**k |
the first omitted term (DLMF 7.12(i)) |
| erfi | (2/sqrt pi) sum x**(2n+1) / (n! (2n+1)); for large x, (2/sqrt pi) e**(x**2) D(x) |
below |
| Ei | gamma + log|x| + sum x**n / (n n!); for large x < 0, -E1(-x) by its asymptotic series; for large x > 0, e**x sum_{k<n} k!/x**(k+1) |
ratio bound; the first omitted term (DLMF 6.12(ii)); below |
| Si, Ci | their series with 1.443 |x| extra bits for the cancellation; for large x, pi/2 - f cos x - g sin x and f sin x - g cos x |
ratio bound; the first omitted terms of f and g (DLMF 6.12(ii)) |
| Shi, Chi | positive series; for large x, (Ei(x) +- E1(x))/2 with 0 < E1(x) < e**(-x)/x |
ratio bound; as Ei |
| Fresnel S, C | series in u = pi x**2 / 2 with 1.443 u extra bits; for large x, 1/2 - f cos u - g sin u and 1/2 + f sin u - g cos u, with x**2/2 reduced exactly modulo 2 |
ratio bound; the first omitted terms (DLMF 7.12(ii)) |
| Gamma, log Gamma, digamma | below w/2 + 10 and up to 1256 bits, the tabulated Taylor series of 1/Gamma(1 + u) and its derivative at x = 1 + u + m, |u| <= 1/2 (below), with P = (1+u) ... (m+u) and P' from one fixed-point pass: Gamma P/R, log Gamma its logarithm (of Gamma(1 + x)/x below 1/2), digamma P'/P - R'/R; in native 128-bit integers where w + 8 <= 64; otherwise Stirling's series at z = x + N >= w/2 + 10, its coefficients in integers from the tabulated tangent numbers (below), back to x by Gamma(x) = Gamma(z) / (x (x+1) ... (x+N-1)), log Gamma by the product's logarithm, digamma by sum 1/(x+k); the products and sums exact in integers for a short dyadic x; reflection below 1/2 |
Johansson's Theorem 5.3 and Cauchy's estimate (below); the first omitted term, for a real z (DLMF 5.11(ii)) |
| Lambert W | Halley's iteration, then the signs of t e**t - x at both ends of t (1 -+ 2**-w) |
none: the root is bracketed |
| log Gamma below 0 | log pi - log |sin pi x| - log Gamma(1 - x), x reduced exactly in sin pi x |
as log Gamma |
| ndtr, log ndtr | erfc(-x/sqrt 2)/2 from the erfc ball at x/sqrt 2, with 2 exponent(x) more bits for erfc's sensitivity; its logarithm, as log1p(-erfc(x/sqrt 2)/2) for x > 0 |
as erfc |
| Hurwitz zeta | direct summation while the terms shrink fast, the tail below its integral; otherwise Euler-Maclaurin at a = q + N >= |s| + 2M, M = (w + 10)/5; Riemann's below -1 by the functional equation |
4 |(s)_2M| (2 pi)**-2M a**(1-s-2M) / (s+2M-1) (DLMF 24.9.4); the integral |
| polygamma | (-1)**(n+1) n! zeta(n+1, x), a negative x shifted above 0 |
as zeta |
| erfinv, ndtri | Newton's iteration on erf for |x| <= 1/2 and on erfc in the tails, from Giles's approximation or t = sqrt(L - log(t sqrt pi)), each step at twice the bits the last one showed right |
none: the root is bracketed by the signs at t (1 -+ 2**-(w+8)) |
| hyp1f1 | the series at |x| after Kummer's transformation for x < 0, or, where the series would be longer than about the precision, DLMF 13.2.41's real part, Gamma(b) [e**x x**(a-b) S_1 / Gamma(a) + cos(pi a) x**-a S_2 / Gamma(b-a)] with U's expansions; rational values exactly (Hypergeometric functions) |
the ratio bound |T_N| / (1 - D) (Johansson 2019); Olver's bounds (DLMF 13.7.5) |
| gammainc, gammaincc | the smaller of P and Q directly, the other as 1 minus it: P by x**a e**-x / Gamma(a+1) M(1, a+1, x) for x < a + 1, Q by its asymptotic expansion past it, or 1 - P with -log2 Q more bits |
the ratio bound; the first omitted term (DLMF 8.11.3) |
| hyp2f1 | Pfaff's transformation below -1/2, the series up to 1/2, the connection with 1 - x (DLMF 15.8.4) above, its digamma limit for an integer c - a - b (15.8.10); Gauss's value at 1 |
the ratio bound; for the digamma series, |L_k| <= |log t| + |a+m-1| h + |b-1| h from psi' < 1/t + 1/t**2 |
| betainc | x**a (1-x)**b / (a B(a, b)) F(a+b, 1; a+1; x) for x <= (a+1)/(a+b+2), positive shrinking terms, else 1 - I_{1-x}(b, a) |
the ratio bound |
| beta, betaln, poch | Gamma(a) Gamma(b) / Gamma(a+b), log |Gamma(a)| + log |Gamma(b)| - log |Gamma(a+b)| with the magnitude of the larger term in extra bits, Gamma(z+m) / Gamma(z); rational values exactly (ball/_ratios.mojo) |
as Gamma |
Every series is used only where its cancellation and its length stay within a few times the working precision; an asymptotic series is used once its smallest term is below the precision, and otherwise the convergent one.
Dawson's integral. D(x) = e**(-x**2) int_0^x e**(t**2) dt
= (x/2) int_0^1 e**(-x**2 v) (1 - v)**(-1/2) dv. Expanding
(1 - v)**(-1/2) = sum c_k v**k, with c_k = (2k-1)!!/(2**k k!) decreasing,
leaves a remainder between 0 and v**n (1 - v)**(-1/2). Integrating, with
int_0^1 = int_0^inf - int_1^inf for the polynomial part and a split at
v = 1/2 for the remainder, gives
-n e**(-x**2)/x <= D(x) - sum_{k<n} T_k <= 2 sqrt(2n) T_n + (x/sqrt 2) e**(-x**2/2)
for T_k = (2k-1)!!/(2**(k+1) x**(2k+1)) and n <= x**2/2, using
c_n >= 1/(2 sqrt n). It reaches about 0.72 x**2 bits.
Ei at large x > 0. e**(-x) Ei(x) = PV int_0^inf e**(-t)/(x - t) dt.
On [0, x/2] the geometric expansion of 1/(1 - t/x) gives
sum_{k<n} k!/x**(k+1) less at most 4 e**(-x/2)/x, plus a remainder in
[0, 2 n!/x**(n+1)], for n <= x/4. The rest is
e**(-x) (2 Shi(x/2) - E1(x/2)), between -e**(-3x/2) and
8 e**(-x/2)/x since Shi(a) <= 2 e**a/a for a >= 8. So
|e**(-x) Ei(x) - sum| <= 2 n!/x**(n+1) + 8 e**(-x/2)/x for x >= 16.
Values near a Float decided by _round_near:
| Function | Argument | Base | Distance below | Side |
|---|---|---|---|---|
| erf | |x| >= 1 |
+-1 |
e**(-x**2) |
toward 0 |
| erfc | x <= -1; |x| < 1/2 |
2; 1 | e**(-x**2); 1.13 |x| |
below; above for x < 0 |
| Si, Shi | |x| < 1/2 |
x | |x|**3 / 16 |
toward 0 for Si, away for Shi |
| Fresnel C | |x| < 1/2 |
x | |x|**5 / 4 |
toward 0 |
| Fresnel S, C | |x| >= 2**(p+4) |
+-1/2 |
1/|x| |
the sign of -(f cos u + g sin u), f sin u - g cos u |
| Lambert W_0 | |x| < 1/4 |
x | 4 x**2 |
below |
| Gamma, digamma | x = +-2**k <= 1/4 |
1/x, -1/x |
2 | below |
Gamma's overflow uses log Gamma(x) > (x - 1/2) log x - x, Binet's remainder
being positive, and its underflow for x < 0 uses
|Gamma(x)| <= pi / (2 d Gamma(1 - x)) with d the distance from x to the
nearest integer. erfc, erfi, Ei, Shi and Chi decide overflow and underflow from
e**(+-x**2) and e**(+-x) bounds before their kernels, whose exponents
would leave the 64-bit range.
Budgets¶
Adaptive calculations are bounded by max_precision in ArithmeticContext
and BallContext. It defaults to max(8 p, p + 4096) for output precision
p. If the driver's next precision would exceed this budget, a Float
function raises an error naming the function, argument, and limit:
precision budget exceeded: sin at 9.900656229295898e+301029 needed more than 4149 bits; pass a larger max_precision in the context.
Argument reduction for sin, cos and tan needs about as many bits of pi
as the argument's exponent, and stops at the budget: sin(2**(10**6)) at 53
bits raises. A ball function never raises for its budget; it returns a
trivially valid enclosure instead, [0 +/- 1] for sin and cos and an
indeterminate ball for tan. A tight budget, such as p + 8, makes every
non-exact call raise at once; exact cases need none.
Constants¶
Constants share one binary-splitting engine (Haible and Papanikolaou). It merges terms like a binary counter on an explicit stack, keeping the depth logarithmic. Each truncated series adds a bound for its tail:
| Constant | Formula | Tail after n terms |
|---|---|---|
| pi | Chudnovsky, 426880 sqrt(10005) / S |
2**30 (n + 1) 2**(-47 n), the next term of an alternating series |
| e | sum 1/k! |
2 / n! |
| ln 2 | 18 atanh(1/26) - 2 atanh(1/4801) + 8 atanh(1/8749) |
2 m**-(2n+1) for atanh(1/m) |
| log2(10) | 3 + 2 atanh(1/9) / ln 2 |
as for atanh |
| gamma | the table up to 4608 bits (as ln 2 and pi); beyond, Brent-McMillan: A/B - log n - K0(2n)/I0(2n) for a power of two n |
the table's unit; the Bessel ratio lies in (0, pi e**(-4n)); tails 2 t_K H_K and (4/3) t_K |
| Catalan | Ramanujan: (pi/8) log(2 + sqrt 3) + (3/8) sum (k!)**2 / ((2k)! (2k + 1)**2) |
2 * 4**-n |
The Bessel bound follows from K0(x) < sqrt(pi/2x) e**-x and
I0(x) > e**x / sqrt(2 pi x) for x >= 1.
ln 2 and pi, which every exp, log and argument reduction needs, come from
tables up to 4096 bits: floor(c 2**(4096 - E)) for the constant's exponent
E, compiled in as 64 words. A request for b bits reads the top b bits as
the midpoint, and one unit of their last place is the radius, since the
constant exceeds its truncation by less than that unit. The tables are the
series' own floors at 4300 bits, which the tests check, and agree with MPFR's.
Recomputing ln 2 was 42% of a 64-bit exp.
A constant's canonical ball at precision p is
[round_down(c, p), round_up(c, p)], computed by two directed runs of the
driver; canonical(ball, p) gives the same ball from any wider enclosure, or
None when it cannot decide both ends. A caller that caches the widest ball it
has computed therefore serves every narrower request exactly as a fresh
computation would.
Determinism¶
Calls share no mutable cache or numerical flags. A result depends only on
its arguments and context, so repeated calls give the same bits, including
through vmap and at different thread counts. The tests check this property.