MATLAB: Finding the exponential of a very small number

exponential

Hi
I wish to find the exact exponential of numbers in the range of 10^-23. Matlab gives me the answer as 1 for exp(3.528393650226818e-23). This is not right. How do I accomplish this?

Best Answer

Multiple ways to solve this problem. Sorry, but quad precision is not an option. There is no way to achieve quad precision, at least without recourse to some other tool, or exporting the results to a language where quad precision is available.
Brute force, without resource to the symbolic toolbox ... Use my HPF toolbox, which is free to download. I'll use 100 decimal digits of precision, even though
DefaultNumberOfDigits 100
s = hpf('-6.471606349773182e-23');
d = hpf('-6.604939683106516e-23');
See that HPF has no problem in evaluating expressions like those that you have.
exp(s)
ans =
0.9999999999999999999999352839365022681800000020940844373212284440930971466223949024568510358194049670
log(1+exp(s))
ans =
0.6931471805599453094171997634264277021655001348837763634509871205166792020337447156058633269050605702
So A is just
A = hpf('1.449056603773585e+19')*(log(1+exp(s))-log(1+exp(d)))
A =
0.000009660377358490614968553143308576941771731705532455911053800738735222612516743226005743174365407550000
As a check with the symbolic toolbox, it yields:
s = sym('-6.471606349773182e-23');
d = sym('-6.604939683106516e-23');
A = sym('1.449056603773585e+19')*(log(1+exp(s))-log(1+exp(d)))
A =
0.0000096603773584906138365242807537049
In fact, the symbolic toolbox loses some precision here, because it used limited precision to do some of those computations. And, since you only provided those numbers to about 20 significant digits, what can you expect? But it does reasonably well. The exact result, reporting it as 32 significant digits is:
0.0000096603773584906149685531433085769
Can we get that result by using only numerical methods in double precision? Brute force will of course fail, as you found.
exp(-6.471606349773182e-23)
ans =
1
A = 1.449056603773585e+19*(log(1+exp(s))-log(1+exp(d)))
A =
0
So in order to get a viable result for A using double precision, one needs to resort to numerical analysis. We need to understand that even if we could compute this for very tiny numbers s and d, the results log(1+exp(s)) and log(1+exp(d)) are very close to each other. Using HPF, the exact results out to 40 decimal digits are:
log(1+exp(s))
ans =
0.6931471805599453094171997634264277021655
log(1+exp(d))
ans =
0.6931471805599453094171990967597610354955
So, when we subtract the two results, we will see massive subtractive cancellation. HPF is able to give the exact results here because I did the computations in sufficiently high precision, but then only reported the first 40 digits.
Going back to double precision, even tools in MATLAB like log1p must fail, because exp(s) returns exactly 1. As far as MATLAB is concerned, with doubles for s and d, these two results are identical.
log(1+exp(s)) == log(1+exp(d))
ans =
logical
1
Ok, so can we do something? Yes. With some mental effort. Note that is s is TINY, then exp(s) is essentially 1. And then we lose accuracy because of the massive subtractive cancellation, because both logs are returning essentially log(2).
What if we computed the result of
log(1+exp(x)) - log(2)
Of course, we need to do this computation accurately, for very tiny x. First see that
log(1+exp(x)) - log(2) = log((1+exp(x))/2)
When we do the subtraction, those -log(2) terms will exactly cancel each other out. But they enable us to do the computation in double precision. We will need to use a Taylor series. I'll use the symbolic toolbox here, because I'm too lazy to do the algebra on paper.
syms x
taylor(log((1+exp(x))/2))
ans =
-x^4/192 + x^2/8 + x/2
For tiny values of x of magnitude smaller than eps, we can truncate that series to one term. But we can see what happens if we use 2 terms anyway.
logapprox1 = @(x) x/2;
logapprox2 = @(x) x/2 + x.^2/8;
s = -6.471606349773182e-23;
d = -6.604939683106516e-23;
format long g
A = 1.449056603773585e+19*(logapprox1(s)-logapprox1(d))
A =
9.66037735849058e-06
A = 1.449056603773585e+19*(logapprox2(s)-logapprox2(d))
A =
9.66037735849058e-06
As you can see, the two results are both reasonably accurate. Remember, the exact result (using scientific notation) was:
9.6603773584906149685531433085769e-6
So our double precision computation was correct out to almost the full precision available using doubles.
The trick was to use finesse. Brute force only works if you are willing and able to do the computation using the proper tools that will support brute force. As I showed, even the symbolic toolbox missed a few digits out there at the end.