Correctly rounded elementary and special functions
#include <flint/decimal.h>int decfloat_pi(decfloat_t res, gr_ctx_t ctx)sourceConstants, correctly rounded.
int decfloat_exp(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_expm1(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_log(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_log1p(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_sin(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_cos(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_sin_cos(decfloat_t res1, decfloat_t res2, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_tan(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_asin(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_acos(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_atan(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_atan2(decfloat_t res, const decfloat_t y, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_sinh(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_cosh(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_sinh_cosh(decfloat_t res1, decfloat_t res2, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_tanh(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_asinh(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_acosh(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_atanh(decfloat_t res, const decfloat_t x, gr_ctx_t ctx)sourceint decfloat_pow(decfloat_t res, const decfloat_t x, const decfloat_t y, gr_ctx_t ctx)sourceThe most common elementary functions, correctly rounded. Versions for the other types (decball_exp, deccfloat_exp, deccball_exp, ...) are documented with those types.
int decfloat_via_arb(decfloat_t res, const decfloat_t x, int (*func)(arb_t, const arb_t, slong), gr_ctx_t ctx)sourceint _decfloat_arb_gr(decfloat_ptr * res, slong nres, decfloat_srcptr * args, slong nargs, int has_flag, int flag, int method, gr_ctx_t ctx)sourceThe machinery behind the real elementary and special functions: evaluates a user-supplied arb function (which must return a gr status code), or the gr method method of the arb ring applied to nargs arguments (with an integer flag if has_flag is set) producing nres results, at increasing precision (Ziv's strategy) until the rounding of every result is decided. GR_UNABLE is returned if this does not happen within 64 times the working precision; for the elementary functions (exponentials, logarithms, powers, trigonometric and hyperbolic functions and their inverses), whose cost at high precision is quasi-linear, the limit is increased by twice the number of digits of the arguments, so that arguments with more digits than the target precision (which matter near cancellation points, e.g. \log_2(1 + 10^{-10^6})) are resolved in time proportional to their length. Points outside the real domain of the inverse functions and logarithms (\mathrm{acosh}(1/2), \log(-1)) are detected before any evaluation and give GR_DOMAIN.
Ziv's strategy cannot decide the rounding when the exact result lies extremely close to a representable number. This happens systematically in three situations, which the decimal functions treat separately:
- Exact values: \Gamma(n) = (n-1)!, 1/\Gamma(n), \zeta(-n), \zeta(0) = -1/2, \log_2(2^n), \log_{10}(10^n), \sin(\pi k/4), \tan(\pi k/4), \cot(\pi k/4), \csc(\pi k/4), \sec(\pi k/4), \mathrm{asin}(1/2)/\pi = 1/6 and the other rational values of the inverse trigonometric functions divided by \pi, \mathrm{acos}(1) = \mathrm{acosh}(1) = 0, \log \Gamma(1) = \log \Gamma(2) = 0, G(n), \log G(n) for n = 1, 2, 3, f(0) for functions whose
arbimplementation may not return an exact zero or one, powers x^n, x^{p/q} and 2^x, 10^x with exact results, and \mathrm{atan2} on the axes. (arbdoes not detect most of these.) These are detected from the sign, the scientific exponent, the limb exponent and individual digits of the argument, in time independent of its length and exponent (for instance \sin(\pi x) depends on three digits, and all trivial zeros \zeta(-2k) and poles of \Gamma are found for any k); the argument is only converted to an integer or rational number when it is small or a genuine candidate (\log_2(x) first checks the digit count, the last digit and a residue modulo a word-size prime). - Tiny arguments: when the leading Taylor term S (x, 1, \pm 1/x, x - 1 for \log, 1/(x-1) for \zeta near 1, -1/2 for \zeta near 0, 1/2 or \operatorname{sgn}(x)/2 for \mathrm{acos}(x)/\pi, \mathrm{acot}(x)/\pi near 0 and \mathrm{atan}(x)/\pi, \mathrm{asec}(x)/\pi near infinity, x for the Barnes G-function) is an exact decimal and the remaining tail, of known sign, lies more than
prec+ 4e digits below it, the result is S rounded with the tail acting as a sticky bit. For example \sin(10^{-10^9}), \Gamma(10^{-10^9}) = 10^{10^9} - \gamma + \ldots, \zeta(1 + 10^{-10^9}) and \cot(10^{-10^9}) are all correctly rounded at negligible cost. - Large arguments: \tanh, \coth, \mathrm{erf}, \mathrm{erfc}, \mathrm{expm1} and \zeta converge exponentially fast to \pm 1, 2, -1 or 1, and \mathrm{acot}, \mathrm{acoth}, \mathrm{acsc}, \mathrm{acsch} behave like 1/x; these are handled in the same way.
The remaining special functions (Bessel functions, hypergeometric functions, ...) rely on Ziv's strategy alone and can in principle return GR_UNABLE for arguments at which the exact value is a representable number not detected by arb.
Some arguments are rejected with GR_UNABLE before any evaluation (for balls as well), since the arb/acb algorithms would need time or memory far beyond what the result is worth: for the non-elementary functions, an argument with a part of magnitude at least 10^{10^6} (and for the Barnes G-function also at most 10^{-10^6}, and at least 10^{50}); for the polylogarithm, the Hurwitz zeta function and the polygamma function, an order s with |\operatorname{Re}(s)| \ge 10^{10} or |\operatorname{Im}(s)| \ge 10^{7} (there is no Riemann-Siegel type formula); and for the zeta-type functions (\zeta, Hurwitz \zeta, \eta, \beta, \operatorname{Li}_s, \xi) in the complex contexts, |\operatorname{Re}(s)| \ge 10^{30}, |\operatorname{Im}(s)| \ge 10^{15} (the number of terms grows with |\operatorname{Im}(s)|) and, for \xi, an imaginary part whose cancellation (\xi(s) decays like e^{-\pi |\operatorname{Im}(s)|/4}) exceeds the maximal working precision.
The elementary functions of decfloat_t (and, through the generic methods, the special functions such as gr_gamma, gr_zeta, gr_erf and gr_bessel_j with a decimal context) are correctly rounded: the result is the exact function value rounded once to the context precision in the context rounding mode. The functions are evaluated with arb and rounded by Ziv's strategy [Ziv1991], with the cases where that strategy cannot succeed handled separately.
Ziv's strategy#
The argument is converted to an arb ball exactly, the function is evaluated at a working precision somewhat above the target, and the resulting ball is rounded: if both of its endpoints round to the same prec-digit number, that number is the correctly rounded result. If not, the working precision is increased and the evaluation repeated. This terminates unless the function value is a representable number or lies extremely close to one; the loop gives up with GR_UNABLE at 64 times the target precision (for the elementary functions, plus twice the number of digits of the argument, since an argument with more digits than the target can still matter near a cancellation, as in \log_2(1 + 10^{-10^6})).
Cases decided without Ziv's strategy#
Three kinds of arguments would defeat Ziv's strategy systematically, and are detected before any evaluation, from the sign, the exponent and a few digits of the argument, in time independent of its length:
- Exact values, such as \Gamma(n) = (n - 1)!, \log_2(2^n), \sin(\pi k/4) for the functions of \pi x, \zeta(-2k) = 0, \operatorname{acos}(1) = 0, \operatorname{asin}(1/2)/\pi = 1/6, and powers x^{p/q} with exact results.
- Tiny arguments, where f(x) = S + t with an exact decimal leading term S (x, 1, \pm 1/x, x - 1, ...) and a tail t of known sign far below the rounding position: the result is S rounded with t as a sticky bit (
_decfloat_round_with_tail). For instance \sin(10^{-10^9}), \Gamma(10^{-10^9}) and \zeta(1 + 10^{-10^9}) are correctly rounded at negligible cost. - Large arguments of functions converging exponentially to a representable limit (\tanh, \operatorname{erf}, \operatorname{expm1}, \zeta) or behaving like 1/x (\operatorname{acot}, ...), handled in the same way.
Points outside the real domain (such as \log(-1) or \operatorname{acosh}(1/2)) give GR_DOMAIN before any evaluation, and arguments whose evaluation would be unreasonably expensive (non-elementary functions at arguments of magnitude 10^{10^6} and beyond, for example) give GR_UNABLE. The special functions other than those listed rely on Ziv's strategy alone and could in principle return GR_UNABLE at an argument where the value is an undetected representable number.
The ball versions (decball_exp and so on) instead evaluate the function once on the arb ball enclosing the argument and convert the result back, and the complex versions use acb (Complex functions).
Example#
#include <flint/flint.h>
#include <flint/gr.h>
#include <flint/gr_special.h>
#include <flint/decimal.h>
int main(void)
{
gr_ctx_t ctx;
decfloat_t x, y;
int status = GR_SUCCESS;
gr_ctx_init_decfloat(ctx, 25, 0);
decfloat_init(x, ctx);
decfloat_init(y, ctx);
status |= decfloat_set_ui(x, 1, ctx);
status |= decfloat_exp(y, x, ctx);
flint_printf("exp(1) = "); gr_println(y, ctx);
status |= decfloat_atan(y, x, ctx);
flint_printf("atan(1) = "); gr_println(y, ctx);
/* exact values are recognised */
status |= decfloat_set_ui(x, 5, ctx);
status |= gr_gamma(y, x, ctx);
flint_printf("gamma(5) = "); gr_println(y, ctx);
status |= decfloat_set_str(x, "1024", ctx);
status |= gr_log2(y, x, ctx);
flint_printf("log2(1024) = "); gr_println(y, ctx);
status |= decfloat_set_str(x, "0.25", ctx);
status |= gr_sin_pi(y, x, ctx);
flint_printf("sin(pi/4) = "); gr_println(y, ctx);
status |= decfloat_set_str(x, "0.5", ctx);
status |= gr_sin_pi(y, x, ctx);
flint_printf("sin(pi/2) = "); gr_println(y, ctx);
/* a tiny argument in directed rounding */
decimal_ctx_set_rnd(ctx, DECIMAL_RND_DOWN);
status |= decfloat_set_str(x, "1e-1000000000", ctx);
status |= decfloat_sin(y, x, ctx);
flint_printf("sin(1e-10^9) = "); gr_println(y, ctx);
status |= decfloat_cos(y, x, ctx);
flint_printf("cos(1e-10^9) = "); gr_println(y, ctx);
/* outside the real domain */
status |= decfloat_set_si(x, -1, ctx);
flint_printf("log(-1) domain error: %d\n", decfloat_log(y, x, ctx) == GR_DOMAIN);
flint_printf("status %d\n", status);
decfloat_clear(x, ctx);
decfloat_clear(y, ctx);
gr_ctx_clear(ctx);
return 0;
}exp(1) = 2.718281828459045235360287 atan(1) = 0.7853981633974483096156608 gamma(5) = 24 log2(1024) = 10 sin(pi/4) = 0.7071067811865475244008444 sin(pi/2) = 1 sin(1e-10^9) = 9.999999999999999999999999e-1000000001 cos(1e-10^9) = 0.9999999999999999999999999 log(-1) domain error: 1 status 0
In the directed rounding mode toward zero, \sin(10^{-10^9}) is the largest 25-digit number below 10^{-10^9} and \cos(10^{-10^9}) the largest below 1.
Guides#
References#
- [Ziv1991] A. Ziv. Fast evaluation of elementary mathematical functions with correctly rounded last bit. ACM Transactions on Mathematical Software 17:3 (1991), 410–423. https://doi.org/10.1145/114697.116813
The FLINT team, “Correctly rounded elementary and special functions (decimal.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/decimal/functions.html (generated 2026-10-20).
@misc{flint-doc-decimal-functions,
author = {{The FLINT team}},
title = {{Correctly rounded elementary and special functions (decimal.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/decimal/functions.html},
note = {Generated 2026-10-20}
}