Add square root finding algorithm.

This commit is contained in:
Oleksii Trekhleb 2019-03-23 13:44:24 +02:00
parent 4aecd5772f
commit 339ae02977
4 changed files with 172 additions and 0 deletions

View File

@ -73,6 +73,7 @@ a set of rules that precisely define a sequence of operations.
* `B` [Radian & Degree](src/algorithms/math/radian) - radians to degree and backwards conversion
* `B` [Fast Powering](src/algorithms/math/fast-powering)
* `A` [Integer Partition](src/algorithms/math/integer-partition)
* `A` [Square Root](src/algorithms/math/square-root) - Newton's method
* `A` [Liu Hui π Algorithm](src/algorithms/math/liu-hui) - approximate π calculations based on N-gons
* `A` [Discrete Fourier Transform](src/algorithms/math/fourier-transform) - decompose a function of time (a signal) into the frequencies that make it up
* **Sets**

View File

@ -0,0 +1,62 @@
# Square Root (Newton's Method)
In numerical analysis, a branch of mathematics, there are several square root
algorithms or methods of computing the principal square root of a non-negative real
number. As, generally, the roots of a function cannot be computed exactly.
The root-finding algorithms provide approximations to roots expressed as floating
point numbers.
Finding ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/bff86975b0e7944720b3e635c53c22c032a7a6f1) is
the same as solving the equation ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/6cf57722151ef19ba1ca918d702b95c335e21cad) for a
positive `x`. Therefore, any general numerical root-finding algorithm can be used.
**Newton's method** (also known as the NewtonRaphson method), named after
_Isaac Newton_ and _Joseph Raphson_, is one example of a root-finding algorithm. It is a
method for finding successively better approximations to the roots of a real-valued function.
Let's start by explaining the general idea of Newton's method and then apply it to our particular
case with finding a square root of the number.
## Newton's Method General Idea
The NewtonRaphson method in one variable is implemented as follows:
The method starts with a function `f` defined over the real numbers `x`, the function's derivative `f'`, and an
initial guess `x0` for a root of the function `f`. If the function satisfies the assumptions made in the derivation
of the formula and the initial guess is close, then a better approximation `x1` is:
![](https://wikimedia.org/api/rest_v1/media/math/render/svg/52c50eca0b7c4d64ef2fdca678665b73e944cb84)
Geometrically, `(x1, 0)` is the intersection of the `x`-axis and the tangent of
the graph of `f` at `(x0, f(x0))`.
The process is repeated as:
![](https://wikimedia.org/api/rest_v1/media/math/render/svg/710c11b9ec4568d1cfff49b7c7d41e0a7829a736)
until a sufficiently accurate value is reached.
![](https://upload.wikimedia.org/wikipedia/commons/e/e0/NewtonIteration_Ani.gif)
## Newton's Method of Finding a Square Root
As it was mentioned above, finding ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/bff86975b0e7944720b3e635c53c22c032a7a6f1) is
the same as solving the equation ![](https://wikimedia.org/api/rest_v1/media/math/render/svg/6cf57722151ef19ba1ca918d702b95c335e21cad) for a
positive `x`.
The derivative of the function `f(x)` in case of square root problem is `2x`.
After applying the Newton's formula (see above) we get the following equation for our algorithm iterations:
```text
x := x - (x² - S) / (2x)
```
The `x² S` above is how far away `x²` is from where it needs to be, and the
division by `2x` is the derivative of `x²`, to scale how much we adjust `x` by how
quickly `x²` is changing.
## References
- [Methods of computing square roots on Wikipedia](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots)
- [Newton's method on Wikipedia](https://en.wikipedia.org/wiki/Newton%27s_method)

View File

@ -0,0 +1,69 @@
import squareRoot from '../squareRoot';
describe('squareRoot', () => {
it('should throw for negative numbers', () => {
function failingSquareRoot() {
squareRoot(-5);
}
expect(failingSquareRoot).toThrow();
});
it('should correctly calculate square root with default tolerance', () => {
expect(squareRoot(0)).toBe(0);
expect(squareRoot(1)).toBe(1);
expect(squareRoot(2)).toBe(1);
expect(squareRoot(3)).toBe(2);
expect(squareRoot(4)).toBe(2);
expect(squareRoot(15)).toBe(4);
expect(squareRoot(16)).toBe(4);
expect(squareRoot(256)).toBe(16);
expect(squareRoot(473)).toBe(22);
expect(squareRoot(14723)).toBe(121);
});
it('should correctly calculate square root for integers with custom tolerance', () => {
let tolerance = 1;
expect(squareRoot(0, tolerance)).toBe(0);
expect(squareRoot(1, tolerance)).toBe(1);
expect(squareRoot(2, tolerance)).toBe(1.4);
expect(squareRoot(3, tolerance)).toBe(1.8);
expect(squareRoot(4, tolerance)).toBe(2);
expect(squareRoot(15, tolerance)).toBe(3.9);
expect(squareRoot(16, tolerance)).toBe(4);
expect(squareRoot(256, tolerance)).toBe(16);
expect(squareRoot(473, tolerance)).toBe(21.7);
expect(squareRoot(14723, tolerance)).toBe(121.3);
tolerance = 3;
expect(squareRoot(0, tolerance)).toBe(0);
expect(squareRoot(1, tolerance)).toBe(1);
expect(squareRoot(2, tolerance)).toBe(1.414);
expect(squareRoot(3, tolerance)).toBe(1.732);
expect(squareRoot(4, tolerance)).toBe(2);
expect(squareRoot(15, tolerance)).toBe(3.873);
expect(squareRoot(16, tolerance)).toBe(4);
expect(squareRoot(256, tolerance)).toBe(16);
expect(squareRoot(473, tolerance)).toBe(21.749);
expect(squareRoot(14723, tolerance)).toBe(121.338);
tolerance = 10;
expect(squareRoot(0, tolerance)).toBe(0);
expect(squareRoot(1, tolerance)).toBe(1);
expect(squareRoot(2, tolerance)).toBe(1.4142135624);
expect(squareRoot(3, tolerance)).toBe(1.7320508076);
expect(squareRoot(4, tolerance)).toBe(2);
expect(squareRoot(15, tolerance)).toBe(3.8729833462);
expect(squareRoot(16, tolerance)).toBe(4);
expect(squareRoot(256, tolerance)).toBe(16);
expect(squareRoot(473, tolerance)).toBe(21.7485631709);
expect(squareRoot(14723, tolerance)).toBe(121.3383698588);
});
it('should correctly calculate square root for integers with custom tolerance', () => {
expect(squareRoot(4.5, 10)).toBe(2.1213203436);
expect(squareRoot(217.534, 10)).toBe(14.7490338667);
});
});

View File

@ -0,0 +1,40 @@
/**
* Calculates the square root of the number with given tolerance (precision)
* by using Newton's method.
*
* @param number - the number we want to find a square root for.
* @param [tolerance] - how many precise numbers after the floating point we want to get.
* @return {number}
*/
export default function squareRoot(number, tolerance = 0) {
// For now we won't support operations that involves manipulation with complex numbers.
if (number < 0) {
throw new Error('The method supports only positive integers');
}
// Handle edge case with finding the square root of zero.
if (number === 0) {
return 0;
}
// We will start approximation from value 1.
let root = 1;
// Delta is a desired distance between the number and the square of the root.
// - if tolerance=0 then delta=1
// - if tolerance=1 then delta=0.1
// - if tolerance=2 then delta=0.01
// - and so on...
const requiredDelta = 1 / (10 ** tolerance);
// Approximating the root value to the point when we get a desired precision.
while (Math.abs(number - (root ** 2)) > requiredDelta) {
// Newton's method reduces in this case to the so-called Babylonian method.
// These methods generally yield approximate results, but can be made arbitrarily
// precise by increasing the number of calculation steps.
root -= ((root ** 2) - number) / (2 * root);
}
// Cut off undesired floating digits and return the root value.
return Math.round(root * (10 ** tolerance)) / (10 ** tolerance);
}