From f4c56238010e7f79d04ffb048a5b49b9cfbbcfcb Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Fri, 11 Aug 2023 18:24:27 +0100 Subject: [PATCH 1/8] changes made to the following files: - mod.js - gcd.js --- src/function/arithmetic/gcd.js | 54 +++++++++++++++++-- src/function/arithmetic/mod.js | 54 +++++++++++++++++-- .../function/arithmetic/mod.test.js | 4 ++ 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/function/arithmetic/gcd.js b/src/function/arithmetic/gcd.js index 46a32b3e8d..553088d893 100644 --- a/src/function/arithmetic/gcd.js +++ b/src/function/arithmetic/gcd.js @@ -1,14 +1,17 @@ +import { isInteger, nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' import { createMatAlgo04xSidSid } from '../../type/matrix/utils/matAlgo04xSidSid.js' import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' -import { gcdNumber } from '../../plain/number/index.js' +// import { gcdNumber } from '../../plain/number/index.js' import { ArgumentsError } from '../../error/ArgumentsError.js' const name = 'gcd' const dependencies = [ 'typed', + 'config', + 'round', 'matrix', 'equalScalar', 'BigNumber', @@ -23,7 +26,7 @@ function is1d (array) { return !array.some(element => Array.isArray(element)) } -export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, BigNumber, DenseMatrix, concat }) => { +export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, config, round, equalScalar, BigNumber, DenseMatrix, concat }) => { const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) @@ -57,7 +60,7 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m return typed( name, { - 'number, number': gcdNumber, + 'number, number': _gcdNumber, 'BigNumber, BigNumber': _gcdBigNumber, 'Fraction, Fraction': (x, y) => x.gcd(y) }, @@ -89,6 +92,28 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m } ) + /** + * Calculate gcd for numbers + * @param {number} a + * @param {number} b + * @returns {number} Returns the greatest common denominator of a and b + * @private + */ + function _gcdNumber (a, b) { + if (!isInteger(a) || !isInteger(b)) { + throw new Error('Parameters in function gcd must be integer numbers') + } + + // https://en.wikipedia.org/wiki/Euclidean_algorithm + let r + while (b !== 0) { + r = _modNumber(a, b) + a = b + b = r + } + return (a < 0) ? -a : a + } + /** * Calculate gcd for BigNumbers * @param {BigNumber} a @@ -110,4 +135,27 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m } return a.lt(zero) ? a.neg() : a } + + /** + * Calculate the modulus of two numbers + * @param {number} x + * @param {number} y + * @returns {number} res + * @private + */ + function _modNumber (x, y) { + if (y === 0) { + return x + } + // then y < 0 or y > 0 + const div = x / Math.abs(y) + if (nearlyEqual(div, round(div), config.epsilon)) { + const result = x - y * round(div) + return nearlyEqual(result, round(result), config.epsilon) + ? round(result) + : result + } else { + return x - Math.abs(y) * Math.floor(x / Math.abs(y)) + } + } }) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index f22492fb81..74d6198549 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -1,22 +1,26 @@ +import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' +import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' import { createMatAlgo05xSfSf } from '../../type/matrix/utils/matAlgo05xSfSf.js' import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' -import { modNumber } from '../../plain/number/index.js' +// import { modNumber } from '../../plain/number/index.js' import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'mod' const dependencies = [ 'typed', + 'config', + 'round', 'matrix', 'equalScalar', 'DenseMatrix', 'concat' ] -export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat }) => { +export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, DenseMatrix, concat }) => { const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) @@ -62,13 +66,24 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, m return typed( name, { - 'number, number': modNumber, + 'number, number': _modNumber, 'BigNumber, BigNumber': function (x, y) { if (y.isNeg()) { throw new Error('Cannot calculate mod for a negative divisor') + } else if (y.isZero()) { + return x + } // else {} + let div = x.div(y) + if (bigNearlyEqual(div, round(div), config.epsilon)) { + const result = x.sub(y.mul(round(div))) + return bigNearlyEqual(result, round(result), config.epsilon) + ? round(result) + : result + } else { + return x.sub(y.mul(div.floor())) } - return y.isZero() ? x : x.mod(y) + }, 'Fraction, Fraction': function (x, y) { @@ -87,4 +102,35 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, m sS: matAlgo12xSfs }) ) + + /** + * Calculate the modulus of two numbers + * @param {number} x + * @param {number} y + * @returns {number} res + * @private + */ + function _modNumber (x, y) { + if (y > 0) { + // We don't use JavaScript's % operator here as this doesn't work + // correctly for x < 0 and x === 0 + // see https://en.wikipedia.org/wiki/Modulo_operation + + // To ensure precision with float approximation + const div = x / y + if (nearlyEqual(div, round(div), config.epsilon)) { + const result = x - y * round(div) + return nearlyEqual(result, round(result), config.epsilon) + ? round(result) + : result + } else { // Math.floor is precise + return x - y * Math.floor(x / y) + } + } else if (y === 0) { + return x + } else { // y < 0 + // TODO: implement mod for a negative divisor + throw new Error('Cannot calculate mod for a negative divisor') + } + } }) diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index 5eddecf4a1..79b54432d2 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -34,6 +34,10 @@ describe('mod', function () { approx.equal(mod(-5, 3), 1) }) + it ('should be precise if odd numbers are involved'), function () { + assert.strictEqual(mod(0.15, 0.05), 0) + } + it('should throw an error if the divisor is negative', function () { assert.throws(function () { mod(10, -4) }) }) From b59484149395fb32158384b92b09e48e34ebba4d Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Fri, 11 Aug 2023 20:12:55 +0100 Subject: [PATCH 2/8] updated BigNumber implementation and added validating tests --- AUTHORS | 1 + src/function/arithmetic/gcd.js | 38 +++++++++++++++---- src/function/arithmetic/mod.js | 5 +-- .../function/arithmetic/mod.test.js | 4 +- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/AUTHORS b/AUTHORS index f3b6abcf93..39dc3e1c03 100644 --- a/AUTHORS +++ b/AUTHORS @@ -229,5 +229,6 @@ Michael Greminger Kiku MaybePixem <47889538+MaybePixem@users.noreply.github.com> Aly Khaled +Praise Nnamonu # Generated by tools/update-authors.js diff --git a/src/function/arithmetic/gcd.js b/src/function/arithmetic/gcd.js index 553088d893..2725c815d7 100644 --- a/src/function/arithmetic/gcd.js +++ b/src/function/arithmetic/gcd.js @@ -1,4 +1,5 @@ import { isInteger, nearlyEqual } from '../../utils/number.js' +import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' import { factory } from '../../utils/factory.js' import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' import { createMatAlgo04xSidSid } from '../../type/matrix/utils/matAlgo04xSidSid.js' @@ -129,7 +130,7 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m // https://en.wikipedia.org/wiki/Euclidean_algorithm const zero = new BigNumber(0) while (!b.isZero()) { - const r = a.mod(b) + const r = _modBigNumber(a, b) a = b b = r } @@ -137,12 +138,12 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m } /** - * Calculate the modulus of two numbers - * @param {number} x - * @param {number} y - * @returns {number} res - * @private - */ + * Calculate the mod of two numbers + * @param {number} x + * @param {number} y + * @returns {number} res + * @private + */ function _modNumber (x, y) { if (y === 0) { return x @@ -158,4 +159,27 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m return x - Math.abs(y) * Math.floor(x / Math.abs(y)) } } + + /** + * Calculate the mod of two BigNumbers + * @param {number} x + * @param {number} y + * @returns {number} res + * @private + */ + function _modBigNumber (x, y) { + if (y === 0) { + return x + } + // then y < 0 or y > 0 + const div = x.div(y.abs()) + if (bigNearlyEqual(div, round(div), config.epsilon)) { + const result = x.sub(y.mul(round(div))) + return bigNearlyEqual(result, round(result), config.epsilon) + ? round(result) + : result + } else { + return x.sub(y.mul(div.floor())) + } + } }) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index 74d6198549..7efcab6d73 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -73,8 +73,8 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c throw new Error('Cannot calculate mod for a negative divisor') } else if (y.isZero()) { return x - } // else {} - let div = x.div(y) + } + const div = x.div(y) if (bigNearlyEqual(div, round(div), config.epsilon)) { const result = x.sub(y.mul(round(div))) return bigNearlyEqual(result, round(result), config.epsilon) @@ -83,7 +83,6 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c } else { return x.sub(y.mul(div.floor())) } - }, 'Fraction, Fraction': function (x, y) { diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index 79b54432d2..323b995314 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -34,9 +34,9 @@ describe('mod', function () { approx.equal(mod(-5, 3), 1) }) - it ('should be precise if odd numbers are involved'), function () { + it ('should be precise if odd numbers are involved', function () { assert.strictEqual(mod(0.15, 0.05), 0) - } + }) it('should throw an error if the divisor is negative', function () { assert.throws(function () { mod(10, -4) }) From 9296f8e60385c93e226f5a168a8a8fa0389e2f2f Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Sat, 12 Aug 2023 17:11:01 +0100 Subject: [PATCH 3/8] added validating test cases --- src/function/arithmetic/mod.js | 7 ++++--- test/unit-tests/function/arithmetic/mod.test.js | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index 7efcab6d73..f62ce93f8e 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -116,13 +116,14 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c // see https://en.wikipedia.org/wiki/Modulo_operation // To ensure precision with float approximation + // fails for y > 1e24 const div = x / y if (nearlyEqual(div, round(div), config.epsilon)) { const result = x - y * round(div) - return nearlyEqual(result, round(result), config.epsilon) - ? round(result) + return nearlyEqual(result, round(Math.abs(result)), config.epsilon) + ? round(Math.abs(result)) : result - } else { // Math.floor is precise + } else { // then Math.floor is precise return x - y * Math.floor(x / y) } } else if (y === 0) { diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index 323b995314..c00cb99cf9 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -34,8 +34,10 @@ describe('mod', function () { approx.equal(mod(-5, 3), 1) }) - it ('should be precise if odd numbers are involved', function () { + it('should handle precise approximation of float approximation', function () { assert.strictEqual(mod(0.15, 0.05), 0) + assert.strictEqual(mod(1.23456789, 0.00000000001), 0) + assert.strictEqual(mod(1e30, 1e24), 0) }) it('should throw an error if the divisor is negative', function () { From 840c998b474a10c2df81ea6f12ee90e896fccbcf Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Wed, 16 Aug 2023 10:36:39 +0100 Subject: [PATCH 4/8] updated test cases --- src/function/arithmetic/mod.js | 3 +-- test/unit-tests/function/arithmetic/mod.test.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index f62ce93f8e..01edec631d 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -115,8 +115,7 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c // correctly for x < 0 and x === 0 // see https://en.wikipedia.org/wiki/Modulo_operation - // To ensure precision with float approximation - // fails for y > 1e24 + // To ensure precision with float approximation const div = x / y if (nearlyEqual(div, round(div), config.epsilon)) { const result = x - y * round(div) diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index c00cb99cf9..cd282bc531 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -35,9 +35,9 @@ describe('mod', function () { }) it('should handle precise approximation of float approximation', function () { + assert.strictEqual(mod(0.1, 0.01), 0) assert.strictEqual(mod(0.15, 0.05), 0) assert.strictEqual(mod(1.23456789, 0.00000000001), 0) - assert.strictEqual(mod(1e30, 1e24), 0) }) it('should throw an error if the divisor is negative', function () { From 52ad56244c4b09232329f241b42148e9bc5a45cd Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Sun, 20 Aug 2023 18:30:36 +0100 Subject: [PATCH 5/8] formatted code --- src/function/arithmetic/mod.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index 01edec631d..f64068ec5d 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -102,7 +102,7 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c }) ) - /** +/** * Calculate the modulus of two numbers * @param {number} x * @param {number} y From 6f87223adc5761a199e5dbbca765d50710124d46 Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Thu, 31 Aug 2023 08:25:48 +0100 Subject: [PATCH 6/8] Made updates according to requirement used mathjs floor in mod.js imported mod in gcd.js made mod work for negative divisors wrote and updated tests to validate new behavior --- src/function/arithmetic/gcd.js | 59 +++---------------- src/function/arithmetic/mod.js | 49 ++++----------- test/unit-tests/expression/parse.test.js | 5 +- .../function/arithmetic/mod.test.js | 10 ++-- 4 files changed, 28 insertions(+), 95 deletions(-) diff --git a/src/function/arithmetic/gcd.js b/src/function/arithmetic/gcd.js index 2725c815d7..d370c498d6 100644 --- a/src/function/arithmetic/gcd.js +++ b/src/function/arithmetic/gcd.js @@ -1,11 +1,10 @@ -import { isInteger, nearlyEqual } from '../../utils/number.js' -import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' +import { isInteger } from '../../utils/number.js' import { factory } from '../../utils/factory.js' +import { createMod } from './mod.js' import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' import { createMatAlgo04xSidSid } from '../../type/matrix/utils/matAlgo04xSidSid.js' import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' -// import { gcdNumber } from '../../plain/number/index.js' import { ArgumentsError } from '../../error/ArgumentsError.js' const name = 'gcd' @@ -15,6 +14,7 @@ const dependencies = [ 'round', 'matrix', 'equalScalar', + 'zeros', 'BigNumber', 'DenseMatrix', 'concat' @@ -27,7 +27,8 @@ function is1d (array) { return !array.some(element => Array.isArray(element)) } -export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, config, round, equalScalar, BigNumber, DenseMatrix, concat }) => { +export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, config, round, equalScalar, zeros, BigNumber, DenseMatrix, concat }) => { + const mod = createMod({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix, concat }) const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) @@ -108,7 +109,7 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m // https://en.wikipedia.org/wiki/Euclidean_algorithm let r while (b !== 0) { - r = _modNumber(a, b) + r = mod(a, b) a = b b = r } @@ -130,56 +131,10 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m // https://en.wikipedia.org/wiki/Euclidean_algorithm const zero = new BigNumber(0) while (!b.isZero()) { - const r = _modBigNumber(a, b) + const r = mod(a, b) a = b b = r } return a.lt(zero) ? a.neg() : a } - - /** - * Calculate the mod of two numbers - * @param {number} x - * @param {number} y - * @returns {number} res - * @private - */ - function _modNumber (x, y) { - if (y === 0) { - return x - } - // then y < 0 or y > 0 - const div = x / Math.abs(y) - if (nearlyEqual(div, round(div), config.epsilon)) { - const result = x - y * round(div) - return nearlyEqual(result, round(result), config.epsilon) - ? round(result) - : result - } else { - return x - Math.abs(y) * Math.floor(x / Math.abs(y)) - } - } - - /** - * Calculate the mod of two BigNumbers - * @param {number} x - * @param {number} y - * @returns {number} res - * @private - */ - function _modBigNumber (x, y) { - if (y === 0) { - return x - } - // then y < 0 or y > 0 - const div = x.div(y.abs()) - if (bigNearlyEqual(div, round(div), config.epsilon)) { - const result = x.sub(y.mul(round(div))) - return bigNearlyEqual(result, round(result), config.epsilon) - ? round(result) - : result - } else { - return x.sub(y.mul(div.floor())) - } - } }) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index f64068ec5d..3228137126 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -1,12 +1,10 @@ -import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' -import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' +import { createFloor } from './floor.js' import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' import { createMatAlgo05xSfSf } from '../../type/matrix/utils/matAlgo05xSfSf.js' import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' -// import { modNumber } from '../../plain/number/index.js' import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'mod' @@ -16,11 +14,13 @@ const dependencies = [ 'round', 'matrix', 'equalScalar', + 'zeros', 'DenseMatrix', 'concat' ] -export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, DenseMatrix, concat }) => { +export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix, concat }) => { + const floor = createFloor({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix }) const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) @@ -71,18 +71,7 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c 'BigNumber, BigNumber': function (x, y) { if (y.isNeg()) { throw new Error('Cannot calculate mod for a negative divisor') - } else if (y.isZero()) { - return x - } - const div = x.div(y) - if (bigNearlyEqual(div, round(div), config.epsilon)) { - const result = x.sub(y.mul(round(div))) - return bigNearlyEqual(result, round(result), config.epsilon) - ? round(result) - : result - } else { - return x.sub(y.mul(div.floor())) - } + } return y.isZero() ? x : x.mod(y) }, 'Fraction, Fraction': function (x, y) { @@ -102,7 +91,7 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c }) ) -/** + /** * Calculate the modulus of two numbers * @param {number} x * @param {number} y @@ -110,26 +99,12 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c * @private */ function _modNumber (x, y) { - if (y > 0) { - // We don't use JavaScript's % operator here as this doesn't work - // correctly for x < 0 and x === 0 - // see https://en.wikipedia.org/wiki/Modulo_operation + // We don't use JavaScript's % operator here as this doesn't work + // correctly for x < 0 and x === 0 + // see https://en.wikipedia.org/wiki/Modulo_operation - // To ensure precision with float approximation - const div = x / y - if (nearlyEqual(div, round(div), config.epsilon)) { - const result = x - y * round(div) - return nearlyEqual(result, round(Math.abs(result)), config.epsilon) - ? round(Math.abs(result)) - : result - } else { // then Math.floor is precise - return x - y * Math.floor(x / y) - } - } else if (y === 0) { - return x - } else { // y < 0 - // TODO: implement mod for a negative divisor - throw new Error('Cannot calculate mod for a negative divisor') - } + // We use mathjs floor to handle errors associated with + // precision float approximation + return (y === 0) ? x : x - y * floor(x / y) } }) diff --git a/test/unit-tests/expression/parse.test.js b/test/unit-tests/expression/parse.test.js index 1380d538d0..fb8cca3e54 100644 --- a/test/unit-tests/expression/parse.test.js +++ b/test/unit-tests/expression/parse.test.js @@ -1234,7 +1234,10 @@ describe('parse', function () { it('should parse mod %', function () { approx.equal(parseAndEval('8 % 3'), 2) approx.equal(parseAndEval('80% pi'), 1.4601836602551685) - assert.throws(function () { parseAndEval('3%(-100)') }, /Cannot calculate mod for a negative divisor/) + }) + + it('should parse mod % for negative divisors', function () { + assert.strictEqual(parseAndEval('3%(-100)'), -97) }) it('should parse % value', function () { diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index cd282bc531..d7f83f7039 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -35,13 +35,13 @@ describe('mod', function () { }) it('should handle precise approximation of float approximation', function () { - assert.strictEqual(mod(0.1, 0.01), 0) - assert.strictEqual(mod(0.15, 0.05), 0) - assert.strictEqual(mod(1.23456789, 0.00000000001), 0) + approx.equal(mod(0.1, 0.01), 0) + approx.equal(mod(0.15, 0.05), 0) + approx.equal(mod(1.23456789, 0.00000000001), 0) }) - it('should throw an error if the divisor is negative', function () { - assert.throws(function () { mod(10, -4) }) + it('should calculate mod for negative divisor', function () { + assert.strictEqual(mod(10, -4), -2) }) it('should throw an error if used with wrong number of arguments', function () { From b3e9db54b22e09aee8152b9494267b20a7a5dbd5 Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Fri, 1 Sep 2023 16:46:23 +0100 Subject: [PATCH 7/8] updated mod in arithmetic.js --- src/plain/number/arithmetic.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/plain/number/arithmetic.js b/src/plain/number/arithmetic.js index 51b12bdcf0..bb006d4f9a 100644 --- a/src/plain/number/arithmetic.js +++ b/src/plain/number/arithmetic.js @@ -157,17 +157,10 @@ log1pNumber.signature = n1 * @private */ export function modNumber (x, y) { - if (y > 0) { - // We don't use JavaScript's % operator here as this doesn't work - // correctly for x < 0 and x === 0 - // see https://en.wikipedia.org/wiki/Modulo_operation - return x - y * Math.floor(x / y) - } else if (y === 0) { - return x - } else { // y < 0 - // TODO: implement mod for a negative divisor - throw new Error('Cannot calculate mod for a negative divisor') - } + // We don't use JavaScript's % operator here as this doesn't work + // correctly for x < 0 and x === 0 + // see https://en.wikipedia.org/wiki/Modulo_operation + return (y === 0) ? x : x - y * Math.floor(x / y) } modNumber.signature = n2 From 364408ecadf8d24b20d6cc45c4cbd034b1e67cc5 Mon Sep 17 00:00:00 2001 From: Praise Nnamonu Date: Sun, 17 Sep 2023 04:53:32 +0100 Subject: [PATCH 8/8] added tests for modNumber function --- .../plain/number/arithmetic.test.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 test/node-tests/plain/number/arithmetic.test.js diff --git a/test/node-tests/plain/number/arithmetic.test.js b/test/node-tests/plain/number/arithmetic.test.js new file mode 100644 index 0000000000..f46da26744 --- /dev/null +++ b/test/node-tests/plain/number/arithmetic.test.js @@ -0,0 +1,55 @@ +// test modNumber function in src\plain\number\arithmetic.js +import assert from 'assert' +import { modNumber as mod } from '../../../../lib/cjs/plain/number/aristhmetic' +import approx from '../../../../tools/approx.js' + +const math = require('../../../../lib/cjs/defaultInstance').default +const bignumber = math.bignumber + +describe('mod', function () { + it('should calculate the modulus of booleans correctly', function () { + assert.strictEqual(mod(true, true), 0) + assert.strictEqual(mod(false, true), 0) + assert.strictEqual(mod(true, false), 1) + assert.strictEqual(mod(false, false), 0) + }) + + it('should calculate the modulus of two numbers', function () { + assert.strictEqual(mod(1, 1), 0) + assert.strictEqual(mod(0, 1), 0) + assert.strictEqual(mod(1, 0), 1) + assert.strictEqual(mod(0, 0), 0) + assert.strictEqual(mod(7, 0), 7) + + approx.equal(mod(7, 2), 1) + approx.equal(mod(9, 3), 0) + approx.equal(mod(10, 4), 2) + approx.equal(mod(-10, 4), 2) + approx.equal(mod(8.2, 3), 2.2) + approx.equal(mod(4, 1.5), 1) + approx.equal(mod(0, 3), 0) + approx.equal(mod(-10, 4), 2) + approx.equal(mod(-5, 3), 1) + }) + + it('should handle precise approximation of float approximation', function () { + approx.equal(mod(0.1, 0.01), 0) + approx.equal(mod(0.15, 0.05), 0) + approx.equal(mod(1.23456789, 0.00000000001), 0) + }) + + it('should calculate mod for negative divisor', function () { + assert.strictEqual(mod(10, -4), -2) + }) + + it('should throw an error if used with wrong number of arguments', function () { + assert.throws(function () { mod(1) }, /TypeError: Too few arguments/) + assert.throws(function () { mod(1, 2, 3) }, /TypeError: Too many arguments/) + }) + + it('should throw an error if used with wrong type of arguments', function () { + assert.throws(function () { mod(1, new Date()) }, /TypeError: Unexpected type of argument/) + assert.throws(function () { mod(1, null) }, /TypeError: Unexpected type of argument/) + assert.throws(function () { mod(new Date(), bignumber(2)) }, /TypeError: Unexpected type of argument/) + }) +})