Skip to content

Commit a2b98cd

Browse files
committed
Add algorithm for approximating the nth root with Newton's Method
1 parent ed1900f commit a2b98cd

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

maths/numerical_analysis/nth_root.py

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Approximate the nth root of a real number using the Newton's Method.
3+
4+
The nth root of a real number R can be computed with Newton's method,
5+
which starts with an initial guess x_0 and then iterates using the
6+
recurrence relation:
7+
8+
x_{k + 1} = x_k - ((x_k)**n - R)/(n*(x_k)**(n-1))
9+
10+
The recurrence relation can be rewritten for computational efficiency:
11+
12+
x_{k + 1} = (n-1)/n*x_k + R/(n*(x_k)**(n-1))
13+
14+
Given a tolerance TOL, a stopping criterion can be set as:
15+
16+
abs(x_{k + 1} - x_k) < TOL
17+
18+
References:
19+
- https://en.wikipedia.org/wiki/Nth_root#Using_Newton's_method
20+
- Sauer, T. (2011): Numerical analysis.
21+
USA. Addison-Wesley Publishing Company.
22+
"""
23+
24+
from math import pow
25+
26+
27+
def nth_root(radicand: float, index: int, tolerance: float = 0.0001) -> float:
28+
"""
29+
Approximate the nth root of the radicand for the given index
30+
31+
Args:
32+
radicand: number from which the root is taken
33+
index: positive integer which is the degree of the root
34+
tolerance: positive real number that establishes the stopping criterion
35+
36+
Returns:
37+
new_aproximation: approximation of the nth root of the radicand for the
38+
given index
39+
40+
Raises:
41+
TypeError: radicand is not real number
42+
TypeError: index is not integer
43+
ValueError: index is not positive integer
44+
TypeError: tolerance is not real number
45+
ValueError: tolerance is not positive real number
46+
ValueError: math domain error
47+
48+
>>> round(nth_root(9, 2),1)
49+
3.0
50+
51+
>>> int(round(nth_root(-8, 3, 0.001)))
52+
-2
53+
54+
>>> int(round(nth_root(256, 4, 0.001)))
55+
4
56+
57+
>>> round(nth_root(2, 2), 5)
58+
1.41421
59+
60+
>>> round(nth_root(0.25, 2, 0.00000001), 1)
61+
0.5
62+
63+
>>> round(nth_root(-8/27, 3, 0.0000001), 5)
64+
-0.66667
65+
66+
>>> nth_root(0, 2, 0.1)
67+
0.0
68+
69+
>>> nth_root(0.0, 5)
70+
0.0
71+
72+
>>> all(abs(nth_root(k, k, 0.00000001) - k**(1/k)) <= 1e-10 for k in range(1,10))
73+
True
74+
75+
>>> nth_root('invalid input', 3, 0.0001)
76+
Traceback (most recent call last):
77+
...
78+
TypeError: radicand must be real number, not str
79+
80+
>>> nth_root(4, 0.5, 0.0001)
81+
Traceback (most recent call last):
82+
...
83+
TypeError: index must be integer, not float
84+
85+
>>> nth_root(16, -4, 0.001)
86+
Traceback (most recent call last):
87+
...
88+
ValueError: index must be positive integer, -4 <= 0
89+
90+
>>> nth_root(4, 2, '0.000001')
91+
Traceback (most recent call last):
92+
...
93+
TypeError: tolerance must be real number, not str
94+
95+
>>> nth_root(9, 2, -0.01)
96+
Traceback (most recent call last):
97+
...
98+
ValueError: tolerance must be positive real number, -0.01 <= 0
99+
100+
>>> nth_root(-256, 4, 0.0001)
101+
Traceback (most recent call last):
102+
...
103+
ValueError: math domain error, radicand must be nonnegative for even index
104+
"""
105+
if not isinstance(radicand, (int, float)):
106+
error_message = f"radicand must be real number, not {type(radicand).__name__}"
107+
raise TypeError(error_message)
108+
109+
if not isinstance(index, int):
110+
error_message = f"index must be integer, not {type(index).__name__}"
111+
raise TypeError(error_message)
112+
113+
if index <= 0:
114+
error_message = f"index must be positive integer, {index} <= 0"
115+
raise ValueError(error_message)
116+
117+
if not isinstance(tolerance, (int, float)):
118+
error_message = f"tolerance must be real number, not {type(tolerance).__name__}"
119+
raise TypeError(error_message)
120+
121+
if tolerance <= 0:
122+
error_message = f"tolerance must be positive real number, {tolerance} <= 0"
123+
raise ValueError(error_message)
124+
125+
if radicand < 0 and index % 2 == 0:
126+
error_message = "math domain error, radicand must be nonnegative for even index"
127+
raise ValueError(error_message)
128+
129+
if radicand == 0.0:
130+
return 0.0
131+
132+
# Set initial guess
133+
new_aproximation = radicand
134+
# Set old_aproximation to enter the loop
135+
old_aproximation = new_aproximation + tolerance + 0.1
136+
137+
# Iterate as long as the stop criterion is not satisfied
138+
while tolerance <= abs(old_aproximation - new_aproximation):
139+
old_aproximation = new_aproximation
140+
# Compute new_approximation with the recurrence relation described above
141+
first_summand = (index - 1) / index * old_aproximation
142+
second_summand = radicand / (index * pow(old_aproximation, index - 1))
143+
new_aproximation = first_summand + second_summand
144+
145+
return new_aproximation
146+
147+
148+
if __name__ == "__main__":
149+
import doctest
150+
151+
doctest.testmod()

0 commit comments

Comments
 (0)