Decreasing X to Zero. Computer errors?

So, I have the following piece of code:

x = 1
a = 0
for _ in range(18):
y = (e ** x - 1) / x
x = x/10
a = a + 1
print(a,"->", y)

as x approaches 0, this equation should be approaching 1, but on the 9th loop, the answer I get is this:

9 → 0.9999999939225288

I just wonder why it went under 1. This is REALLY bugging me, so if someone could help me figure out why this is less that 1 that would be really helpful.
Further more, from 17 and after the number just becomes 0. I assume that this is because the computer cannot tell a difference between the given number and zero.

Can someone confirm this for me? It is bugging the S**t out of me and I need to be sure.

Below is the first 20 calculated values (Everything else that isn’t posted is zero)
1 → 1.718281828459045
2 → 1.0517091807564771
3 → 1.005016708416795
4 → 1.0005001667083846
5 → 1.000050001667141
6 → 1.000005000006965
7 → 1.0000004999621834
8 → 1.00000004943368
9 → 0.9999999939225288
10 → 1.0000000827403708
11 → 1.0000000827403708
12 → 1.0000000827403708
13 → 1.0000889005823408
14 → 0.9992007221626408
15 → 0.9992007221626408
16 → 1.1102230246251565
17 → 0.0
18 → 0.0
19 → 0.0
20 → 0.0

Please help me. This hurts my brain so much and I need to know what is going on.

Hi Kodiak,

I’ll start by suggesting a couple of small improvements to your code.
Try this instead:

from math import exp
for a in range(18):
    x = 10**(-a)
    y = (exp(x) - 1)/x
    print(a, '-->', y)

There is no need for an unused loop variable while manually counting the
number of loops. Just use the loop variable as your counter.

In general, using exp(x) is more accurate than e**x.

Also in general, it is more accurate to compute values like your x in
one go, rather than by repeatedly dividing. Compare the results here:

>>> x = 1
>>> for i in range(10):
...     x = x/10  # repeated division: x = 1/10/10/10/10 ...
... 
>>> print(x)
1.0000000000000003e-10
>>> print(10**-10)  # do it in one calculation
1e-10

Notice that the repeated division is slightly off. That is because of
accumulated rounding error. Each time you divide by 10, the result has
to be rounded from the mathematically precise answer to just 64 bits of
memory. If you round, and then round again, and then round again,
sometimes the errors cancel but often they accumulate and the errors get
bigger.

The basic rule when using floating point maths is that the fewer
intermediate steps you have, the fewer opportunities there are for
rounding error. The more intermediate steps, the more likely that the
rounding errors accumulate.

1 Like

Here is an even better improvement to your code:

from math import expm1
for a in range(18):
    x = 10**(-a)
    y = expm1(x)/x
    print(a, '-->', y)

If you try that, I think you will find that the results are much closer
to the mathematically exact results.

The documentation for expm1 says:

Return exp(x)-1.

This function avoids the loss of precision involved in the direct 
evaluation of exp(x)-1 for small x.

So what happens with your code is that when x is very small, e**x or
exp(x) is very close to 1. When you subtract 1 from the result, most
of the precision is lost to rounding error. We call that “catastrophic
cancellation”.

So your calculation of e**x - 1 has lost nearly all of its digits.
Then when you divide by x, instead of getting something a tiny bit
bigger than 1, sometimes it rounds down to something a bit smaller than

  1. And eventually, e**x - 1 rounds to zero, and zero divided by x is
    just zero.

You can read more about the perils of floating point numbers here:

https://docs.python.org/3/faq/design.html#why-are-floating-point-calculations-so-inaccurate

https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues

1 Like

Here are a couple more useful resources.

The canonical, but quite advanced, description of what goes on when you
do floating point maths using a computer is this:

https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Here is an easier-to-digest version:

https://floating-point-gui.de/