diff --git a/src/algorithms/math/euclidean-algorithm/__test__/euclideanAlgorithmIterative.test.js b/src/algorithms/math/euclidean-algorithm/__test__/euclideanAlgorithmIterative.test.js index 5fd0f846ea..a9b30e9d6a 100644 --- a/src/algorithms/math/euclidean-algorithm/__test__/euclideanAlgorithmIterative.test.js +++ b/src/algorithms/math/euclidean-algorithm/__test__/euclideanAlgorithmIterative.test.js @@ -1,6 +1,13 @@ import euclideanAlgorithmIterative from '../euclideanAlgorithmIterative'; describe('euclideanAlgorithmIterative', () => { + it('should handle widely separated safe integers', () => { + expect(euclideanAlgorithmIterative(Number.MAX_SAFE_INTEGER, 2)).toBe(1); + expect(euclideanAlgorithmIterative(2, Number.MAX_SAFE_INTEGER)).toBe(1); + expect(euclideanAlgorithmIterative(-Number.MAX_SAFE_INTEGER, 3)).toBe(1); + expect(euclideanAlgorithmIterative(2 ** 52, 2)).toBe(2); + }); + it('should calculate GCD iteratively', () => { expect(euclideanAlgorithmIterative(0, 0)).toBe(0); expect(euclideanAlgorithmIterative(2, 0)).toBe(2); diff --git a/src/algorithms/math/euclidean-algorithm/euclideanAlgorithmIterative.js b/src/algorithms/math/euclidean-algorithm/euclideanAlgorithmIterative.js index 302b5bffe9..11ebd17ab4 100644 --- a/src/algorithms/math/euclidean-algorithm/euclideanAlgorithmIterative.js +++ b/src/algorithms/math/euclidean-algorithm/euclideanAlgorithmIterative.js @@ -9,12 +9,10 @@ export default function euclideanAlgorithmIterative(originalA, originalB) { let a = Math.abs(originalA); let b = Math.abs(originalB); - // Subtract one number from another until both numbers would become the same. - // This will be out GCD. Also quit the loop if one of the numbers is zero. - while (a && b && a !== b) { - [a, b] = a > b ? [a - b, b] : [a, b - a]; + // Each remainder replaces a whole sequence of repeated subtractions. + while (b !== 0) { + [a, b] = [b, a % b]; } - // Return the number that is not equal to zero since the last subtraction (it will be a GCD). - return a || b; + return a; } diff --git a/src/algorithms/math/least-common-multiple/__test__/leastCommonMultiple.test.js b/src/algorithms/math/least-common-multiple/__test__/leastCommonMultiple.test.js index 2152e110a1..b4ed291930 100644 --- a/src/algorithms/math/least-common-multiple/__test__/leastCommonMultiple.test.js +++ b/src/algorithms/math/least-common-multiple/__test__/leastCommonMultiple.test.js @@ -1,6 +1,15 @@ import leastCommonMultiple from '../leastCommonMultiple'; describe('leastCommonMultiple', () => { + it.each([ + [549533988, 76324165], + [76324165, 549533988], + [-549533988, 76324165], + [549533988, -76324165], + ])('should avoid overflowing the intermediate product for %i and %i', (a, b) => { + expect(leastCommonMultiple(a, b)).toBe(2747669940); + }); + it('should find least common multiple', () => { expect(leastCommonMultiple(0, 0)).toBe(0); expect(leastCommonMultiple(1, 0)).toBe(0); diff --git a/src/algorithms/math/least-common-multiple/leastCommonMultiple.js b/src/algorithms/math/least-common-multiple/leastCommonMultiple.js index 26a7681039..ec0ef5ef36 100644 --- a/src/algorithms/math/least-common-multiple/leastCommonMultiple.js +++ b/src/algorithms/math/least-common-multiple/leastCommonMultiple.js @@ -7,5 +7,6 @@ import euclideanAlgorithm from '../euclidean-algorithm/euclideanAlgorithm'; */ export default function leastCommonMultiple(a, b) { - return ((a === 0) || (b === 0)) ? 0 : Math.abs(a * b) / euclideanAlgorithm(a, b); + // Divide first so a safe integer result does not require an unsafe intermediate product. + return ((a === 0) || (b === 0)) ? 0 : Math.abs((a / euclideanAlgorithm(a, b)) * b); } diff --git a/src/algorithms/math/pascal-triangle/__test__/pascalTriangle.test.js b/src/algorithms/math/pascal-triangle/__test__/pascalTriangle.test.js index 152fc8ea35..7ecde0ad89 100644 --- a/src/algorithms/math/pascal-triangle/__test__/pascalTriangle.test.js +++ b/src/algorithms/math/pascal-triangle/__test__/pascalTriangle.test.js @@ -1,6 +1,18 @@ import pascalTriangle from '../pascalTriangle'; describe('pascalTriangle', () => { + it('should preserve safe integer coefficients when intermediate products are larger', () => { + expect(pascalTriangle(57)[22]).toBe(3489348548829780); + expect(pascalTriangle(56)[28]).toBe(7648690600760440); + + // Build independent rows using Pascal's addition identity. + let expected = [1]; + for (let row = 1; row <= 56; row += 1) { + expected = [1, ...expected.slice(1).map((value, i) => value + expected[i]), 1]; + expect(pascalTriangle(row)).toEqual(expected); + } + }); + it('should calculate Pascal Triangle coefficients for specific line number', () => { expect(pascalTriangle(0)).toEqual([1]); expect(pascalTriangle(1)).toEqual([1, 1]); diff --git a/src/algorithms/math/pascal-triangle/pascalTriangle.js b/src/algorithms/math/pascal-triangle/pascalTriangle.js index 35686fa5b0..c0004fdcce 100644 --- a/src/algorithms/math/pascal-triangle/pascalTriangle.js +++ b/src/algorithms/math/pascal-triangle/pascalTriangle.js @@ -1,15 +1,19 @@ +/* eslint-env es2020 */ /** * @param {number} lineNumber - zero based. * @return {number[]} */ export default function pascalTriangle(lineNumber) { const currentLine = [1]; + let coefficient = BigInt(1); const currentLineSize = lineNumber + 1; for (let numIndex = 1; numIndex < currentLineSize; numIndex += 1) { - // See explanation of this formula in README. - currentLine[numIndex] = (currentLine[numIndex - 1] * (lineNumber - numIndex + 1)) / numIndex; + // See explanation of this formula in README. Keep the intermediate product + // exact even when it exceeds Number.MAX_SAFE_INTEGER before division. + coefficient = (coefficient * BigInt(lineNumber - numIndex + 1)) / BigInt(numIndex); + currentLine[numIndex] = Number(coefficient); } return currentLine;