diff --git a/README.md b/README.md index 151ebde372..9f11f2b305 100644 --- a/README.md +++ b/README.md @@ -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** diff --git a/src/algorithms/math/square-root/README.md b/src/algorithms/math/square-root/README.md new file mode 100644 index 0000000000..7f9713b5f6 --- /dev/null +++ b/src/algorithms/math/square-root/README.md @@ -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 Newton–Raphson 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 Newton–Raphson 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) diff --git a/src/algorithms/math/square-root/__test__/squareRoot.test.js b/src/algorithms/math/square-root/__test__/squareRoot.test.js new file mode 100644 index 0000000000..9679d1a1c5 --- /dev/null +++ b/src/algorithms/math/square-root/__test__/squareRoot.test.js @@ -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); + }); +}); diff --git a/src/algorithms/math/square-root/squareRoot.js b/src/algorithms/math/square-root/squareRoot.js new file mode 100644 index 0000000000..19779bf34c --- /dev/null +++ b/src/algorithms/math/square-root/squareRoot.js @@ -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); +}