the expm1 \"function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.\"
is there a rule of thumb as to what is small? 1e-1, 1e
If 6 significant decimal digits are enough for you, exp(x)-1
is okay up to x being 1e-10. In general, you are going to lose N decimal digits of accuracy when x is about 10**(-N)
. We start with about 16 digits in double precision.
Why not always use expm1
? Because expm1(x) + 1
has absolutely no benefit over exp(x)
, no matter how small x
is. It only makes sense to use expm1
when your computation actually requires something like exp(x) - 1
. One has to consider the wider context of the computation.
It's not really about how small x is, but about how exp(x)
is used in your computation. The purpose of expm1
should be understood in wider context of Loss of significance. Some formulas are subject to loss of significance for certain ranges of parameters; one has to analyze the formula to see if and when it happens. And if there is a potential loss of significance in some range, rework the formula into something algebraically equivalent but numerically stable. Wikipedia explains this well on the example of quadratic equation.
If your goal is to compute exp(x)
, or 3*exp(x) + 4
or such, you should use exp
. There is no loss of significance here, and no benefit of putting expm1
in such a formula, regardless of how small x
is. Writing expm1(x) + 1
instead of exp(x)
is entirely pointless.
If your formula is exp(x) - 1
or exp(x) - cos(x)
, then there is potential loss of significance for small x
. This is not always a reason to rewrite; if you only plan to use this formula when x is 1 or more, there is no issue. If you are okay with absolute error being at machine epsilon level (1e-16 or so), and don't care much about relative error, there is no issue.
When the loss of significance occurs, it's up to the ultimate user of information to decide how much loss is acceptable. Often, getting 6 significant digits is quite enough for practical purposes, so losing 10 decimal digits out of double precision accuracy may be acceptable then. In this context, the formula exp(x) - 1
incurs unacceptable loss of precision when x
is smaller than 1e-10
. Indeed, the value of exp(x) - 1
is close to x
but exp(x)
looks like 1.00000000... with 10 digits after dot being 0; so only 6 digits remain from x itself.
Rewriting functions in a numerically safer form requires some human effort, to work out whatever algebraic or trigonometric identities are needed. Examples of such rewriting:
f = lambda x: np.exp(x) - np.cos(x)
g = lambda x: np.sqrt(x**2 + 1) - 1
Numerically safer form of the above:
f_safe = lambda x: np.expm1(x) + 2*np.sin(x/2)**2
g_safe = lambda x: x / (np.sqrt(x**2 + 1) + 1)
Rewriting np.exp(x) - np.cos(x)
as np.expmp(x) - np.cos(x) + 1
would have no benefit at all; one has to think through the entire computation to eliminate the subtraction of nearly-equal numbers.