Algorithm for computing base-10 logarithm in Python - python

I have tried to create a program to calculate the base-10 logarithm based on the Taylor series-based algorithm described in "The Mathematical-Function Computation Handbook" (I found an online copy via my University's library).
A similar algorithm is given on another question on StackOverflow for which I cannot find the link right now.
10.3.2 Computing logarithms in a decimal base
For a decimal base, the base-10 logarithm is the natural choice, and the decomposition of the argument into an
exponent and a fraction gives us a decimal representation:
x = (−1)^s × f × 10^n, either f = 0 exactly, or f is in [1/10, 1).
If f ≤√1/10, set f = 10 × f and n = n − 1, so that f is now in the interval (√1/10,√10]. Then introduce a change of variable, a Taylor-series expansion, and a polynomial representation of that expansion:
z = (f − 1)/( f + 1),
f = (1 + z)/(1 − z),
D = 2 log10(e)
= 2/ log(10)
log10( f) = D × (z + z3/3 + z5/5 + z7/7 + z9/9 + z11/11 + · · · )
≈ D × z + z3Q(z2), polynomial fit incorporates D in Q(z2).
For f in (√1/10,√10], we have z in roughly [−0.5195,+0.5195]. The wider range of z requires longer polynomials compared to the binary case, and also makes the correction term z3Q(z2) relatively larger. Its magnitude does not exceed |0.35z|, so it provides barely one extra decimal digit of precision, instead of two. Accurate computation of z is easier than in the binary case: just set z = fl(fl(f−12)−12)/fl(f+1).
For this I wrote this program in Python:
def log10(x):
n = 0.0 #Start exponent of base 10
while (x >= 1.0):
x = x/10.0
n+=1
# if x <= sqrt(1/10)
if(x<=0.316227766016838):
x = x*10.0
n = n-1
#Produce a change of variable
z = (x-1.0)/(x+1.0)
D = 4.60517018598809 #2*log10(e)
sum = z
for k in range(3,111,2):
sum+=(z**k)/k
return D*n*sum
I compared the results to the math.log10 function, and the results are not as expected. My biggest issue when debugging is understanding the algorithm and why it works.

Here is my source code after the suggested corrections (changed return statement to D*sum+n fixed the value of D, and changed if(x<=0.316227766016838) to while(x<=0.316227766016838). I added some if statements to handle exceptional cases.
The code below works well within my target precision of 6 digits (I tested it with very small input, large input).
def log10(x):
# Handle exceptional cases
if (x == 1):
return 0
if (x == 0):
return float('-Inf')
if (x < 0):
return float('nan')
n = 0 #Start exponent of base 10
while (x >= 1.0):
x = x/10.0
n+=1
# if x <= sqrt(1/10)
while(x<=0.316227766016838):
x = x*10.0
n = n-1
#Produce a change of variable
z = (x-1.0)/(x+1.0)
D = 0.868588964 #2*log10(e)
#Taylor series
sum = z
for k in range(3,23,2):
sum+=(z**k)/k
return D*sum+n

Related

How to find Log10(x) without Math library [duplicate]

I have tried to create a program to calculate the base-10 logarithm based on the Taylor series-based algorithm described in "The Mathematical-Function Computation Handbook" (I found an online copy via my University's library).
A similar algorithm is given on another question on StackOverflow for which I cannot find the link right now.
10.3.2 Computing logarithms in a decimal base
For a decimal base, the base-10 logarithm is the natural choice, and the decomposition of the argument into an
exponent and a fraction gives us a decimal representation:
x = (−1)^s × f × 10^n, either f = 0 exactly, or f is in [1/10, 1).
If f ≤√1/10, set f = 10 × f and n = n − 1, so that f is now in the interval (√1/10,√10]. Then introduce a change of variable, a Taylor-series expansion, and a polynomial representation of that expansion:
z = (f − 1)/( f + 1),
f = (1 + z)/(1 − z),
D = 2 log10(e)
= 2/ log(10)
log10( f) = D × (z + z3/3 + z5/5 + z7/7 + z9/9 + z11/11 + · · · )
≈ D × z + z3Q(z2), polynomial fit incorporates D in Q(z2).
For f in (√1/10,√10], we have z in roughly [−0.5195,+0.5195]. The wider range of z requires longer polynomials compared to the binary case, and also makes the correction term z3Q(z2) relatively larger. Its magnitude does not exceed |0.35z|, so it provides barely one extra decimal digit of precision, instead of two. Accurate computation of z is easier than in the binary case: just set z = fl(fl(f−12)−12)/fl(f+1).
For this I wrote this program in Python:
def log10(x):
n = 0.0 #Start exponent of base 10
while (x >= 1.0):
x = x/10.0
n+=1
# if x <= sqrt(1/10)
if(x<=0.316227766016838):
x = x*10.0
n = n-1
#Produce a change of variable
z = (x-1.0)/(x+1.0)
D = 4.60517018598809 #2*log10(e)
sum = z
for k in range(3,111,2):
sum+=(z**k)/k
return D*n*sum
I compared the results to the math.log10 function, and the results are not as expected. My biggest issue when debugging is understanding the algorithm and why it works.
Here is my source code after the suggested corrections (changed return statement to D*sum+n fixed the value of D, and changed if(x<=0.316227766016838) to while(x<=0.316227766016838). I added some if statements to handle exceptional cases.
The code below works well within my target precision of 6 digits (I tested it with very small input, large input).
def log10(x):
# Handle exceptional cases
if (x == 1):
return 0
if (x == 0):
return float('-Inf')
if (x < 0):
return float('nan')
n = 0 #Start exponent of base 10
while (x >= 1.0):
x = x/10.0
n+=1
# if x <= sqrt(1/10)
while(x<=0.316227766016838):
x = x*10.0
n = n-1
#Produce a change of variable
z = (x-1.0)/(x+1.0)
D = 0.868588964 #2*log10(e)
#Taylor series
sum = z
for k in range(3,23,2):
sum+=(z**k)/k
return D*sum+n

Error for trapezoidal method increases with N if integral equals 0 [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed last month.
I've written a simple implementation of the trapezoidal method to find the integral of sine:
def trapezoidal_method(a: float, b: float, n: int) -> float:
length = (b - a)/n
integral = 0
start = a
integral += math.sin(a)/2
for _ in range(1, n):
integral += math.sin(start + length)
start += length
integral += math.sin(b)/2
return integral * length
It converges as expected for most situations...
but it goes crazy when the result should be 0 (like integrating from -1 to 1):
How do I fix this? Tried explicitly casting n to float and tried using the decimal library, didn't change anything
That's (mainly) due to floating number precision. In the case of functions as sine, which are transcendental by nature, only an approximation of it is possible (or depends on its implementation).
By adding a (very quick!) a print of the x and y values at each term of the sequence you will see the odd symmetry is not respected... or at least with a small error. So, f(x) = -f(-x) + error.
import math
def trapezoidal_method(func, a: float, b: float, n: int) -> float:
length = (b - a)/n
integral = 0
start = a
integral += func(a)/2
for _ in range(1, n):
integral += func(start + length)
start += length
integral += func(b)/2
# evaluation for each term of the sequence
print(0, f'x={a} y={func(a)}')
x = a
for i in range(1, n):
x += length
y = func(x)
print(i, f'{x=} {y=}')
print(n, f'x={b} y={func(b)}')
return integral * length
A test with sin
a, b, n = -1, 1, 7
func = math.sin
tm = trapezoidal_method(func, a, b, n)
Output
0 x=-1 y=-0.8414709848078965
1 x=-0.7142857142857143 y=-0.6550778971785186
2 x=-0.4285714285714286 y=-0.41557185499305205 # <-
3 x=-0.1428571428571429 y=-0.1423717297922637
4 x= 0.1428571428571428 y=0.1423717297922636
5 x= 0.4285714285714285 y=0.41557185499305194 # <-
6 x= 0.7142857142857142 y=0.6550778971785185
8 x= 1 y=0.8414709848078965
-1.586032892321652e-16
Notice that also the x-values maybe slightly different!
By choosing suitable limits of integration and amount of strips then the in some cases the integral maybe 0. Here an example with the diagonal function
a, b, n = -1, 1, 6
func = lambda x: x
tm = trapezoidal_method(func, a, b, n)
Output
0 x=-3.0 y=-3.0
1 x=-2.0 y=-2.0
2 x=-1.0 y=-1.0
3 x=0.0 y=0.0
4 x=1.0 y=1.0
5 x=2.0 y=2.0
7 x=3.0 y=3.0
0.0

Arithmetic precision problems with large numbers

I am writing a program that handles numbers as large as 10 ** 100, everything looks good when dealing with smaller numbers but when values get big I get these kind of problems:
>>> N = 615839386751705599129552248800733595745824820450766179696019084949158872160074326065170966642688
>>> ((N + 63453534345) / sqrt(2)) == (N / sqrt(2))
>>> True
Clearly the above comparision is false, why is this happening?
Program code:
from math import *
def rec (n):
r = sqrt (2)
s = r + 2
m = int (floor (n * r))
j = int (floor (m / s))
if j <= 1:
return sum ([floor (r * i) for i in range (1, n + 1)])
assert m >= s * j and j > 1, "Error: something went wrong"
return m * (m + 1) / 2 - j * (j + 1) - rec (j)
print rec (1e100)
Edit:
I don't think my question is a duplicate of the linked question above because the decimal points in n, m and j are not important to me and I am looking for a solution to avoid this precision issue.
You can’t retain the precision you want while dividing by standard floating point numbers, so you should instead divide by a Fraction. The Fraction class in the fractions module lets you do exact rational arithmetic.
Of course, the square root of 2 is not rational. But if the error is less than one part in 10**100, you’ll get the right result.
So, how to compute an approximation to sqrt(2) as a Fraction? There are several ways to do it, but one simple way is to compute the integer square root of 2 * 10**200, which will be close to sqrt(2) * 10**100, then just make that the numerator and make 10**100 the denominator.
Here’s a little routine in Python 3 for integer square root.
def isqrt(n):
lg = -1
g = (1 >> n.bit_length() // 2) + 1
while abs(lg - g) > 1:
lg = g
g = (g + n//g) // 2
while g * g > n:
g -= 1
return g
You should be able to take it from there.

Python 3 - float(X) * i = int(Z)

I have a very large number, both before and after the decimal, but for this I'll just call it 4.58.
I want to know the number, Y, that will yield me an integer if multiplied by X and not any sort of float number.
Here is my code:
from decimal import *
setcontext(ExtendedContext)
getcontext().prec = 300
x=Decimal('4.58')
while True:
i=1
a=Decimal(i*x)
if float(a).is_integer():
print(i*x)
break
else:
i=+1
However, this method is incredibly slow and inefficient. I was wondering how could I implement continued fractions or some other method to make it predict the value of Y?
Edit
The decimal module stores float numbers more accurately (As strings), so 0.5 won't become 0.499999999.
Edit 2
I've got X (4.58).
I want to know what number will multiply by X to make an integer; as efficiently as possible.
Edit 3
Okay, maybe not my best question yet.
Here's my dilemma.
I've got a number spat out from a trivial programme I made. That number is a decimal number, 1.5.
All I want to do is find what integer will multiply by my decimal to yield another integer.
For 1.5, the best answer will be 2. (1.5*2=3) (float*int=int)
My while-loop above will do that, eventually, but I just wanted to know whether there was a better way to do this, such as continued fractions; and if there was, how could I implement it.
Edit 4
Here's my code thanks to user6794072. It's lengthy but functional.
from gmpy2 import mpz, isqrt
from fractions import Fraction
import operator
import functools
from decimal import *
setcontext(ExtendedContext)
getcontext().prec = 300
def factors(n):
n = mpz(n)
result = set()
result |= {mpz(1), n}
def all_multiples(result, n, factor):
z = n
f = mpz(factor)
while z % f == 0:
result |= {f, z // f}
f += factor
return result
result = all_multiples(result, n, 2)
result = all_multiples(result, n, 3)
for i in range(1, isqrt(n) + 1, 6):
i1 = i + 1
i2 = i + 5
if not n % i1:
result |= {mpz(i1), n // i1}
if not n % i2:
result |= {mpz(i2), n // i2}
return result
j=Decimal('4.58')
a=(Fraction(j).numerator)
b=(Fraction(j).denominator)
y=(factors(a))
x=(factors(b))
q=([item for item in x if item not in y])
w=([item for item in y if item not in x]) q.extend(w)
p=(functools.reduce(operator.mul, q, 1)) ans=(p*j)
print(ans)
If I understand your question correctly, you want to find the smallest integer (i) that can be multiplied to a non-integer number (n) so that:
i*n is an integer
I would do this by finding the factors of the numerator and denominator for n. In your example, if n = 4.58, then you can extract 458 for the numerator and 100 for the denominator.
The multiples of 458 are 2 and 229
The multiples of 100 are 2, 2, 5, 5
You can cross off one instance of 2 for the numerator and denominator. Then your solution is just multiplying the remaining factors in the denominator: in this case, 2*5*5 or 50.
Well think of what if you wanted to reach z = 1 and then use the fact that z == z * 1 to scale the answer. For any float x != 0.0, y = 1/x will yield z = 1, so for arbitrary integer z, just use y = z/x.
I'm not a Python programmer, but what about round function?

Calculate e in Python [duplicate]

This question already has answers here:
Python basic math [closed]
(3 answers)
Closed 8 years ago.
What would the easiest way to represent the following equation be?
Just to clarify, my question is asking for some code that calculates the answer to the equation.
There are two problems with this:
The summation warrants an infinite loop which is impossible to get an answer from
I hoping for a long, detailed answer (maybe to 40 digits or so).
If you need more precision, you could try to use Fraction:
from fractions import Fraction # use rational numbers, they are more precise than floats
e = Fraction(0)
f = Fraction(1)
n = Fraction(1)
while True:
d = Fraction(1) / f # this ...
if d < Fraction(1, 10**40): # don't continue if the advancement is too small
break
e += d # ... and this are the formula you wrote for "e"
f *= n # calculate factorial incrementally, faster than calling "factorial()" all the time
n += Fraction(1) # we will use this for calculating the next factorial
print(float(e))
or Decimal:
from decimal import Decimal, getcontext
getcontext().prec = 40 # set the precision to 40 places
e = Decimal(0)
f = Decimal(1)
n = Decimal(1)
while True:
olde = e
e += Decimal(1) / f
if e == olde: # if there was no change in the 40 places, stop.
break
f *= n
n += Decimal(1)
print(float(e))
So here is e in 1000 places:
2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466391932003059921817413596629043572900334295260595630738132328627943490763233829880753195251019011573834187930702154089149934884167509244761460668082264800168477411853742345442437107539077744992069551702761838606261331384583000752044933826560297606737113200709328709127443747047230696977209310141692836819025515108657463772111252389784425056953696770785449969967946864454905987931636889230098793127736178215424999229576351482208269895193668033182528869398496465105820939239829488793320362509443117301238197068416140397019837679320683282376464804295311802328782509819455815301756717361332069811250996181881593041690351598888519345807273866738589422879228499892086805825749279610484198444363463244968487560233624827041978623209002160990235304369941849146314093431738143640546253152096183690888707016768396424378140592714563549061303107208510383750510115747704171898610687396965521267154688957035044
To see more clearly what it does, here is its simplified version:
e = f = 1.0
for i in range(2, 16):
e += 1.0 / f
f *= i
print(e)
The obvious solution would be
import math
def e(n=10):
return sum(1 / float(math.factorial(i)) for i in range(n))
but it loses precision around n=20 (the error as compared to math.e is around 10^-16)
40-digits precision might be a challenge, and might require arbitrary precision arithmetic
I do not really see the point to have such precise "e" value since you won't be able to perform any calculations with such precision (if you do anything to it, you will lose that precision, unless you do everything in some arbitrary precision arithmetic).
To produce a similar result to my answer from a question on approximating pi:
from functools import wraps
def memoize(f):
"""Store and retrive previous results of the decorated function f."""
cache = {}
#wraps(f)
def func(*args):
if args not in cache:
cache[args] = f(*args)
return cache[args]
return func
#memoize
def fact(n):
"""Recursively calculates n!."""
if n <= 1:
return 1
return n * fact(n - 1)
def inverse_fact_n(start_n=0):
"""Generator producing the infinite series 1/n!."""
numerator = 1.0
denominator = start_n
while True:
yield numerator / fact(denominator)
denominator += 1
def approximate_e(steps=None, tolerance=None):
"""Calculate an approximation of e from summation of 1/n!."""
if steps is None and tolerance is None:
raise ValueError("Must supply one of steps or tolerance.")
series = inverse_fact_n()
if steps is not None: # stepwise method
return sum(next(series) for _ in range(steps))
output = 0 # tolerance method
term = next(series)
while abs(term) > tolerance:
output += term
term = next(series)
return output
if __name__ == "__main__":
from math import e
print("math.e:\t\t{0:.20f}.".format(e))
stepwise = approximate_e(steps=100)
print("Stepwise:\t{0:.20f}.".format(stepwise))
tolerated = approximate_e(tolerance=0.0000000001)
print("Tolerated:\t{0:.20f}.".format(tolerated))
The function approximate_e allows you to specify either:
A number of steps ("I want it to take this long"); or
A desired tolerance ("I want it to be this accurate").
There is some relatively advanced Python around this (e.g. the memoizing decorator function and the generator function to produce the series), but you can just focus on the main function, where next(series) gives you the next term of the summation.
This gives me the output:
math.e: 2.71828182845904509080.
Stepwise: 2.71828182845904553488.
Tolerated: 2.71828182844675936281.
The most effective way is to use the properties of the exponential function.
exp(x)=(exp(x/N))^N
Thus you compute x=exp(2^(-n)) with 2n bits more precision than required in the final result, and compute e by squaring the result n times.
For small numbers x, the error of truncating the series for exp(x) at the term with power m-1 is smaller than two times the next term with power m.
To summarize, to compute e with a precision/accuracy of d bit, you select some medium large n and select m such that
2^(1-mn)/m! is smaller than 2^(-d-2n)
This determination of m can also be done dynamically (using Decimal as in the answer of user22698)
from decimal import Decimal, getcontext
def eulernumber(d):
dd=d
n=4
while dd > 1:
dd /= 8
n += 1
getcontext().prec = d+n
x = Decimal(1)/Decimal(1 << n)
eps = Decimal(1)/Decimal(1 << (1 + (10*d)/3 ))
term = x
expsum = Decimal(1) + x
m = 2
while term > eps:
term *= x / Decimal(m)
m += 1
expsum += term
for k in range(n):
expsum *= expsum
getcontext().prec = d
expsum += Decimal(0)
return expsum
if __name__ == "__main__":
for k in range(1,6):
print(k,eulernumber(4*k))
for k in range(10,13):
print(k,eulernumber(4*k))
with output
( 1, Decimal('2.718'))
( 2, Decimal('2.7182818'))
( 3, Decimal('2.71828182846'))
( 4, Decimal('2.718281828459045'))
( 5, Decimal('2.7182818284590452354'))
(10, Decimal('2.718281828459045235360287471352662497757'))
(11, Decimal('2.7182818284590452353602874713526624977572471'))
(12, Decimal('2.71828182845904523536028747135266249775724709370'))
See the (unix/posix) bc math library for a more professional implementation of this idea, also for the logarithm and trig functions. The code of the exponential function is even given as example in the man page.

Categories

Resources