From dd9481e22b2a512e5ba6b5c4e280d266c688318f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Sun, 20 Feb 2022 16:19:36 -0400 Subject: [PATCH 01/12] Create ArithmeticGeometricMean.js --- Maths/ArithmeticGeometricMean.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Maths/ArithmeticGeometricMean.js diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js new file mode 100644 index 0000000000..d509ab857b --- /dev/null +++ b/Maths/ArithmeticGeometricMean.js @@ -0,0 +1,24 @@ +/** + * @function agm + * @description This finds the Arithmetic-Geometric Mean between any 2 numbers. + * @param {Number} a - 1st number, also used to store Arithmetic Mean. + * @param {Number} g - 2nd number, also used to store Geometric Mean. + * @return {Number} - AGM of both numbers. + * @see [AGM](https://en.wikipedia.org/wiki/Arithmetic%E2%80%93geometric_mean) + */ + + export const agm = (a, g) => { + if (a === g) return a; //avoid rounding errors, and increase efficiency + let x; //temp var, for detecting rounding differences between `sqrt` and division + do { + [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] + } while (a !== x && !isNaN(a)); + /* + `x !== a` ensures the return value has full precision, + and prevents infinite loops caused by rounding errors (no need for "epsilon"). + Precision is not the same as accuracy, but they're related. + This function isn't always 100% accurate (round-errors), but at least is more than 95% accurate. + `!isNaN(x)` prevents infinite loops caused by invalid inputs like: negatives, NaNs and both Infinities. + */ + return a +} From 6ab3f4bb7ad7a93e961aae21433c83d654fd2fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Tue, 22 Feb 2022 23:06:44 -0400 Subject: [PATCH 02/12] Finally added the test script for AGM --- Maths/test/ArithmeticGeometricMean.test.js | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Maths/test/ArithmeticGeometricMean.test.js diff --git a/Maths/test/ArithmeticGeometricMean.test.js b/Maths/test/ArithmeticGeometricMean.test.js new file mode 100644 index 0000000000..df9e8f8068 --- /dev/null +++ b/Maths/test/ArithmeticGeometricMean.test.js @@ -0,0 +1,54 @@ +import { agm } from '../ArithmeticGeometricMean.js' + +describe('Tests for AGM', () => { + it('should be a function', () => { + expect(typeof agm).toEqual('function') + }) + + it('number of parameters should be 2', () => { + expect(agm.length).toEqual(2) + }) + + const m = 0x100; //scale for rand + + it('should return NaN if any or all params has a negative argument', () => { + //I multiplied by minus one, because the sign inversion is more clearly visible + expect(agm(-1 * Math.random() * m, Math.random() * m)).toBe(NaN) + expect(agm(Math.random() * m, -1 * Math.random() * m)).toBe(NaN) + expect(agm(-1 * Math.random() * m, -1 * Math.random() * m)).toBe(NaN) + }) + + it('should return Infinity if any arg is Infinity and the other is not 0', () => { + expect(agm(Math.random() * m + 1, Infinity)).toEqual(Infinity) + expect(agm(Infinity, Math.random() * m + 1)).toEqual(Infinity) + expect(agm(Infinity, Infinity).toEqual(Infinity)) + }) + + it('should return NaN if some arg is Infinity and the other is 0', () => { + expect(agm(0, Infinity).toBe(NaN)) + expect(agm(Infinity, 0).toBe(NaN)) + }) + + it('should return +0 if any or all args are +0 or -0, and return -0 if all are -0', () => { + expect(agm(Math.random() * m, -0)).toBe(0) + expect(agm(0, Math.random() * m)).toBe(0) + expect(agm(0, -0).toBe(0)) + expect(agm(-0, 0).toBe(0)) + expect(agm(-0, -0).toBe(-0)) + }) + + it('should return NaN if any or all args are NaN', () => { + expect(agm(Math.random() * m, NaN)).toBe(NaN) + expect(agm(NaN, Math.random() * m)).toBe(NaN) + expect(agm(NaN, NaN).toBe(NaN)) + }) + + it('should return an accurate approximation of the AGM between 2 valid input args', () => { + //all the constants are provided by WolframAlpha + expect(agm(1, 2)).toBeCloseTo(1.4567910310469068691864323832650819749738639432213055907941723832) + expect(agm(2, 256)).toBeCloseTo(64.4594071943866695986665434983250898480227029006463800294306182) + expect(agm(55555, 34)).toBeCloseTo(9933.40472395519953051873484897963370925448369441581220656590) + //test "unsafe" numbers + expect(agm(2 ** 48, 3 ** 27).toBeCloseTo(88506556379265.712723873253375677194780)) + }) +}) \ No newline at end of file From 6ab7a984a63ef86d094f8ec10fb53846c8bbe243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Tue, 22 Feb 2022 23:20:28 -0400 Subject: [PATCH 03/12] Better doc, and corrected some formatting --- Maths/ArithmeticGeometricMean.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js index d509ab857b..d7911465d1 100644 --- a/Maths/ArithmeticGeometricMean.js +++ b/Maths/ArithmeticGeometricMean.js @@ -9,16 +9,18 @@ export const agm = (a, g) => { if (a === g) return a; //avoid rounding errors, and increase efficiency - let x; //temp var, for detecting rounding differences between `sqrt` and division + let x; //temp var do { [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] } while (a !== x && !isNaN(a)); /* `x !== a` ensures the return value has full precision, - and prevents infinite loops caused by rounding errors (no need for "epsilon"). + and prevents infinite loops caused by rounding differences between `div` and `sqrt` (no need for "epsilon"). + If we were to compare `a` with `g`, some input combinations (not all) can cause an infinite loop, + because the rounding mode never changes at runtime. Precision is not the same as accuracy, but they're related. This function isn't always 100% accurate (round-errors), but at least is more than 95% accurate. - `!isNaN(x)` prevents infinite loops caused by invalid inputs like: negatives, NaNs and both Infinities. + `!isNaN(x)` prevents infinite loops caused by invalid inputs like: negatives, NaNs and Infinities. */ - return a + return a; } From 1dca4e6aff2d0af9fc1f7c7b0f4b4d0c83fe5ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 17:48:03 -0400 Subject: [PATCH 04/12] Fixed syntax typos --- Maths/test/ArithmeticGeometricMean.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Maths/test/ArithmeticGeometricMean.test.js b/Maths/test/ArithmeticGeometricMean.test.js index df9e8f8068..e832b99312 100644 --- a/Maths/test/ArithmeticGeometricMean.test.js +++ b/Maths/test/ArithmeticGeometricMean.test.js @@ -21,26 +21,26 @@ describe('Tests for AGM', () => { it('should return Infinity if any arg is Infinity and the other is not 0', () => { expect(agm(Math.random() * m + 1, Infinity)).toEqual(Infinity) expect(agm(Infinity, Math.random() * m + 1)).toEqual(Infinity) - expect(agm(Infinity, Infinity).toEqual(Infinity)) + expect(agm(Infinity, Infinity)).toEqual(Infinity) }) it('should return NaN if some arg is Infinity and the other is 0', () => { - expect(agm(0, Infinity).toBe(NaN)) - expect(agm(Infinity, 0).toBe(NaN)) + expect(agm(0, Infinity)).toBe(NaN) + expect(agm(Infinity, 0)).toBe(NaN) }) it('should return +0 if any or all args are +0 or -0, and return -0 if all are -0', () => { expect(agm(Math.random() * m, -0)).toBe(0) expect(agm(0, Math.random() * m)).toBe(0) - expect(agm(0, -0).toBe(0)) - expect(agm(-0, 0).toBe(0)) - expect(agm(-0, -0).toBe(-0)) + expect(agm(0, -0)).toBe(0) + expect(agm(-0, 0)).toBe(0) + expect(agm(-0, -0)).toBe(-0) }) it('should return NaN if any or all args are NaN', () => { expect(agm(Math.random() * m, NaN)).toBe(NaN) expect(agm(NaN, Math.random() * m)).toBe(NaN) - expect(agm(NaN, NaN).toBe(NaN)) + expect(agm(NaN, NaN)).toBe(NaN) }) it('should return an accurate approximation of the AGM between 2 valid input args', () => { @@ -49,6 +49,6 @@ describe('Tests for AGM', () => { expect(agm(2, 256)).toBeCloseTo(64.4594071943866695986665434983250898480227029006463800294306182) expect(agm(55555, 34)).toBeCloseTo(9933.40472395519953051873484897963370925448369441581220656590) //test "unsafe" numbers - expect(agm(2 ** 48, 3 ** 27).toBeCloseTo(88506556379265.712723873253375677194780)) + expect(agm(2 ** 48, 3 ** 27)).toBeCloseTo(88506556379265.712723873253375677194780) }) -}) \ No newline at end of file +}) From f1382e3d18c60da36d65ce141e712eb66df7733e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:12:58 -0400 Subject: [PATCH 05/12] Added more tests and made FP comparison more "loose" --- Maths/test/ArithmeticGeometricMean.test.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Maths/test/ArithmeticGeometricMean.test.js b/Maths/test/ArithmeticGeometricMean.test.js index e832b99312..07105533c6 100644 --- a/Maths/test/ArithmeticGeometricMean.test.js +++ b/Maths/test/ArithmeticGeometricMean.test.js @@ -30,8 +30,10 @@ describe('Tests for AGM', () => { }) it('should return +0 if any or all args are +0 or -0, and return -0 if all are -0', () => { - expect(agm(Math.random() * m, -0)).toBe(0) + expect(agm(Math.random() * m, 0)).toBe(0) expect(agm(0, Math.random() * m)).toBe(0) + expect(agm(Math.random() * m, -0)).toBe(0) + expect(agm(-0, Math.random() * m)).toBe(0) expect(agm(0, -0)).toBe(0) expect(agm(-0, 0)).toBe(0) expect(agm(-0, -0)).toBe(-0) @@ -44,11 +46,11 @@ describe('Tests for AGM', () => { }) it('should return an accurate approximation of the AGM between 2 valid input args', () => { - //all the constants are provided by WolframAlpha - expect(agm(1, 2)).toBeCloseTo(1.4567910310469068691864323832650819749738639432213055907941723832) - expect(agm(2, 256)).toBeCloseTo(64.4594071943866695986665434983250898480227029006463800294306182) - expect(agm(55555, 34)).toBeCloseTo(9933.40472395519953051873484897963370925448369441581220656590) + //all the constants are provided by WolframAlpha (and truncated) + expect(agm(1, 2)).toBeCloseTo(1.45679103104690686918643238326508197497386394) + expect(agm(2, 256)).toBeCloseTo(64.45940719438666959866654349832508984802270) + expect(agm(55555, 34)).toBeCloseTo(9933.404723955199530518734848979633709254) //test "unsafe" numbers - expect(agm(2 ** 48, 3 ** 27)).toBeCloseTo(88506556379265.712723873253375677194780) + expect(agm(2 ** 48, 3 ** 27)).toBeCloseTo(88506556379265.7) }) }) From 1762ac93437159e135c03b2d9a03e94418f3587a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:13:59 -0400 Subject: [PATCH 06/12] Patched bugs --- Maths/ArithmeticGeometricMean.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js index d7911465d1..5ef15d1345 100644 --- a/Maths/ArithmeticGeometricMean.js +++ b/Maths/ArithmeticGeometricMean.js @@ -9,6 +9,8 @@ export const agm = (a, g) => { if (a === g) return a; //avoid rounding errors, and increase efficiency + if (a === Infinity && g === 0) return NaN; + if (Object.is(a, -0) && !Object.is(g, -0)) return 0; let x; //temp var do { [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] From 0b812e99b51a9c3dd05c7812afb7a3f817b6dad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:30:53 -0400 Subject: [PATCH 07/12] Fixed-0 bug --- Maths/ArithmeticGeometricMean.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js index 5ef15d1345..5641565f37 100644 --- a/Maths/ArithmeticGeometricMean.js +++ b/Maths/ArithmeticGeometricMean.js @@ -10,7 +10,8 @@ export const agm = (a, g) => { if (a === g) return a; //avoid rounding errors, and increase efficiency if (a === Infinity && g === 0) return NaN; - if (Object.is(a, -0) && !Object.is(g, -0)) return 0; + if (Object.is(a, -0) && Object.is(g, -0)) return a; + if (a === 0 || g === 0) return 0; let x; //temp var do { [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] From 3c8e162f3cd97ff27eec89f471ca552614b2095a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:35:57 -0400 Subject: [PATCH 08/12] Again, tried to fix minus zero --- Maths/ArithmeticGeometricMean.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js index 5641565f37..5ef15d1345 100644 --- a/Maths/ArithmeticGeometricMean.js +++ b/Maths/ArithmeticGeometricMean.js @@ -10,8 +10,7 @@ export const agm = (a, g) => { if (a === g) return a; //avoid rounding errors, and increase efficiency if (a === Infinity && g === 0) return NaN; - if (Object.is(a, -0) && Object.is(g, -0)) return a; - if (a === 0 || g === 0) return 0; + if (Object.is(a, -0) && !Object.is(g, -0)) return 0; let x; //temp var do { [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] From f9d28a81c496cd72f9970101a85e1a0e0deccc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:38:55 -0400 Subject: [PATCH 09/12] Finally fixed all bugs (probably) --- Maths/ArithmeticGeometricMean.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js index 5ef15d1345..26c87a3ffe 100644 --- a/Maths/ArithmeticGeometricMean.js +++ b/Maths/ArithmeticGeometricMean.js @@ -8,9 +8,9 @@ */ export const agm = (a, g) => { - if (a === g) return a; //avoid rounding errors, and increase efficiency if (a === Infinity && g === 0) return NaN; if (Object.is(a, -0) && !Object.is(g, -0)) return 0; + if (a === g) return a; //avoid rounding errors, and increase efficiency let x; //temp var do { [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] From f81549dced78ce090f7c1a5a95020b877d78bfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:45:05 -0400 Subject: [PATCH 10/12] Fixed style (probably) --- Maths/ArithmeticGeometricMean.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js index 26c87a3ffe..100d094ef6 100644 --- a/Maths/ArithmeticGeometricMean.js +++ b/Maths/ArithmeticGeometricMean.js @@ -7,14 +7,14 @@ * @see [AGM](https://en.wikipedia.org/wiki/Arithmetic%E2%80%93geometric_mean) */ - export const agm = (a, g) => { - if (a === Infinity && g === 0) return NaN; - if (Object.is(a, -0) && !Object.is(g, -0)) return 0; - if (a === g) return a; //avoid rounding errors, and increase efficiency - let x; //temp var +export const agm = (a, g) => { + if (a === Infinity && g === 0) return NaN + if (Object.is(a, -0) && !Object.is(g, -0)) return 0 + if (a === g) return a // avoid rounding errors, and increase efficiency + let x // temp var do { [a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] - } while (a !== x && !isNaN(a)); + } while (a !== x && !isNaN(a)) /* `x !== a` ensures the return value has full precision, and prevents infinite loops caused by rounding differences between `div` and `sqrt` (no need for "epsilon"). @@ -24,5 +24,5 @@ This function isn't always 100% accurate (round-errors), but at least is more than 95% accurate. `!isNaN(x)` prevents infinite loops caused by invalid inputs like: negatives, NaNs and Infinities. */ - return a; + return a } From b0b0776e54b70ea82a2730a91f615115f7f2a584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:47:24 -0400 Subject: [PATCH 11/12] Fixed style --- Maths/test/ArithmeticGeometricMean.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Maths/test/ArithmeticGeometricMean.test.js b/Maths/test/ArithmeticGeometricMean.test.js index 07105533c6..4d57096e98 100644 --- a/Maths/test/ArithmeticGeometricMean.test.js +++ b/Maths/test/ArithmeticGeometricMean.test.js @@ -9,10 +9,10 @@ describe('Tests for AGM', () => { expect(agm.length).toEqual(2) }) - const m = 0x100; //scale for rand + const m = 0x100 //scale for rand it('should return NaN if any or all params has a negative argument', () => { - //I multiplied by minus one, because the sign inversion is more clearly visible + // I multiplied by minus one, because the sign inversion is more clearly visible expect(agm(-1 * Math.random() * m, Math.random() * m)).toBe(NaN) expect(agm(Math.random() * m, -1 * Math.random() * m)).toBe(NaN) expect(agm(-1 * Math.random() * m, -1 * Math.random() * m)).toBe(NaN) @@ -46,11 +46,11 @@ describe('Tests for AGM', () => { }) it('should return an accurate approximation of the AGM between 2 valid input args', () => { - //all the constants are provided by WolframAlpha (and truncated) - expect(agm(1, 2)).toBeCloseTo(1.45679103104690686918643238326508197497386394) - expect(agm(2, 256)).toBeCloseTo(64.45940719438666959866654349832508984802270) - expect(agm(55555, 34)).toBeCloseTo(9933.404723955199530518734848979633709254) - //test "unsafe" numbers + // all the constants are provided by WolframAlpha + expect(agm(1, 2)).toBeCloseTo(1.4567910310469068) + expect(agm(2, 256)).toBeCloseTo(64.45940719438667) + expect(agm(55555, 34)).toBeCloseTo(9933.4047239552) + // test "unsafe" numbers expect(agm(2 ** 48, 3 ** 27)).toBeCloseTo(88506556379265.7) }) }) From 4f7431c8b9c349b8fe9c33440fbc800465d7d045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:49:59 -0400 Subject: [PATCH 12/12] Fixed all style --- Maths/test/ArithmeticGeometricMean.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maths/test/ArithmeticGeometricMean.test.js b/Maths/test/ArithmeticGeometricMean.test.js index 4d57096e98..b0f9361c27 100644 --- a/Maths/test/ArithmeticGeometricMean.test.js +++ b/Maths/test/ArithmeticGeometricMean.test.js @@ -9,7 +9,7 @@ describe('Tests for AGM', () => { expect(agm.length).toEqual(2) }) - const m = 0x100 //scale for rand + const m = 0x100 // scale for rand it('should return NaN if any or all params has a negative argument', () => { // I multiplied by minus one, because the sign inversion is more clearly visible