From 1b7547eb537be2230f222d4cbfa8c7c45c758459 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 9 Feb 2024 21:09:18 -0600 Subject: [PATCH 01/14] nearlyEqual with absolute and relative tolerances --- src/utils/number.js | 58 +++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/src/utils/number.js b/src/utils/number.js index 730f2cf7ac..83916f82b3 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -596,43 +596,35 @@ export function digits (value) { export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 /** - * Compares two floating point numbers. - * @param {number} x First value to compare - * @param {number} y Second value to compare - * @param {number} [epsilon] The maximum relative difference between x and y - * If epsilon is undefined or null, the function will - * test whether x and y are exactly equal. - * @return {boolean} whether the two numbers are nearly equal -*/ -export function nearlyEqual (x, y, epsilon) { - // if epsilon is null or undefined, test whether x and y are exactly equal - if (epsilon === null || epsilon === undefined) { - return x === y - } - - if (x === y) { - return true + * Determines if two numbers are considered nearly equal based on relative and absolute tolerances. + * + * @param {number} a - The first number to compare. + * @param {number} b - The second number to compare. + * @param {number} [rel_tol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. + * @param {number} [abs_tol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. + * @returns {boolean} True if the numbers are considered nearly equal, false otherwise. + * + * @throws {Error} If `rel_tol` is less than or equal to 0. + * @throws {Error} If `abs_tol` is less than 0. + * + * @example + * nearlyEqual(1.000000001, 1.0, 1e-9); // true + * nearlyEqual(1.000000002, 1.0, 1e-9); // false + * nearlyEqual(1.0, 1.01, undefined, 0.01); // true + * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true + */ +export function nearlyEqual(a, b, rel_tol = 1e-09, abs_tol = 0) { + if(rel_tol <= 0){ + throw new Error('Relative tolerance must be greater than 0') } - - // NaN - if (isNaN(x) || isNaN(y)) { - return false + if(abs_tol < 0){ + throw new Error('Absolute tolerance must be at least 0') } - - // at this point x and y should be finite - if (isFinite(x) && isFinite(y)) { - // check numbers are very close, needed when comparing numbers near zero - const diff = Math.abs(x - y) - if (diff < DBL_EPSILON) { - return true - } else { - // use relative error - return diff <= Math.max(Math.abs(x), Math.abs(y)) * epsilon - } + if(!isFinite(a) || !isFinite(b)){ + return a === b } - // Infinite and Number or negative Infinite and positive Infinite cases - return false + return Math.abs(a - b) <= Math.max(rel_tol * Math.max(Math.abs(a), Math.abs(b)), abs_tol) } /** From 4dfea1dbefef9328f80ea491fb832c51b0f351b2 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 9 Feb 2024 21:10:50 -0600 Subject: [PATCH 02/14] Format --- src/utils/number.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/number.js b/src/utils/number.js index 83916f82b3..bab5eb2c7a 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -613,14 +613,14 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * nearlyEqual(1.0, 1.01, undefined, 0.01); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual(a, b, rel_tol = 1e-09, abs_tol = 0) { - if(rel_tol <= 0){ +export function nearlyEqual (a, b, rel_tol = 1e-09, abs_tol = 0) { + if (rel_tol <= 0) { throw new Error('Relative tolerance must be greater than 0') } - if(abs_tol < 0){ + if (abs_tol < 0) { throw new Error('Absolute tolerance must be at least 0') } - if(!isFinite(a) || !isFinite(b)){ + if (!isFinite(a) || !isFinite(b)) { return a === b } From a254ceee0badbc39ae38277a072512dbfdecf334 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 9 Feb 2024 22:25:59 -0600 Subject: [PATCH 03/14] nearlyEqual for bigNumber --- src/utils/bignumber/nearlyEqual.js | 59 ++++++++++++++---------------- src/utils/number.js | 21 ++++++----- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/utils/bignumber/nearlyEqual.js b/src/utils/bignumber/nearlyEqual.js index cc7918aeee..ea3851f9af 100644 --- a/src/utils/bignumber/nearlyEqual.js +++ b/src/utils/bignumber/nearlyEqual.js @@ -1,41 +1,36 @@ /** - * Compares two BigNumbers. - * @param {BigNumber} x First value to compare - * @param {BigNumber} y Second value to compare - * @param {number} [epsilon] The maximum relative difference between x and y - * If epsilon is undefined or null, the function will - * test whether x and y are exactly equal. - * @return {boolean} whether the two numbers are nearly equal + * Determines if two numbers are considered nearly equal based on relative and absolute tolerances. + * + * @param {BigNumber} a - The first number to compare. + * @param {BigNumber} b - The second number to compare. + * @param {number} [relTol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. + * @param {number} [absTol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. + * @returns {boolean} True if the numbers are considered nearly equal, false otherwise. + * + * @throws {Error} If `relTol` is less than or equal to 0. + * @throws {Error} If `absTol` is less than 0. + * + * @example + * nearlyEqual(1.000000001, 1.0, 1e-9); // true + * nearlyEqual(1.000000002, 1.0, 1e-9); // false + * nearlyEqual(1.0, 1.01, undefined, 0.01); // true + * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (x, y, epsilon) { - // if epsilon is null or undefined, test whether x and y are exactly equal - if (epsilon === null || epsilon === undefined) { - return x.eq(y) +export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { + if (relTol <= 0) { + throw new Error('Relative tolerance must be greater than 0') } - // use "==" operator, handles infinities - if (x.eq(y)) { - return true + if (absTol < 0) { + throw new Error('Absolute tolerance must be at least 0') } - // NaN - if (x.isNaN() || y.isNaN()) { - return false + if (!a.isFinite() || !b.isFinite()) { + return a.eq(b) } - - // at this point x and y should be finite - if (x.isFinite() && y.isFinite()) { - // check numbers are very close, needed when comparing numbers near zero - const diff = x.minus(y).abs() - if (diff.isZero()) { - return true - } else { - // use relative error - const max = x.constructor.max(x.abs(), y.abs()) - return diff.lte(max.times(epsilon)) - } + if (a.eq(b)) { + return true } - - // Infinite and Number or negative Infinite and positive Infinite cases - return false + // abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + return a.minus(b).abs().lte(a.constructor.max(a.constructor.max(a.abs(), b.abs()).mul(relTol), absTol)) } diff --git a/src/utils/number.js b/src/utils/number.js index bab5eb2c7a..ed6216f1ba 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -600,12 +600,12 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * * @param {number} a - The first number to compare. * @param {number} b - The second number to compare. - * @param {number} [rel_tol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. - * @param {number} [abs_tol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. + * @param {number} [relTol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. + * @param {number} [absTol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. * @returns {boolean} True if the numbers are considered nearly equal, false otherwise. * - * @throws {Error} If `rel_tol` is less than or equal to 0. - * @throws {Error} If `abs_tol` is less than 0. + * @throws {Error} If `relTol` is less than or equal to 0. + * @throws {Error} If `absTol` is less than 0. * * @example * nearlyEqual(1.000000001, 1.0, 1e-9); // true @@ -613,18 +613,21 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * nearlyEqual(1.0, 1.01, undefined, 0.01); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (a, b, rel_tol = 1e-09, abs_tol = 0) { - if (rel_tol <= 0) { +export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { + if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } - if (abs_tol < 0) { + if (absTol < 0) { throw new Error('Absolute tolerance must be at least 0') } if (!isFinite(a) || !isFinite(b)) { return a === b } - - return Math.abs(a - b) <= Math.max(rel_tol * Math.max(Math.abs(a), Math.abs(b)), abs_tol) + if (a === b) { + return true + } + // abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + return Math.abs(a - b) <= Math.max(relTol * Math.max(Math.abs(a), Math.abs(b)), absTol) } /** From 704ba5c3c5ccb377cce4b72483b6466716deb9b7 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 9 Feb 2024 23:06:59 -0600 Subject: [PATCH 04/14] Added skip for NaN --- src/utils/bignumber/nearlyEqual.js | 4 ++++ src/utils/number.js | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/utils/bignumber/nearlyEqual.js b/src/utils/bignumber/nearlyEqual.js index ea3851f9af..46de8dec38 100644 --- a/src/utils/bignumber/nearlyEqual.js +++ b/src/utils/bignumber/nearlyEqual.js @@ -25,6 +25,10 @@ export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { throw new Error('Absolute tolerance must be at least 0') } + if (a.isNaN() || b.isNaN()) { + return false + } + if (!a.isFinite() || !b.isFinite()) { return a.eq(b) } diff --git a/src/utils/number.js b/src/utils/number.js index ed6216f1ba..c74eb21c64 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -617,15 +617,23 @@ export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } + if (absTol < 0) { throw new Error('Absolute tolerance must be at least 0') } + + if (isNaN(a) || isNaN(b)) { + return false + } + if (!isFinite(a) || !isFinite(b)) { return a === b } + if (a === b) { return true } + // abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) return Math.abs(a - b) <= Math.max(relTol * Math.max(Math.abs(a), Math.abs(b)), absTol) } From 706154b21fbf94cc6913f56a2914a47c4b687fa0 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 9 Feb 2024 23:41:18 -0600 Subject: [PATCH 05/14] Reduce diff a bit --- src/utils/bignumber/nearlyEqual.js | 13 ++++++------- src/utils/number.js | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/utils/bignumber/nearlyEqual.js b/src/utils/bignumber/nearlyEqual.js index 46de8dec38..6cf8cafcd2 100644 --- a/src/utils/bignumber/nearlyEqual.js +++ b/src/utils/bignumber/nearlyEqual.js @@ -1,12 +1,10 @@ /** - * Determines if two numbers are considered nearly equal based on relative and absolute tolerances. - * - * @param {BigNumber} a - The first number to compare. - * @param {BigNumber} b - The second number to compare. + * Compares two BigNumbers. + * @param {BigNumber} a - First value to compare + * @param {BigNumber} b - Second value to compare * @param {number} [relTol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. * @param {number} [absTol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. - * @returns {boolean} True if the numbers are considered nearly equal, false otherwise. - * + * @returns {boolean} whether the two numbers are nearly equal * @throws {Error} If `relTol` is less than or equal to 0. * @throws {Error} If `absTol` is less than 0. * @@ -24,7 +22,7 @@ export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { if (absTol < 0) { throw new Error('Absolute tolerance must be at least 0') } - + // NaN if (a.isNaN() || b.isNaN()) { return false } @@ -32,6 +30,7 @@ export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { if (!a.isFinite() || !b.isFinite()) { return a.eq(b) } + // use "==" operator, handles infinities if (a.eq(b)) { return true } diff --git a/src/utils/number.js b/src/utils/number.js index c74eb21c64..97f2934b73 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -596,13 +596,12 @@ export function digits (value) { export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 /** - * Determines if two numbers are considered nearly equal based on relative and absolute tolerances. - * - * @param {number} a - The first number to compare. - * @param {number} b - The second number to compare. + * Compares two floating point numbers. + * @param {number} a - First value to compare + * @param {number} b - Second value to compare * @param {number} [relTol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. * @param {number} [absTol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. - * @returns {boolean} True if the numbers are considered nearly equal, false otherwise. + * @return {boolean} whether the two numbers are nearly equal * * @throws {Error} If `relTol` is less than or equal to 0. * @throws {Error} If `absTol` is less than 0. @@ -622,6 +621,7 @@ export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { throw new Error('Absolute tolerance must be at least 0') } + // NaN if (isNaN(a) || isNaN(b)) { return false } From 4ba0e8c827d26dad4423e11456d676e225a89a56 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sat, 10 Feb 2024 00:04:34 -0600 Subject: [PATCH 06/14] Issue with examples in jsdcos --- src/utils/bignumber/nearlyEqual.js | 6 +++--- src/utils/number.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/bignumber/nearlyEqual.js b/src/utils/bignumber/nearlyEqual.js index 6cf8cafcd2..4808e6ec79 100644 --- a/src/utils/bignumber/nearlyEqual.js +++ b/src/utils/bignumber/nearlyEqual.js @@ -9,12 +9,12 @@ * @throws {Error} If `absTol` is less than 0. * * @example - * nearlyEqual(1.000000001, 1.0, 1e-9); // true + * nearlyEqual(1.000000001, 1.0, 1e-8); // true * nearlyEqual(1.000000002, 1.0, 1e-9); // false - * nearlyEqual(1.0, 1.01, undefined, 0.01); // true + * nearlyEqual(1.0, 1.009, undefined, 0.02); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { +export function nearlyEqual (a, b, relTol = Number.EPSILON, absTol = 1e-12) { if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } diff --git a/src/utils/number.js b/src/utils/number.js index 97f2934b73..bb8c3797e5 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -607,12 +607,12 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * @throws {Error} If `absTol` is less than 0. * * @example - * nearlyEqual(1.000000001, 1.0, 1e-9); // true + * nearlyEqual(1.000000001, 1.0, 1e-8); // true * nearlyEqual(1.000000002, 1.0, 1e-9); // false - * nearlyEqual(1.0, 1.01, undefined, 0.01); // true + * nearlyEqual(1.0, 1.009, undefined, 0.01); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (a, b, relTol = 1e-09, absTol = 0) { +export function nearlyEqual (a, b, relTol = Number.EPSILON, absTol = 1e-12) { if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } From a76126092270191dc4e73f9895b375b04b519fac Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 5 Apr 2024 22:03:11 -0600 Subject: [PATCH 07/14] Updated all calls for nearlyEqual --- docs/datatypes/numbers.md | 7 +- src/function/arithmetic/ceil.js | 8 +- src/function/arithmetic/floor.js | 8 +- src/function/arithmetic/round.js | 8 +- src/function/relational/compare.js | 4 +- src/function/relational/equalScalar.js | 6 +- src/function/relational/larger.js | 4 +- src/function/relational/largerEq.js | 4 +- src/function/relational/smaller.js | 4 +- src/function/relational/smallerEq.js | 4 +- src/utils/bignumber/nearlyEqual.js | 4 +- src/utils/complex.js | 2 +- src/utils/number.js | 4 +- .../utils/bignumber/nearlyEqual.test.js | 74 +++++++++--------- test/unit-tests/utils/number.test.js | 76 +++++++++---------- 15 files changed, 109 insertions(+), 108 deletions(-) diff --git a/docs/datatypes/numbers.md b/docs/datatypes/numbers.md index 29e7534b21..c250cc8cf6 100644 --- a/docs/datatypes/numbers.md +++ b/docs/datatypes/numbers.md @@ -73,11 +73,12 @@ false, as the addition `0.1 + 0.2` introduces a round-off error and does not return exactly `0.3`. To solve this problem, the relational functions of math.js check whether the -relative difference between the compared values is smaller than the configured +relative and absolute differences between the compared values is smaller than the configured option `epsilon`. In pseudo code (without exceptions for 0, Infinity and NaN): - diff = abs(x - y) - nearlyEqual = (diff <= max(abs(x), abs(y)) * EPSILON) OR (diff < DBL_EPSILON) + relTol = epsilon + absTol = epsilon / 1000 + abs(a-b) <= max(relTol * max(abs(a), abs(b)), absTol) where: diff --git a/src/function/arithmetic/ceil.js b/src/function/arithmetic/ceil.js index dab20a45aa..746514053e 100644 --- a/src/function/arithmetic/ceil.js +++ b/src/function/arithmetic/ceil.js @@ -14,7 +14,7 @@ export const createCeilNumber = /* #__PURE__ */ factory( name, ['typed', 'config', 'round'], ({ typed, config, round }) => { return typed(name, { number: function (x) { - if (nearlyEqual(x, round(x), config.epsilon)) { + if (nearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { return round(x) } else { return Math.ceil(x) @@ -22,7 +22,7 @@ export const createCeilNumber = /* #__PURE__ */ factory( }, 'number, number': function (x, n) { - if (nearlyEqual(x, round(x, n), config.epsilon)) { + if (nearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { return round(x, n) } else { let [number, exponent] = `${x}e`.split('e') @@ -95,7 +95,7 @@ export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, BigNumber: function (x) { - if (bigNearlyEqual(x, round(x), config.epsilon)) { + if (bigNearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { return round(x) } else { return x.ceil() @@ -103,7 +103,7 @@ export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, 'BigNumber, BigNumber': function (x, n) { - if (bigNearlyEqual(x, round(x, n), config.epsilon)) { + if (bigNearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { return round(x, n) } else { return x.toDecimalPlaces(n.toNumber(), Decimal.ROUND_CEIL) diff --git a/src/function/arithmetic/floor.js b/src/function/arithmetic/floor.js index b48e9aeefc..d1583ec5e4 100644 --- a/src/function/arithmetic/floor.js +++ b/src/function/arithmetic/floor.js @@ -14,7 +14,7 @@ export const createFloorNumber = /* #__PURE__ */ factory( name, ['typed', 'config', 'round'], ({ typed, config, round }) => { return typed(name, { number: function (x) { - if (nearlyEqual(x, round(x), config.epsilon)) { + if (nearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { return round(x) } else { return Math.floor(x) @@ -22,7 +22,7 @@ export const createFloorNumber = /* #__PURE__ */ factory( }, 'number, number': function (x, n) { - if (nearlyEqual(x, round(x, n), config.epsilon)) { + if (nearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { return round(x, n) } else { let [number, exponent] = `${x}e`.split('e') @@ -98,7 +98,7 @@ export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, BigNumber: function (x) { - if (bigNearlyEqual(x, round(x), config.epsilon)) { + if (bigNearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { return round(x) } else { return x.floor() @@ -106,7 +106,7 @@ export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, 'BigNumber, BigNumber': function (x, n) { - if (bigNearlyEqual(x, round(x, n), config.epsilon)) { + if (bigNearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { return round(x, n) } else { return x.toDecimalPlaces(n.toNumber(), Decimal.ROUND_FLOOR) diff --git a/src/function/arithmetic/round.js b/src/function/arithmetic/round.js index 6bd4da261e..d9a64569f1 100644 --- a/src/function/arithmetic/round.js +++ b/src/function/arithmetic/round.js @@ -77,7 +77,7 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, number: function (x) { // Handle round off errors by first rounding to epsilon precision const xEpsilon = roundNumber(x, toExponent(config.epsilon)) - const xSelected = nearlyEqual(x, xEpsilon, config.epsilon) ? xEpsilon : x + const xSelected = nearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x return roundNumber(xSelected) }, @@ -87,7 +87,7 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, if (n >= epsilonExponent) { return roundNumber(x, n) } const xEpsilon = roundNumber(x, epsilonExponent) - const xSelected = nearlyEqual(x, xEpsilon, config.epsilon) ? xEpsilon : x + const xSelected = nearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x return roundNumber(xSelected, n) }, @@ -117,7 +117,7 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { // Handle round off errors by first rounding to epsilon precision const xEpsilon = new BigNumber(x).toDecimalPlaces(toExponent(config.epsilon)) - const xSelected = bigNearlyEqual(x, xEpsilon, config.epsilon) ? xEpsilon : x + const xSelected = bigNearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x return xSelected.toDecimalPlaces(0) }, @@ -129,7 +129,7 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, if (n >= epsilonExponent) { return x.toDecimalPlaces(n.toNumber()) } const xEpsilon = x.toDecimalPlaces(epsilonExponent) - const xSelected = bigNearlyEqual(x, xEpsilon, config.epsilon) ? xEpsilon : x + const xSelected = bigNearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x return xSelected.toDecimalPlaces(n.toNumber()) }, diff --git a/src/function/relational/compare.js b/src/function/relational/compare.js index cc8076a088..a140e0cb20 100644 --- a/src/function/relational/compare.js +++ b/src/function/relational/compare.js @@ -72,7 +72,7 @@ export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ type }, 'BigNumber, BigNumber': function (x, y) { - return bigNearlyEqual(x, y, config.epsilon) + return bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) ? new BigNumber(0) : new BigNumber(x.cmp(y)) }, @@ -97,7 +97,7 @@ export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ type export const createCompareNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon) + return nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) ? 0 : (x > y ? 1 : -1) } diff --git a/src/function/relational/equalScalar.js b/src/function/relational/equalScalar.js index e51f49f489..140a578eed 100644 --- a/src/function/relational/equalScalar.js +++ b/src/function/relational/equalScalar.js @@ -25,11 +25,11 @@ export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ }, 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon) + return nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) }, 'BigNumber, BigNumber': function (x, y) { - return x.eq(y) || bigNearlyEqual(x, y, config.epsilon) + return x.eq(y) || bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) }, 'Fraction, Fraction': function (x, y) { @@ -45,7 +45,7 @@ export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ export const createEqualScalarNumber = factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon) + return nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) } }) }) diff --git a/src/function/relational/larger.js b/src/function/relational/larger.js index 454db83671..90a77598ac 100644 --- a/src/function/relational/larger.js +++ b/src/function/relational/larger.js @@ -61,7 +61,7 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed 'boolean, boolean': (x, y) => x > y, 'BigNumber, BigNumber': function (x, y) { - return x.gt(y) && !bigNearlyEqual(x, y, config.epsilon) + return x.gt(y) && !bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) }, 'Fraction, Fraction': (x, y) => (x.compare(y) === 1), @@ -82,7 +82,7 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed export const createLargerNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x > y && !nearlyEqual(x, y, config.epsilon) + return x > y && !nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) } }) }) diff --git a/src/function/relational/largerEq.js b/src/function/relational/largerEq.js index b15d67ff10..15f0dc51ac 100644 --- a/src/function/relational/largerEq.js +++ b/src/function/relational/largerEq.js @@ -57,7 +57,7 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ 'boolean, boolean': (x, y) => x >= y, 'BigNumber, BigNumber': function (x, y) { - return x.gte(y) || bigNearlyEqual(x, y, config.epsilon) + return x.gte(y) || bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) }, 'Fraction, Fraction': (x, y) => (x.compare(y) !== -1), @@ -78,7 +78,7 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ export const createLargerEqNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x >= y || nearlyEqual(x, y, config.epsilon) + return x >= y || nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) } }) }) diff --git a/src/function/relational/smaller.js b/src/function/relational/smaller.js index f8cb62c984..36affd5e60 100644 --- a/src/function/relational/smaller.js +++ b/src/function/relational/smaller.js @@ -61,7 +61,7 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type 'boolean, boolean': (x, y) => x < y, 'BigNumber, BigNumber': function (x, y) { - return x.lt(y) && !bigNearlyEqual(x, y, config.epsilon) + return x.lt(y) && !bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) }, 'Fraction, Fraction': (x, y) => (x.compare(y) === -1), @@ -82,7 +82,7 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type export const createSmallerNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x < y && !nearlyEqual(x, y, config.epsilon) + return x < y && !nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) } }) }) diff --git a/src/function/relational/smallerEq.js b/src/function/relational/smallerEq.js index d29eb4553a..95027aaa32 100644 --- a/src/function/relational/smallerEq.js +++ b/src/function/relational/smallerEq.js @@ -57,7 +57,7 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty 'boolean, boolean': (x, y) => (x <= y), 'BigNumber, BigNumber': function (x, y) { - return x.lte(y) || bigNearlyEqual(x, y, config.epsilon) + return x.lte(y) || bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) }, 'Fraction, Fraction': (x, y) => (x.compare(y) !== 1), @@ -78,7 +78,7 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty export const createSmallerEqNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x <= y || nearlyEqual(x, y, config.epsilon) + return x <= y || nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) } }) }) diff --git a/src/utils/bignumber/nearlyEqual.js b/src/utils/bignumber/nearlyEqual.js index 4808e6ec79..418a2db6be 100644 --- a/src/utils/bignumber/nearlyEqual.js +++ b/src/utils/bignumber/nearlyEqual.js @@ -14,7 +14,7 @@ * nearlyEqual(1.0, 1.009, undefined, 0.02); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (a, b, relTol = Number.EPSILON, absTol = 1e-12) { +export function nearlyEqual (a, b, relTol = 1e-9, absTol = 0) { if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } @@ -34,6 +34,6 @@ export function nearlyEqual (a, b, relTol = Number.EPSILON, absTol = 1e-12) { if (a.eq(b)) { return true } - // abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + // abs(a-b) <= max(relTol * max(abs(a), abs(b)), absTol) return a.minus(b).abs().lte(a.constructor.max(a.constructor.max(a.abs(), b.abs()).mul(relTol), absTol)) } diff --git a/src/utils/complex.js b/src/utils/complex.js index dae1aaa09c..fcf1b80348 100644 --- a/src/utils/complex.js +++ b/src/utils/complex.js @@ -9,5 +9,5 @@ import { nearlyEqual } from './number.js' * @returns {boolean} */ export function complexEquals (x, y, epsilon) { - return nearlyEqual(x.re, y.re, epsilon) && nearlyEqual(x.im, y.im, epsilon) + return nearlyEqual(x.re, y.re, epsilon, epsilon * 1e-3) && nearlyEqual(x.im, y.im, epsilon, epsilon * 1e-3) } diff --git a/src/utils/number.js b/src/utils/number.js index d1295c1a80..f0cb735686 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -619,7 +619,7 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * @param {number} a - First value to compare * @param {number} b - Second value to compare * @param {number} [relTol=1e-09] - The relative tolerance, indicating the maximum allowed difference relative to the larger absolute value. Must be greater than 0. - * @param {number} [absTol=0] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. + * @param {number} [absTol=1e-12] - The minimum absolute tolerance, useful for comparisons near zero. Must be at least 0. * @return {boolean} whether the two numbers are nearly equal * * @throws {Error} If `relTol` is less than or equal to 0. @@ -631,7 +631,7 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * nearlyEqual(1.0, 1.009, undefined, 0.01); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (a, b, relTol = Number.EPSILON, absTol = 1e-12) { +export function nearlyEqual (a, b, relTol = 1e-12, absTol = 1e-9) { if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } diff --git a/test/unit-tests/utils/bignumber/nearlyEqual.test.js b/test/unit-tests/utils/bignumber/nearlyEqual.test.js index 9d42944aa0..70889285ee 100644 --- a/test/unit-tests/utils/bignumber/nearlyEqual.test.js +++ b/test/unit-tests/utils/bignumber/nearlyEqual.test.js @@ -6,71 +6,71 @@ import BigNumber from 'decimal.js' describe('nearlyEqual', function () { it('should test whether two BigNumbers are nearly equal', function () { const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.95), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.98), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.991), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.1), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.05), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.02), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.01), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1), epsilon), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.95), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.98), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.991), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.1), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.05), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.02), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.01), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1), epsilon, epsilon * 1e-3), true) // smaller epsilon const epsilon2 = 1e-4 - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), epsilon2), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.999), epsilon2), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9999), epsilon2), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), epsilon2, epsilon2 * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.999), epsilon2, epsilon2 * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9999), epsilon2, epsilon2 * 1e-3), true) }) it('should test whether a positive and negative number are nearly equal', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(-1.2), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(1.2), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(-1.2), epsilon), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(-1.2), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(-1.2), epsilon, epsilon * 1e-3), true) }) it('should test whether two large numbers are nearly equal', function () { const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.90e500'), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.95e500'), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.98e500'), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.99e500'), epsilon), true) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.90e500'), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.95e500'), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.98e500'), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.99e500'), epsilon, epsilon * 1e-3), true) }) it('should test whether two small numbers are nearly equal (always true)', function () { const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('0.99e-200'), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('10e-200'), epsilon), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('0.99e-200'), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('10e-200'), epsilon, epsilon * 1e-3), false) }) it('should compare with zero', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(0), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(-0), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1.2), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e30), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e-30), epsilon), false) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(0), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(-0), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e30), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e-30), epsilon, epsilon * 1e-3), false) }) it('should compare with Infinity', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(Infinity), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(1.2), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(Infinity), epsilon), true) - assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(-Infinity), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(Infinity), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(-Infinity), epsilon), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(Infinity), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(Infinity), epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(-Infinity), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(Infinity), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(-Infinity), epsilon, epsilon * 1e-3), true) }) it('should compare with NaN', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(NaN), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(1.2), epsilon), false) - assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(NaN), epsilon), false) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(NaN), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(NaN), epsilon, epsilon * 1e-3), false) }) it('should do exact comparison when epsilon is null or undefined', function () { diff --git a/test/unit-tests/utils/number.test.js b/test/unit-tests/utils/number.test.js index 358e129506..1e9f81b44c 100644 --- a/test/unit-tests/utils/number.test.js +++ b/test/unit-tests/utils/number.test.js @@ -506,21 +506,21 @@ describe('number', function () { describe('nearlyEqual', function () { it('should test whether two numbers are nearly equal', function () { const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(1, 0.9, epsilon), false) - assert.strictEqual(nearlyEqual(1, 0.95, epsilon), false) - assert.strictEqual(nearlyEqual(1, 0.98, epsilon), false) - assert.strictEqual(nearlyEqual(1, 0.991, epsilon), true) - assert.strictEqual(nearlyEqual(1, 1.1, epsilon), false) - assert.strictEqual(nearlyEqual(1, 1.05, epsilon), false) - assert.strictEqual(nearlyEqual(1, 1.02, epsilon), false) - assert.strictEqual(nearlyEqual(1, 1.01, epsilon), true) - assert.strictEqual(nearlyEqual(1, 1, epsilon), true) + assert.strictEqual(nearlyEqual(1, 0.9, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 0.95, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 0.98, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 0.991, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(1, 1.1, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 1.05, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 1.02, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 1.01, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(1, 1, epsilon, epsilon * 1e-3), true) // smaller epsilon const epsilon2 = 1e-4 - assert.strictEqual(nearlyEqual(1, 0.99, epsilon2), false) - assert.strictEqual(nearlyEqual(1, 0.999, epsilon2), false) - assert.strictEqual(nearlyEqual(1, 0.9999, epsilon2), true) + assert.strictEqual(nearlyEqual(1, 0.99, epsilon2, epsilon2 * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 0.999, epsilon2, epsilon2 * 1e-3), false) + assert.strictEqual(nearlyEqual(1, 0.9999, epsilon2, epsilon2 * 1e-3), true) // test one of these famous round-off errors assert.strictEqual((0.1 + 0.2) === 0.3, false) @@ -529,56 +529,56 @@ describe('number', function () { it('should test whether a positive and negative number are nearly equal', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(1.2, 1.2, epsilon), true) - assert.strictEqual(nearlyEqual(1.2, -1.2, epsilon), false) - assert.strictEqual(nearlyEqual(-1.2, 1.2, epsilon), false) - assert.strictEqual(nearlyEqual(-1.2, -1.2, epsilon), true) + assert.strictEqual(nearlyEqual(1.2, 1.2, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(1.2, -1.2, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(-1.2, 1.2, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(-1.2, -1.2, epsilon, epsilon * 1e-3), true) }) it('should test whether two large numbers are nearly equal', function () { const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(1e200, 0.90e200, epsilon), false) - assert.strictEqual(nearlyEqual(1e200, 0.95e200, epsilon), false) - assert.strictEqual(nearlyEqual(1e200, 0.98e200, epsilon), false) - assert.strictEqual(nearlyEqual(1e200, 0.99e200, epsilon), true) + assert.strictEqual(nearlyEqual(1e200, 0.90e200, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1e200, 0.95e200, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1e200, 0.98e200, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(1e200, 0.99e200, epsilon, epsilon * 1e-3), true) }) it('should test whether two small numbers are nearly equal (always true)', function () { const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(1e-200, 0.99e-200, epsilon), true) - assert.strictEqual(nearlyEqual(1e-200, 10e-200, epsilon), true) // FIXME: why is this true? + assert.strictEqual(nearlyEqual(1e-200, 0.99e-200, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(1e-200, 10e-200, epsilon, epsilon * 1e-3), true) // FIXME: why is this true? }) it('should compare with zero', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(0, 0, epsilon), true) - assert.strictEqual(nearlyEqual(0, -0, epsilon), true) - assert.strictEqual(nearlyEqual(0, 1.2, epsilon), false) - assert.strictEqual(nearlyEqual(0, 1e30, epsilon), false) - assert.strictEqual(nearlyEqual(0, 1e-30, epsilon), true) // FIXME: why is this true? + assert.strictEqual(nearlyEqual(0, 0, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(0, -0, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(0, 1.2, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(0, 1e30, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(0, 1e-30, epsilon, epsilon * 1e-3), true) // FIXME: why is this true? }) it('should compare with Infinity', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(1.2, Infinity, epsilon), false) - assert.strictEqual(nearlyEqual(Infinity, 1.2, epsilon), false) - assert.strictEqual(nearlyEqual(Infinity, Infinity, epsilon), true) - assert.strictEqual(nearlyEqual(Infinity, -Infinity, epsilon), false) - assert.strictEqual(nearlyEqual(-Infinity, Infinity, epsilon), false) - assert.strictEqual(nearlyEqual(-Infinity, -Infinity, epsilon), true) + assert.strictEqual(nearlyEqual(1.2, Infinity, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(Infinity, 1.2, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(Infinity, Infinity, epsilon, epsilon * 1e-3), true) + assert.strictEqual(nearlyEqual(Infinity, -Infinity, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(-Infinity, Infinity, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(-Infinity, -Infinity, epsilon, epsilon * 1e-3), true) }) it('should compare with NaN', function () { const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(1.2, NaN, epsilon), false) - assert.strictEqual(nearlyEqual(NaN, 1.2, epsilon), false) - assert.strictEqual(nearlyEqual(NaN, NaN, epsilon), false) + assert.strictEqual(nearlyEqual(1.2, NaN, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(NaN, 1.2, epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(NaN, NaN, epsilon, epsilon * 1e-3), false) }) - it('should do exact comparison when epsilon is null or undefined', function () { + it('should do exact comparison when rtol is null or undefined', function () { assert.strictEqual(nearlyEqual(1.2, 1.2), true) - assert.strictEqual(nearlyEqual(1.2, 1.2, null), true) + assert.strictEqual(nearlyEqual(1.2, 1.2, null, 0), true) assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3), false) assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3, null), false) From a073665fa72f4b55d17f7c550412fa46dfd392f5 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sat, 6 Apr 2024 10:21:19 -0600 Subject: [PATCH 08/14] Fixed failing tests --- .../utils/bignumber/nearlyEqual.test.js | 18 ++++++++++++------ test/unit-tests/utils/number.test.js | 14 ++++++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/test/unit-tests/utils/bignumber/nearlyEqual.test.js b/test/unit-tests/utils/bignumber/nearlyEqual.test.js index 70889285ee..a497d49b36 100644 --- a/test/unit-tests/utils/bignumber/nearlyEqual.test.js +++ b/test/unit-tests/utils/bignumber/nearlyEqual.test.js @@ -43,7 +43,7 @@ describe('nearlyEqual', function () { it('should test whether two small numbers are nearly equal (always true)', function () { const epsilon = 1e-2 assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('0.99e-200'), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('10e-200'), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('10e-200'), epsilon, epsilon * 1e-3), true) }) it('should compare with zero', function () { @@ -52,7 +52,7 @@ describe('nearlyEqual', function () { assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(-0), epsilon, epsilon * 1e-3), true) assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e30), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e-30), epsilon, epsilon * 1e-3), false) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e-3), epsilon, epsilon * 1e-3), false) }) it('should compare with Infinity', function () { @@ -73,11 +73,17 @@ describe('nearlyEqual', function () { assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(NaN), epsilon, epsilon * 1e-3), false) }) - it('should do exact comparison when epsilon is null or undefined', function () { + it('should use default values when absTol and relTol are undefined', function () { assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2)), true) - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), null), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), undefined), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), undefined, undefined), true) - assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-18), new BigNumber(1.2)), false) - assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-18), new BigNumber(1.2), null), false) + assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-18), new BigNumber(1.2)), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-18), new BigNumber(1.2), undefined), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-18), new BigNumber(1.2), undefined, undefined), true) + + assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-8), new BigNumber(1.2)), false) + assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-8), new BigNumber(1.2), undefined), false) + assert.strictEqual(nearlyEqual(new BigNumber(1.2).plus(1e-8), new BigNumber(1.2), undefined, undefined), false) }) }) diff --git a/test/unit-tests/utils/number.test.js b/test/unit-tests/utils/number.test.js index 1e9f81b44c..f71d64f183 100644 --- a/test/unit-tests/utils/number.test.js +++ b/test/unit-tests/utils/number.test.js @@ -576,12 +576,18 @@ describe('number', function () { assert.strictEqual(nearlyEqual(NaN, NaN, epsilon, epsilon * 1e-3), false) }) - it('should do exact comparison when rtol is null or undefined', function () { + it('should use default values when absTol and relTol are undefined', function () { assert.strictEqual(nearlyEqual(1.2, 1.2), true) - assert.strictEqual(nearlyEqual(1.2, 1.2, null, 0), true) + assert.strictEqual(nearlyEqual(1.2, 1.2, undefined), true) + assert.strictEqual(nearlyEqual(1.2, 1.2, undefined, undefined), true) - assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3), false) - assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3, null), false) + assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3), true) + assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3, undefined), true) + assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3, undefined, undefined), true) + + assert.strictEqual(nearlyEqual(1.2 + 1e-8, 1.2), false) + assert.strictEqual(nearlyEqual(1.2 + 1e-8, 1.2, undefined), false) + assert.strictEqual(nearlyEqual(1.2 + 1e-8, 1.2, undefined, undefined), false) }) }) }) From 05aab2b8ba1430812a506377a7bb59806c645e0c Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sat, 6 Apr 2024 22:23:46 -0600 Subject: [PATCH 09/14] Changed epsilon to relTol, absTol --- src/core/config.js | 6 +++++- src/function/arithmetic/ceil.js | 8 ++++---- src/function/arithmetic/floor.js | 8 ++++---- src/function/arithmetic/round.js | 16 ++++++++-------- src/function/geometry/intersect.js | 2 +- src/function/matrix/eigs.js | 4 ++-- src/function/matrix/eigs/realSymmetric.js | 6 +++--- src/function/relational/compare.js | 4 ++-- src/function/relational/equalScalar.js | 8 ++++---- src/function/relational/larger.js | 4 ++-- src/function/relational/largerEq.js | 4 ++-- src/function/relational/smaller.js | 4 ++-- src/function/relational/smallerEq.js | 4 ++-- src/function/special/zeta.js | 2 +- src/utils/bignumber/nearlyEqual.js | 4 ++-- src/utils/complex.js | 7 ++++--- src/utils/number.js | 4 ++-- .../unit-tests/function/arithmetic/round.test.js | 6 +++--- test/unit-tests/function/matrix/eigs.test.js | 4 ++-- .../function/relational/compare.test.js | 2 +- .../function/relational/compareNatural.test.js | 2 +- .../unit-tests/function/relational/equal.test.js | 2 +- .../function/relational/larger.test.js | 2 +- .../function/relational/largerEq.test.js | 2 +- .../function/relational/smaller.test.js | 2 +- .../function/relational/smallerEq.test.js | 4 ++-- .../function/relational/unequal.test.js | 4 ++-- test/unit-tests/utils/number.test.js | 6 +++--- 28 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/core/config.js b/src/core/config.js index f8c693e358..fa7f30e67d 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -1,7 +1,11 @@ export const DEFAULT_CONFIG = { // minimum relative difference between two compared values, // used by all comparison functions - epsilon: 1e-12, + relTol: 1e-12, + + // minimum absolute difference between two compared values, + // used by all comparison functions + absTol: 1e-15, // type of default matrix output. Choose 'matrix' (default) or 'array' matrix: 'Matrix', diff --git a/src/function/arithmetic/ceil.js b/src/function/arithmetic/ceil.js index 746514053e..4d4c7a0ea1 100644 --- a/src/function/arithmetic/ceil.js +++ b/src/function/arithmetic/ceil.js @@ -14,7 +14,7 @@ export const createCeilNumber = /* #__PURE__ */ factory( name, ['typed', 'config', 'round'], ({ typed, config, round }) => { return typed(name, { number: function (x) { - if (nearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { + if (nearlyEqual(x, round(x), config.relTol, config.absTol)) { return round(x) } else { return Math.ceil(x) @@ -22,7 +22,7 @@ export const createCeilNumber = /* #__PURE__ */ factory( }, 'number, number': function (x, n) { - if (nearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { + if (nearlyEqual(x, round(x, n), config.relTol, config.absTol)) { return round(x, n) } else { let [number, exponent] = `${x}e`.split('e') @@ -95,7 +95,7 @@ export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, BigNumber: function (x) { - if (bigNearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { + if (bigNearlyEqual(x, round(x), config.relTol, config.absTol)) { return round(x) } else { return x.ceil() @@ -103,7 +103,7 @@ export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, 'BigNumber, BigNumber': function (x, n) { - if (bigNearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { + if (bigNearlyEqual(x, round(x, n), config.relTol, config.absTol)) { return round(x, n) } else { return x.toDecimalPlaces(n.toNumber(), Decimal.ROUND_CEIL) diff --git a/src/function/arithmetic/floor.js b/src/function/arithmetic/floor.js index d1583ec5e4..4129d5561b 100644 --- a/src/function/arithmetic/floor.js +++ b/src/function/arithmetic/floor.js @@ -14,7 +14,7 @@ export const createFloorNumber = /* #__PURE__ */ factory( name, ['typed', 'config', 'round'], ({ typed, config, round }) => { return typed(name, { number: function (x) { - if (nearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { + if (nearlyEqual(x, round(x), config.relTol, config.absTol)) { return round(x) } else { return Math.floor(x) @@ -22,7 +22,7 @@ export const createFloorNumber = /* #__PURE__ */ factory( }, 'number, number': function (x, n) { - if (nearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { + if (nearlyEqual(x, round(x, n), config.relTol, config.absTol)) { return round(x, n) } else { let [number, exponent] = `${x}e`.split('e') @@ -98,7 +98,7 @@ export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, BigNumber: function (x) { - if (bigNearlyEqual(x, round(x), config.epsilon, config.epsilon * 1e-3)) { + if (bigNearlyEqual(x, round(x), config.relTol, config.absTol)) { return round(x) } else { return x.floor() @@ -106,7 +106,7 @@ export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, 'BigNumber, BigNumber': function (x, n) { - if (bigNearlyEqual(x, round(x, n), config.epsilon, config.epsilon * 1e-3)) { + if (bigNearlyEqual(x, round(x, n), config.relTol, config.absTol)) { return round(x, n) } else { return x.toDecimalPlaces(n.toNumber(), Decimal.ROUND_FLOOR) diff --git a/src/function/arithmetic/round.js b/src/function/arithmetic/round.js index d9a64569f1..a8cbccea19 100644 --- a/src/function/arithmetic/round.js +++ b/src/function/arithmetic/round.js @@ -76,18 +76,18 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, return typed(name, { number: function (x) { // Handle round off errors by first rounding to epsilon precision - const xEpsilon = roundNumber(x, toExponent(config.epsilon)) - const xSelected = nearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x + const xEpsilon = roundNumber(x, toExponent(config.relTol)) + const xSelected = nearlyEqual(x, xEpsilon, config.relTol, config.absTol) ? xEpsilon : x return roundNumber(xSelected) }, 'number, number': function (x, n) { // Same as number: unless user specifies more decimals than epsilon - const epsilonExponent = toExponent(config.epsilon) + const epsilonExponent = toExponent(config.relTol) if (n >= epsilonExponent) { return roundNumber(x, n) } const xEpsilon = roundNumber(x, epsilonExponent) - const xSelected = nearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x + const xSelected = nearlyEqual(x, xEpsilon, config.relTol, config.absTol) ? xEpsilon : x return roundNumber(xSelected, n) }, @@ -116,8 +116,8 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { // Handle round off errors by first rounding to epsilon precision - const xEpsilon = new BigNumber(x).toDecimalPlaces(toExponent(config.epsilon)) - const xSelected = bigNearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x + const xEpsilon = new BigNumber(x).toDecimalPlaces(toExponent(config.relTol)) + const xSelected = bigNearlyEqual(x, xEpsilon, config.relTol, config.absTol) ? xEpsilon : x return xSelected.toDecimalPlaces(0) }, @@ -125,11 +125,11 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, if (!n.isInteger()) { throw new TypeError(NO_INT) } // Same as BigNumber: unless user specifies more decimals than epsilon - const epsilonExponent = toExponent(config.epsilon) + const epsilonExponent = toExponent(config.relTol) if (n >= epsilonExponent) { return x.toDecimalPlaces(n.toNumber()) } const xEpsilon = x.toDecimalPlaces(epsilonExponent) - const xSelected = bigNearlyEqual(x, xEpsilon, config.epsilon, config.epsilon * 1e-3) ? xEpsilon : x + const xSelected = bigNearlyEqual(x, xEpsilon, config.relTol, config.absTol) ? xEpsilon : x return xSelected.toDecimalPlaces(n.toNumber()) }, diff --git a/src/function/geometry/intersect.js b/src/function/geometry/intersect.js index 02dfb7300d..ea6836cff0 100644 --- a/src/function/geometry/intersect.js +++ b/src/function/geometry/intersect.js @@ -119,7 +119,7 @@ export const createIntersect = /* #__PURE__ */ factory(name, dependencies, ({ ty const d2 = subtract(o2, p2b) const det = subtract(multiplyScalar(d1[0], d2[1]), multiplyScalar(d2[0], d1[1])) if (isZero(det)) return null - if (smaller(abs(det), config.epsilon)) { + if (smaller(abs(det), config.relTol)) { return null } const d20o11 = multiplyScalar(d2[0], o1[1]) diff --git a/src/function/matrix/eigs.js b/src/function/matrix/eigs.js index 140176126f..deed883971 100644 --- a/src/function/matrix/eigs.js +++ b/src/function/matrix/eigs.js @@ -68,7 +68,7 @@ export const createEigs = /* #__PURE__ */ factory(name, dependencies, ({ config, * * @param {Array | Matrix} x Matrix to be diagonalized * - * @param {number | BigNumber | OptsObject} [opts] Object with keys `precision`, defaulting to config.epsilon, and `eigenvectors`, defaulting to true and specifying whether to compute eigenvectors. If just a number, specifies precision. + * @param {number | BigNumber | OptsObject} [opts] Object with keys `precision`, defaulting to config.relTol, and `eigenvectors`, defaulting to true and specifying whether to compute eigenvectors. If just a number, specifies precision. * @return {{values: Array|Matrix, eigenvectors?: Array}} Object containing an array of eigenvalues and an array of {value: number|BigNumber, vector: Array|Matrix} objects. The eigenvectors property is undefined if eigenvectors were not requested. * */ @@ -100,7 +100,7 @@ export const createEigs = /* #__PURE__ */ factory(name, dependencies, ({ config, function doEigs (mat, opts = {}) { const computeVectors = 'eigenvectors' in opts ? opts.eigenvectors : true - const prec = opts.precision ?? config.epsilon + const prec = opts.precision ?? config.relTol const result = computeValuesAndVectors(mat, prec, computeVectors) if (opts.matricize) { result.values = matrix(result.values) diff --git a/src/function/matrix/eigs/realSymmetric.js b/src/function/matrix/eigs/realSymmetric.js index 2090a9a343..2e44e98866 100644 --- a/src/function/matrix/eigs/realSymmetric.js +++ b/src/function/matrix/eigs/realSymmetric.js @@ -7,7 +7,7 @@ export function createRealSymmetric ({ config, addScalar, subtract, abs, atan, c * @param {number} prec * @param {'number' | 'BigNumber'} type */ - function main (arr, N, prec = config.epsilon, type, computeVectors) { + function main (arr, N, prec = config.relTol, type, computeVectors) { if (type === 'number') { return diag(arr, prec, computeVectors) } @@ -85,7 +85,7 @@ export function createRealSymmetric ({ config, addScalar, subtract, abs, atan, c // get angle function getTheta (aii, ajj, aij) { const denom = (ajj - aii) - if (Math.abs(denom) <= config.epsilon) { + if (Math.abs(denom) <= config.relTol) { return Math.PI / 4.0 } else { return 0.5 * Math.atan(2.0 * aij / (ajj - aii)) @@ -95,7 +95,7 @@ export function createRealSymmetric ({ config, addScalar, subtract, abs, atan, c // get angle function getThetaBig (aii, ajj, aij) { const denom = subtract(ajj, aii) - if (abs(denom) <= config.epsilon) { + if (abs(denom) <= config.relTol) { return bignumber(-1).acos().div(4) } else { return multiplyScalar(0.5, atan(multiply(2.0, aij, inv(denom)))) diff --git a/src/function/relational/compare.js b/src/function/relational/compare.js index a140e0cb20..429c6c9461 100644 --- a/src/function/relational/compare.js +++ b/src/function/relational/compare.js @@ -72,7 +72,7 @@ export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ type }, 'BigNumber, BigNumber': function (x, y) { - return bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return bigNearlyEqual(x, y, config.relTol, config.absTol) ? new BigNumber(0) : new BigNumber(x.cmp(y)) }, @@ -97,7 +97,7 @@ export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ type export const createCompareNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return nearlyEqual(x, y, config.relTol, config.absTol) ? 0 : (x > y ? 1 : -1) } diff --git a/src/function/relational/equalScalar.js b/src/function/relational/equalScalar.js index 140a578eed..f922fece17 100644 --- a/src/function/relational/equalScalar.js +++ b/src/function/relational/equalScalar.js @@ -25,11 +25,11 @@ export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ }, 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return nearlyEqual(x, y, config.relTol, config.absTol) }, 'BigNumber, BigNumber': function (x, y) { - return x.eq(y) || bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x.eq(y) || bigNearlyEqual(x, y, config.relTol, config.absTol) }, 'Fraction, Fraction': function (x, y) { @@ -37,7 +37,7 @@ export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ }, 'Complex, Complex': function (x, y) { - return complexEquals(x, y, config.epsilon) + return complexEquals(x, y, config.relTol, config.absTol) } }, compareUnits) }) @@ -45,7 +45,7 @@ export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ export const createEqualScalarNumber = factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return nearlyEqual(x, y, config.relTol, config.absTol) } }) }) diff --git a/src/function/relational/larger.js b/src/function/relational/larger.js index 90a77598ac..5f453f21b4 100644 --- a/src/function/relational/larger.js +++ b/src/function/relational/larger.js @@ -61,7 +61,7 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed 'boolean, boolean': (x, y) => x > y, 'BigNumber, BigNumber': function (x, y) { - return x.gt(y) && !bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x.gt(y) && !bigNearlyEqual(x, y, config.relTol, config.absTol) }, 'Fraction, Fraction': (x, y) => (x.compare(y) === 1), @@ -82,7 +82,7 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed export const createLargerNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x > y && !nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x > y && !nearlyEqual(x, y, config.relTol, config.absTol) } }) }) diff --git a/src/function/relational/largerEq.js b/src/function/relational/largerEq.js index 15f0dc51ac..f622767a02 100644 --- a/src/function/relational/largerEq.js +++ b/src/function/relational/largerEq.js @@ -57,7 +57,7 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ 'boolean, boolean': (x, y) => x >= y, 'BigNumber, BigNumber': function (x, y) { - return x.gte(y) || bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x.gte(y) || bigNearlyEqual(x, y, config.relTol, config.absTol) }, 'Fraction, Fraction': (x, y) => (x.compare(y) !== -1), @@ -78,7 +78,7 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ export const createLargerEqNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x >= y || nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x >= y || nearlyEqual(x, y, config.relTol, config.absTol) } }) }) diff --git a/src/function/relational/smaller.js b/src/function/relational/smaller.js index 36affd5e60..2d525a3816 100644 --- a/src/function/relational/smaller.js +++ b/src/function/relational/smaller.js @@ -61,7 +61,7 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type 'boolean, boolean': (x, y) => x < y, 'BigNumber, BigNumber': function (x, y) { - return x.lt(y) && !bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x.lt(y) && !bigNearlyEqual(x, y, config.relTol, config.absTol) }, 'Fraction, Fraction': (x, y) => (x.compare(y) === -1), @@ -82,7 +82,7 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type export const createSmallerNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x < y && !nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x < y && !nearlyEqual(x, y, config.relTol, config.absTol) } }) }) diff --git a/src/function/relational/smallerEq.js b/src/function/relational/smallerEq.js index 95027aaa32..28a318dd31 100644 --- a/src/function/relational/smallerEq.js +++ b/src/function/relational/smallerEq.js @@ -57,7 +57,7 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty 'boolean, boolean': (x, y) => (x <= y), 'BigNumber, BigNumber': function (x, y) { - return x.lte(y) || bigNearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x.lte(y) || bigNearlyEqual(x, y, config.relTol, config.absTol) }, 'Fraction, Fraction': (x, y) => (x.compare(y) !== 1), @@ -78,7 +78,7 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty export const createSmallerEqNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { return typed(name, { 'number, number': function (x, y) { - return x <= y || nearlyEqual(x, y, config.epsilon, config.epsilon * 1e-3) + return x <= y || nearlyEqual(x, y, config.relTol, config.absTol) } }) }) diff --git a/src/function/special/zeta.js b/src/function/special/zeta.js index d401232655..f4e1e589f6 100644 --- a/src/function/special/zeta.js +++ b/src/function/special/zeta.js @@ -38,7 +38,7 @@ export const createZeta = /* #__PURE__ */ factory(name, dependencies, ({ typed, value => new BigNumber(value), () => { // epsilon is for example 1e-12. Extract the positive exponent 12 from that - return Math.abs(Math.log10(config.epsilon)) + return Math.abs(Math.log10(config.relTol)) } ), Complex: zetaComplex diff --git a/src/utils/bignumber/nearlyEqual.js b/src/utils/bignumber/nearlyEqual.js index 418a2db6be..ea5c69cfba 100644 --- a/src/utils/bignumber/nearlyEqual.js +++ b/src/utils/bignumber/nearlyEqual.js @@ -9,8 +9,8 @@ * @throws {Error} If `absTol` is less than 0. * * @example - * nearlyEqual(1.000000001, 1.0, 1e-8); // true - * nearlyEqual(1.000000002, 1.0, 1e-9); // false + * nearlyEqual(1.000000001, 1.0, 1e-9); // true + * nearlyEqual(1.000000002, 1.0, 0); // false * nearlyEqual(1.0, 1.009, undefined, 0.02); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ diff --git a/src/utils/complex.js b/src/utils/complex.js index fcf1b80348..f7263ac2f1 100644 --- a/src/utils/complex.js +++ b/src/utils/complex.js @@ -5,9 +5,10 @@ import { nearlyEqual } from './number.js' * Does not use or change the global Complex.EPSILON setting * @param {Complex} x * @param {Complex} y - * @param {number} epsilon + * @param {number} relTol + * @param {number} absTol * @returns {boolean} */ -export function complexEquals (x, y, epsilon) { - return nearlyEqual(x.re, y.re, epsilon, epsilon * 1e-3) && nearlyEqual(x.im, y.im, epsilon, epsilon * 1e-3) +export function complexEquals (x, y, relTol, absTol) { + return nearlyEqual(x.re, y.re, relTol, absTol) && nearlyEqual(x.im, y.im, relTol, absTol) } diff --git a/src/utils/number.js b/src/utils/number.js index f0cb735686..e1e4662ac4 100644 --- a/src/utils/number.js +++ b/src/utils/number.js @@ -627,11 +627,11 @@ export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E- * * @example * nearlyEqual(1.000000001, 1.0, 1e-8); // true - * nearlyEqual(1.000000002, 1.0, 1e-9); // false + * nearlyEqual(1.000000002, 1.0, 0); // false * nearlyEqual(1.0, 1.009, undefined, 0.01); // true * nearlyEqual(0.000000001, 0.0, undefined, 1e-8); // true */ -export function nearlyEqual (a, b, relTol = 1e-12, absTol = 1e-9) { +export function nearlyEqual (a, b, relTol = 1e-8, absTol = 0) { if (relTol <= 0) { throw new Error('Relative tolerance must be greater than 0') } diff --git a/test/unit-tests/function/arithmetic/round.test.js b/test/unit-tests/function/arithmetic/round.test.js index 04b841e401..25acf4a0c1 100644 --- a/test/unit-tests/function/arithmetic/round.test.js +++ b/test/unit-tests/function/arithmetic/round.test.js @@ -173,13 +173,13 @@ describe('round', function () { assert.deepStrictEqual(round(math.matrix([1.7, 2.3])).valueOf(), [2, 2]) }) - describe('changing config.epsilon during runtime', function () { - it('uses default config.epsilon of 1e-12', function () { + describe('changing config.relTol during runtime', function () { + it('uses default config.relTol of 1e-12', function () { assert.strictEqual(math2.round((0.000000000001459), 12), 1e-12) assert.deepStrictEqual(math2.round(bignumber(1.49e-12), bignumber(12)), bignumber(1e-12)) }) - it('uses updated config.epsilon value', function () { + it('uses updated config.relTol value', function () { math2.config({ epsilon: 1e-13 }) assert.strictEqual(math2.round((0.000000000001459), 12), 1e-12) assert.deepStrictEqual(math2.round(bignumber(1.49e-12), bignumber(12)), bignumber(1e-12)) diff --git a/test/unit-tests/function/matrix/eigs.test.js b/test/unit-tests/function/matrix/eigs.test.js index 9850224677..ecb46daa9c 100644 --- a/test/unit-tests/function/matrix/eigs.test.js +++ b/test/unit-tests/function/matrix/eigs.test.js @@ -214,7 +214,7 @@ describe('eigs', function () { approx.equal(ev[1].value, 2) approx.equal(ev[0].vector[0], 0) approx.equal(ev[0].vector[1], 0) - assert.ok(abs(ev[0].vector[2]) > math.config.epsilon) + assert.ok(abs(ev[0].vector[2]) > math.config.relTol) approx.equal(ev[1].vector[0], -ev[1].vector[2]) approx.equal(ev[1].vector[1], 0) const web2 = eigs([[1, 1, 0], [0, 1, 2], [0, 0, 3]]) // https://www2.math.upenn.edu/~moose/240S2013/slides7-31.pdf @@ -224,7 +224,7 @@ describe('eigs', function () { assert.strictEqual(ev2[1].value, 3) assert.strictEqual(ev2[0].vector[1], 0) assert.strictEqual(ev2[0].vector[2], 0) - assert.ok(abs(ev2[0].vector[0]) > math.config.epsilon) + assert.ok(abs(ev2[0].vector[0]) > math.config.relTol) assert.strictEqual(ev2[1].vector[1], ev2[1].vector[2]) approx.equal(ev2[1].vector[1], 2 * ev2[1].vector[0]) }) diff --git a/test/unit-tests/function/relational/compare.test.js b/test/unit-tests/function/relational/compare.test.js index d9ca0f48da..4c3e919e98 100644 --- a/test/unit-tests/function/relational/compare.test.js +++ b/test/unit-tests/function/relational/compare.test.js @@ -178,7 +178,7 @@ describe('compare', function () { assert.strictEqual(mymath.compare(1, 0.991), 1) assert.strictEqual(mymath.compare(mymath.bignumber(1), mymath.bignumber(0.991)).valueOf(), '1') - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.compare(1, 0.991), 0) assert.strictEqual(mymath.compare(mymath.bignumber(1), mymath.bignumber(0.991)).valueOf(), '0') }) diff --git a/test/unit-tests/function/relational/compareNatural.test.js b/test/unit-tests/function/relational/compareNatural.test.js index 722adc74bf..4bdc0487bc 100644 --- a/test/unit-tests/function/relational/compareNatural.test.js +++ b/test/unit-tests/function/relational/compareNatural.test.js @@ -225,7 +225,7 @@ describe('compareNatural', function () { assert.strictEqual(mymath.compareNatural(1, 0.991), 1) assert.strictEqual(mymath.compareNatural(mymath.bignumber(1), mymath.bignumber(0.991)).valueOf(), 1) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.compareNatural(1, 0.991), 0) assert.strictEqual(mymath.compareNatural(mymath.bignumber(1), mymath.bignumber(0.991)), 0) }) diff --git a/test/unit-tests/function/relational/equal.test.js b/test/unit-tests/function/relational/equal.test.js index 056cca837f..f510ed7cf3 100644 --- a/test/unit-tests/function/relational/equal.test.js +++ b/test/unit-tests/function/relational/equal.test.js @@ -152,7 +152,7 @@ describe('equal', function () { assert.strictEqual(mymath.equal(mymath.bignumber(1), mymath.bignumber(0.991)), false) assert.strictEqual(mymath.equal(mymath.complex(1, 0), mymath.complex(0.991, 0)), false) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.equal(1, 0.991), true) assert.strictEqual(mymath.equal(mymath.bignumber(1), mymath.bignumber(0.991)), true) assert.strictEqual(mymath.equal(mymath.complex(1, 0), mymath.complex(0.991, 0)), true) diff --git a/test/unit-tests/function/relational/larger.test.js b/test/unit-tests/function/relational/larger.test.js index 8c768b1ba3..efdeccacd5 100644 --- a/test/unit-tests/function/relational/larger.test.js +++ b/test/unit-tests/function/relational/larger.test.js @@ -95,7 +95,7 @@ describe('larger', function () { assert.strictEqual(mymath.larger(1, 0.991), true) assert.strictEqual(mymath.larger(mymath.bignumber(1), mymath.bignumber(0.991)), true) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.larger(1, 0.991), false) assert.strictEqual(mymath.larger(mymath.bignumber(1), mymath.bignumber(0.991)), false) }) diff --git a/test/unit-tests/function/relational/largerEq.test.js b/test/unit-tests/function/relational/largerEq.test.js index d34d481d85..caf9247f2a 100644 --- a/test/unit-tests/function/relational/largerEq.test.js +++ b/test/unit-tests/function/relational/largerEq.test.js @@ -97,7 +97,7 @@ describe('largerEq', function () { assert.strictEqual(mymath.largerEq(1, 1.01), false) assert.strictEqual(mymath.largerEq(mymath.bignumber(1), mymath.bignumber(1.01)), false) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.largerEq(1, 1.01), true) assert.strictEqual(mymath.largerEq(mymath.bignumber(1), mymath.bignumber(1.01)), true) }) diff --git a/test/unit-tests/function/relational/smaller.test.js b/test/unit-tests/function/relational/smaller.test.js index 061f36fb00..157a40edd8 100644 --- a/test/unit-tests/function/relational/smaller.test.js +++ b/test/unit-tests/function/relational/smaller.test.js @@ -102,7 +102,7 @@ describe('smaller', function () { assert.strictEqual(mymath.smaller(0.991, 1), true) assert.strictEqual(mymath.smaller(mymath.bignumber(0.991), mymath.bignumber(1)), true) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.smaller(0.991, 1), false) assert.strictEqual(mymath.smaller(mymath.bignumber(0.991), mymath.bignumber(1)), false) }) diff --git a/test/unit-tests/function/relational/smallerEq.test.js b/test/unit-tests/function/relational/smallerEq.test.js index 6cbc304b0d..e3d43e47fd 100644 --- a/test/unit-tests/function/relational/smallerEq.test.js +++ b/test/unit-tests/function/relational/smallerEq.test.js @@ -95,12 +95,12 @@ describe('smallerEq', function () { assert.strictEqual(smallerEq(unit('101cm'), unit('1m')), false) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.smallerEq(1.01, 1), false) assert.strictEqual(mymath.smallerEq(mymath.bignumber(1.01), mymath.bignumber(1)), false) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.smallerEq(1.01, 1), true) assert.strictEqual(mymath.smallerEq(mymath.bignumber(1.01), mymath.bignumber(1)), true) }) diff --git a/test/unit-tests/function/relational/unequal.test.js b/test/unit-tests/function/relational/unequal.test.js index 8300c9e6d1..d00b58c20b 100644 --- a/test/unit-tests/function/relational/unequal.test.js +++ b/test/unit-tests/function/relational/unequal.test.js @@ -132,13 +132,13 @@ describe('unequal', function () { assert.strictEqual(unequal(2, undefined), true) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.unequal(1, 0.991), true) assert.strictEqual(mymath.unequal(mymath.bignumber(1), mymath.bignumber(0.991)), true) assert.strictEqual(mymath.unequal(mymath.complex(1, 0), mymath.complex(0.991, 0)), true) - mymath.config({ epsilon: 1e-2 }) + mymath.config({ relTol: 1e-2 }) assert.strictEqual(mymath.unequal(1, 0.991), false) assert.strictEqual(mymath.unequal(mymath.bignumber(1), mymath.bignumber(0.991)), false) assert.strictEqual(mymath.unequal(mymath.complex(1, 0), mymath.complex(0.991, 0)), false) diff --git a/test/unit-tests/utils/number.test.js b/test/unit-tests/utils/number.test.js index f71d64f183..1233e21fa9 100644 --- a/test/unit-tests/utils/number.test.js +++ b/test/unit-tests/utils/number.test.js @@ -585,9 +585,9 @@ describe('number', function () { assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3, undefined), true) assert.strictEqual(nearlyEqual(0.1 + 0.2, 0.3, undefined, undefined), true) - assert.strictEqual(nearlyEqual(1.2 + 1e-8, 1.2), false) - assert.strictEqual(nearlyEqual(1.2 + 1e-8, 1.2, undefined), false) - assert.strictEqual(nearlyEqual(1.2 + 1e-8, 1.2, undefined, undefined), false) + assert.strictEqual(nearlyEqual(1.2 + 1e-7, 1.2), false) + assert.strictEqual(nearlyEqual(1.2 + 1e-7, 1.2, undefined), false) + assert.strictEqual(nearlyEqual(1.2 + 1e-7, 1.2, undefined, undefined), false) }) }) }) From e7eb46e9a249b7f210d95f4c85392fd1c9eed676 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sun, 7 Apr 2024 00:15:40 -0600 Subject: [PATCH 10/14] Changed references to epsilon in docs and tests --- docs/core/configuration.md | 9 +- docs/datatypes/bignumbers.md | 11 +- docs/datatypes/numbers.md | 7 +- src/core/create.js | 5 +- src/core/function/config.js | 5 +- src/function/arithmetic/round.js | 8 +- src/function/relational/compare.js | 2 +- src/function/relational/compareNatural.js | 2 +- src/function/relational/equal.js | 2 +- src/function/relational/larger.js | 2 +- src/function/relational/largerEq.js | 2 +- src/function/relational/smaller.js | 2 +- src/function/relational/smallerEq.js | 2 +- src/function/relational/unequal.js | 2 +- src/function/special/zeta.js | 2 +- src/utils/complex.js | 12 +-- test/node-tests/defaultInstance.test.js | 12 ++- test/unit-tests/constants.test.js | 4 +- .../function/arithmetic/round.test.js | 2 +- .../function/relational/compare.test.js | 2 +- .../relational/compareNatural.test.js | 2 +- .../function/relational/equal.test.js | 2 +- .../function/relational/larger.test.js | 2 +- .../function/relational/largerEq.test.js | 2 +- .../function/relational/smaller.test.js | 2 +- .../type/unit/physicalConstants.test.js | 4 +- .../utils/bignumber/nearlyEqual.test.js | 100 +++++++++-------- test/unit-tests/utils/number.test.js | 102 ++++++++++-------- types/index.d.ts | 35 +++--- 29 files changed, 188 insertions(+), 158 deletions(-) diff --git a/docs/core/configuration.md b/docs/core/configuration.md index 3365794dfe..f1223d7734 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -8,7 +8,8 @@ import { create, all } from 'mathjs' // create a mathjs instance with configuration const config = { - epsilon: 1e-12, + relTol: 1e-12, + absTol: 1e.15, matrix: 'Matrix', number: 'number', precision: 64, @@ -28,10 +29,14 @@ math.config({ The following configuration options are available: -- `epsilon`. The minimum relative difference used to test equality between two +- `relTol`. The minimum relative difference used to test equality between two compared values. This value is used by all relational functions. Default value is `1e-12`. +- `absTol`. The minimum absolute difference used to test equality between two + compared values. This value is used by all relational functions. + Default value is `1e-15`. + - `matrix`. The default type of matrix output for functions. Available values are: `'Matrix'` (default) or `'Array'`. Where possible, the type of matrix output from functions is determined from diff --git a/docs/datatypes/bignumbers.md b/docs/datatypes/bignumbers.md index 220c5d5498..974e937d5b 100644 --- a/docs/datatypes/bignumbers.md +++ b/docs/datatypes/bignumbers.md @@ -24,7 +24,8 @@ math.config({ number: 'BigNumber', // Default type of number: // 'number' (default), 'BigNumber', or 'Fraction' precision: 64, // Number of significant digits for BigNumbers - epsilon: 1e-60 + relTol: 1e-60, + absTol: 1e-63 }) // use math @@ -34,12 +35,12 @@ math.evaluate('0.1 + 0.2') // BigNumber, 0.3 The default precision for BigNumber is 64 digits, and can be configured with the option `precision`. -Note that we also change the configuration of `epsilon` -to be close to the precision limit of our BigNumbers. `epsilon` is used for +Note that we also change the configuration of `relTol` and `absTol` +to be close to the precision limit of our BigNumbers. `relTol` and `absTol` are used for example in relational and rounding functions (`equal`, `larger`, `smaller`, `round`, `floor`, etc) to determine when a value is nearly equal, -see [Equality](numbers.md#equality). If we would leave `epsilon` unchanged, -having the default value of `1e-12`, we could get inaccurate and misleading +see [Equality](numbers.md#equality). If we would leave `relTol` and `absTol` unchanged, +having the default value of `1e-12` and `1e-15` respectively, we could get inaccurate and misleading results since we're now working with a higher precision. diff --git a/docs/datatypes/numbers.md b/docs/datatypes/numbers.md index c250cc8cf6..5d0372253a 100644 --- a/docs/datatypes/numbers.md +++ b/docs/datatypes/numbers.md @@ -74,16 +74,13 @@ return exactly `0.3`. To solve this problem, the relational functions of math.js check whether the relative and absolute differences between the compared values is smaller than the configured -option `epsilon`. In pseudo code (without exceptions for 0, Infinity and NaN): +option `relTol` and `absTol`. In pseudo code (without exceptions for 0, Infinity and NaN): - relTol = epsilon - absTol = epsilon / 1000 abs(a-b) <= max(relTol * max(abs(a), abs(b)), absTol) where: - - `EPSILON` is the relative difference between x and y. Epsilon is configurable - and is `1e-12` by default. See [Configuration](../core/configuration.md). + - `relTol` is the relative tolerance between x and y and `absTol` the absolute tolerance. Relative tolerance and absolute tolerance are configurable and are `1e-12` and `1e-15` respectively by default. See [Configuration](../core/configuration.md). - `DBL_EPSILON` is the minimum positive floating point number such that `1.0 + DBL_EPSILON !== 1.0`. This is a constant with a value of approximately `2.2204460492503130808472633361816e-16`. diff --git a/src/core/create.js b/src/core/create.js index 2999898d36..864162598f 100644 --- a/src/core/create.js +++ b/src/core/create.js @@ -63,9 +63,12 @@ import { DEFAULT_CONFIG } from './config.js' * The object can contain nested objects, * all nested objects will be flattened. * @param {Object} [config] Available options: - * {number} epsilon + * {number} relTol * Minimum relative difference between two * compared values, used by all comparison functions. + * {number} absTol + * Minimum absolute difference between two + * compared values, used by all comparison functions. * {string} matrix * A string 'Matrix' (default) or 'Array'. * {string} number diff --git a/src/core/function/config.js b/src/core/function/config.js index b93f76fae1..9eebdfeead 100644 --- a/src/core/function/config.js +++ b/src/core/function/config.js @@ -29,9 +29,12 @@ export function configFactory (config, emit) { * math.evaluate('0.4') // outputs Fraction 2/5 * * @param {Object} [options] Available options: - * {number} epsilon + * {number} relTol * Minimum relative difference between two * compared values, used by all comparison functions. + * {number} absTol + * Minimum absolute difference between two + * compared values, used by all comparison functions. * {string} matrix * A string 'Matrix' (default) or 'Array'. * {string} number diff --git a/src/function/arithmetic/round.js b/src/function/arithmetic/round.js index a8cbccea19..7e585fb5af 100644 --- a/src/function/arithmetic/round.js +++ b/src/function/arithmetic/round.js @@ -75,14 +75,14 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, */ return typed(name, { number: function (x) { - // Handle round off errors by first rounding to epsilon precision + // Handle round off errors by first rounding to relTol precision const xEpsilon = roundNumber(x, toExponent(config.relTol)) const xSelected = nearlyEqual(x, xEpsilon, config.relTol, config.absTol) ? xEpsilon : x return roundNumber(xSelected) }, 'number, number': function (x, n) { - // Same as number: unless user specifies more decimals than epsilon + // Same as number: unless user specifies more decimals than relTol const epsilonExponent = toExponent(config.relTol) if (n >= epsilonExponent) { return roundNumber(x, n) } @@ -115,7 +115,7 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, BigNumber: function (x) { - // Handle round off errors by first rounding to epsilon precision + // Handle round off errors by first rounding to relTol precision const xEpsilon = new BigNumber(x).toDecimalPlaces(toExponent(config.relTol)) const xSelected = bigNearlyEqual(x, xEpsilon, config.relTol, config.absTol) ? xEpsilon : x return xSelected.toDecimalPlaces(0) @@ -124,7 +124,7 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, 'BigNumber, BigNumber': function (x, n) { if (!n.isInteger()) { throw new TypeError(NO_INT) } - // Same as BigNumber: unless user specifies more decimals than epsilon + // Same as BigNumber: unless user specifies more decimals than relTol const epsilonExponent = toExponent(config.relTol) if (n >= epsilonExponent) { return x.toDecimalPlaces(n.toNumber()) } diff --git a/src/function/relational/compare.js b/src/function/relational/compare.js index 429c6c9461..963f349566 100644 --- a/src/function/relational/compare.js +++ b/src/function/relational/compare.js @@ -30,7 +30,7 @@ export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ type * Compare two values. Returns 1 when x > y, -1 when x < y, and 0 when x == y. * * x and y are considered equal when the relative difference between x and y - * is smaller than the configured epsilon. The function cannot be used to + * is smaller than the configured absTol and relTol. The function cannot be used to * compare values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/relational/compareNatural.js b/src/function/relational/compareNatural.js index 9e882ad46e..17239331fa 100644 --- a/src/function/relational/compareNatural.js +++ b/src/function/relational/compareNatural.js @@ -19,7 +19,7 @@ export const createCompareNatural = /* #__PURE__ */ factory(name, dependencies, * the function compares in a natural way. * * For numeric values, x and y are considered equal when the relative - * difference between x and y is smaller than the configured epsilon. + * difference between x and y is smaller than the configured relTol and absTol. * The function cannot be used to compare values smaller than * approximately 2.22e-16. * diff --git a/src/function/relational/equal.js b/src/function/relational/equal.js index 2a14720202..e9c2bc36ea 100644 --- a/src/function/relational/equal.js +++ b/src/function/relational/equal.js @@ -23,7 +23,7 @@ export const createEqual = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Test whether two values are equal. * * The function tests whether the relative difference between x and y is - * smaller than the configured epsilon. The function cannot be used to + * smaller than the configured relTol and absTol. The function cannot be used to * compare values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/relational/larger.js b/src/function/relational/larger.js index 5f453f21b4..30303a2104 100644 --- a/src/function/relational/larger.js +++ b/src/function/relational/larger.js @@ -27,7 +27,7 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed * Test whether value x is larger than y. * * The function returns true when x is larger than y and the relative - * difference between x and y is larger than the configured epsilon. The + * difference between x and y is larger than the configured relTol and absTol. The * function cannot be used to compare values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/relational/largerEq.js b/src/function/relational/largerEq.js index f622767a02..9f645e99e6 100644 --- a/src/function/relational/largerEq.js +++ b/src/function/relational/largerEq.js @@ -27,7 +27,7 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ * Test whether value x is larger or equal to y. * * The function returns true when x is larger than y or the relative - * difference between x and y is smaller than the configured epsilon. The + * difference between x and y is smaller than the configured relTol and absTol. The * function cannot be used to compare values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/relational/smaller.js b/src/function/relational/smaller.js index 2d525a3816..4894aded66 100644 --- a/src/function/relational/smaller.js +++ b/src/function/relational/smaller.js @@ -27,7 +27,7 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type * Test whether value x is smaller than y. * * The function returns true when x is smaller than y and the relative - * difference between x and y is smaller than the configured epsilon. The + * difference between x and y is smaller than the configured relTol and absTol. The * function cannot be used to compare values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/relational/smallerEq.js b/src/function/relational/smallerEq.js index 28a318dd31..7656ba9d4a 100644 --- a/src/function/relational/smallerEq.js +++ b/src/function/relational/smallerEq.js @@ -27,7 +27,7 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty * Test whether value x is smaller or equal to y. * * The function returns true when x is smaller than y or the relative - * difference between x and y is smaller than the configured epsilon. The + * difference between x and y is smaller than the configured relTol and absTol. The * function cannot be used to compare values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/relational/unequal.js b/src/function/relational/unequal.js index 53d1d7ffa2..207ca7d21d 100644 --- a/src/function/relational/unequal.js +++ b/src/function/relational/unequal.js @@ -24,7 +24,7 @@ export const createUnequal = /* #__PURE__ */ factory(name, dependencies, ({ type * Test whether two values are unequal. * * The function tests whether the relative difference between x and y is - * larger than the configured epsilon. The function cannot be used to compare + * larger than the configured relTol and absTol. The function cannot be used to compare * values smaller than approximately 2.22e-16. * * For matrices, the function is evaluated element wise. diff --git a/src/function/special/zeta.js b/src/function/special/zeta.js index f4e1e589f6..675a9080d3 100644 --- a/src/function/special/zeta.js +++ b/src/function/special/zeta.js @@ -37,7 +37,7 @@ export const createZeta = /* #__PURE__ */ factory(name, dependencies, ({ typed, s, value => new BigNumber(value), () => { - // epsilon is for example 1e-12. Extract the positive exponent 12 from that + // relTol is for example 1e-12. Extract the positive exponent 12 from that return Math.abs(Math.log10(config.relTol)) } ), diff --git a/src/utils/complex.js b/src/utils/complex.js index f7263ac2f1..9439d549f4 100644 --- a/src/utils/complex.js +++ b/src/utils/complex.js @@ -1,13 +1,13 @@ import { nearlyEqual } from './number.js' /** - * Test whether two complex values are equal provided a given epsilon. + * Test whether two complex values are equal provided a given relTol and absTol. * Does not use or change the global Complex.EPSILON setting - * @param {Complex} x - * @param {Complex} y - * @param {number} relTol - * @param {number} absTol - * @returns {boolean} + * @param {Complex} x - The first complex number for comparison. + * @param {Complex} y - The second complex number for comparison. + * @param {number} relTol - The relative tolerance for comparison. + * @param {number} absTol - The absolute tolerance for comparison. + * @returns {boolean} - Returns true if the two complex numbers are equal within the given tolerances, otherwise returns false. */ export function complexEquals (x, y, relTol, absTol) { return nearlyEqual(x.re, y.re, relTol, absTol) && nearlyEqual(x.im, y.im, relTol, absTol) diff --git a/test/node-tests/defaultInstance.test.js b/test/node-tests/defaultInstance.test.js index 211f168457..a04a1358d7 100644 --- a/test/node-tests/defaultInstance.test.js +++ b/test/node-tests/defaultInstance.test.js @@ -12,7 +12,8 @@ describe('defaultInstance', function () { number: 'number', precision: 64, predictable: false, - epsilon: 1e-12, + relTol: 1e-12, + absTol: 1e-15, randomSeed: null }) }) @@ -29,7 +30,8 @@ describe('defaultInstance', function () { number: 'BigNumber', precision: 64, predictable: false, - epsilon: 1e-12, + relTol: 1e-12, + absTol: 1e-15, randomSeed: null }) }) @@ -72,7 +74,8 @@ describe('defaultInstance', function () { number: 'BigNumber', precision: 4, predictable: true, - epsilon: 1e-12, + relTol: 1e-12, + absTol: 1e-15, randomSeed: null }) @@ -92,7 +95,8 @@ describe('defaultInstance', function () { number: 'number', precision: 64, predictable: false, - epsilon: 1e-12, + relTol: 1e-12, + absTol: 1e-15, randomSeed: null }) diff --git a/test/unit-tests/constants.test.js b/test/unit-tests/constants.test.js index 3789855f07..265bfc3047 100644 --- a/test/unit-tests/constants.test.js +++ b/test/unit-tests/constants.test.js @@ -23,7 +23,7 @@ import { describe('constants', function () { describe('number', function () { - const config = { number: 'number', precision: 64, epsilon: 1e-12 } + const config = { number: 'number', precision: 64, relTol: 1e-12 } const BigNumber = createBigNumberClass({ config }) const Complex = createComplexClass({ config }) const dependencies = { @@ -86,7 +86,7 @@ describe('constants', function () { }) describe('bignumbers', function () { - const config = { number: 'BigNumber', precision: 64, epsilon: 1e-12 } + const config = { number: 'BigNumber', precision: 64, relTol: 1e-12 } const BigNumber = createBigNumberClass({ config }) const Complex = createComplexClass({ config }) const dependencies = { diff --git a/test/unit-tests/function/arithmetic/round.test.js b/test/unit-tests/function/arithmetic/round.test.js index 25acf4a0c1..536925d91c 100644 --- a/test/unit-tests/function/arithmetic/round.test.js +++ b/test/unit-tests/function/arithmetic/round.test.js @@ -180,7 +180,7 @@ describe('round', function () { }) it('uses updated config.relTol value', function () { - math2.config({ epsilon: 1e-13 }) + math2.config({ relTol: 1e-13 }) assert.strictEqual(math2.round((0.000000000001459), 12), 1e-12) assert.deepStrictEqual(math2.round(bignumber(1.49e-12), bignumber(12)), bignumber(1e-12)) }) diff --git a/test/unit-tests/function/relational/compare.test.js b/test/unit-tests/function/relational/compare.test.js index 4c3e919e98..aee6b369df 100644 --- a/test/unit-tests/function/relational/compare.test.js +++ b/test/unit-tests/function/relational/compare.test.js @@ -173,7 +173,7 @@ describe('compare', function () { }) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.compare(1, 0.991), 1) assert.strictEqual(mymath.compare(mymath.bignumber(1), mymath.bignumber(0.991)).valueOf(), '1') diff --git a/test/unit-tests/function/relational/compareNatural.test.js b/test/unit-tests/function/relational/compareNatural.test.js index 4bdc0487bc..cbe82244c1 100644 --- a/test/unit-tests/function/relational/compareNatural.test.js +++ b/test/unit-tests/function/relational/compareNatural.test.js @@ -219,7 +219,7 @@ describe('compareNatural', function () { assert.strictEqual(compareNatural({ a: 2, b: { c: 3 } }, { a: 2, b: { c: 4 } }), -1) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.compareNatural(1, 0.991), 1) diff --git a/test/unit-tests/function/relational/equal.test.js b/test/unit-tests/function/relational/equal.test.js index f510ed7cf3..16a037011a 100644 --- a/test/unit-tests/function/relational/equal.test.js +++ b/test/unit-tests/function/relational/equal.test.js @@ -146,7 +146,7 @@ describe('equal', function () { assert.throws(function () { equal('A', 'B') }, /Cannot convert "A" to a number/) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.equal(1, 0.991), false) assert.strictEqual(mymath.equal(mymath.bignumber(1), mymath.bignumber(0.991)), false) diff --git a/test/unit-tests/function/relational/larger.test.js b/test/unit-tests/function/relational/larger.test.js index efdeccacd5..813643eba0 100644 --- a/test/unit-tests/function/relational/larger.test.js +++ b/test/unit-tests/function/relational/larger.test.js @@ -90,7 +90,7 @@ describe('larger', function () { assert.strictEqual(larger(unit('101cm'), unit('1m')), true) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.larger(1, 0.991), true) assert.strictEqual(mymath.larger(mymath.bignumber(1), mymath.bignumber(0.991)), true) diff --git a/test/unit-tests/function/relational/largerEq.test.js b/test/unit-tests/function/relational/largerEq.test.js index caf9247f2a..4213a278cf 100644 --- a/test/unit-tests/function/relational/largerEq.test.js +++ b/test/unit-tests/function/relational/largerEq.test.js @@ -92,7 +92,7 @@ describe('largerEq', function () { assert.strictEqual(largerEq(unit('101cm'), unit('1m')), true) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.largerEq(1, 1.01), false) assert.strictEqual(mymath.largerEq(mymath.bignumber(1), mymath.bignumber(1.01)), false) diff --git a/test/unit-tests/function/relational/smaller.test.js b/test/unit-tests/function/relational/smaller.test.js index 157a40edd8..a1fa9932b4 100644 --- a/test/unit-tests/function/relational/smaller.test.js +++ b/test/unit-tests/function/relational/smaller.test.js @@ -97,7 +97,7 @@ describe('smaller', function () { assert.strictEqual(smaller(unit('101cm'), unit('1m')), false) }) - it('should apply configuration option epsilon', function () { + it('should apply configuration option relTol', function () { const mymath = math.create() assert.strictEqual(mymath.smaller(0.991, 1), true) assert.strictEqual(mymath.smaller(mymath.bignumber(0.991), mymath.bignumber(1)), true) diff --git a/test/unit-tests/type/unit/physicalConstants.test.js b/test/unit-tests/type/unit/physicalConstants.test.js index eda53edccc..c31320d550 100644 --- a/test/unit-tests/type/unit/physicalConstants.test.js +++ b/test/unit-tests/type/unit/physicalConstants.test.js @@ -60,7 +60,7 @@ const { BigNumber, Unit } = math describe('physical constants', function () { it('should return the correct value and unit for physical constants', function () { // Note: to keep these unit tests readable and compact, the toString() of the units is compared - const config = { number: 'number', precision: 64, epsilon: 1e-12 } + const config = { number: 'number', precision: 64, relTol: 1e-12 } const dependencies = { config, BigNumber, Unit } // Universal constants @@ -137,7 +137,7 @@ describe('physical constants', function () { }) it('should create BigNumber unit values if configured', function () { - const config = { number: 'BigNumber', precision: 64, epsilon: 1e-12 } + const config = { number: 'BigNumber', precision: 64, relTol: 1e-12 } const dependencies = { config, BigNumber, Unit } const molarMass = createMolarMass(dependencies) diff --git a/test/unit-tests/utils/bignumber/nearlyEqual.test.js b/test/unit-tests/utils/bignumber/nearlyEqual.test.js index a497d49b36..119536ed65 100644 --- a/test/unit-tests/utils/bignumber/nearlyEqual.test.js +++ b/test/unit-tests/utils/bignumber/nearlyEqual.test.js @@ -5,72 +5,78 @@ import BigNumber from 'decimal.js' describe('nearlyEqual', function () { it('should test whether two BigNumbers are nearly equal', function () { - const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.95), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.98), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.991), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.1), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.05), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.02), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.01), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1), epsilon, epsilon * 1e-3), true) + const relTol = 1e-2; const absTol = 1e-5 + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.95), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.98), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.991), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.1), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.05), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.02), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1.01), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(1), relTol, absTol), true) - // smaller epsilon - const epsilon2 = 1e-4 - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), epsilon2, epsilon2 * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.999), epsilon2, epsilon2 * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9999), epsilon2, epsilon2 * 1e-3), true) + // smaller relTol and absTol + const relTol2 = 1e-4 + const absTol2 = 1e-7 + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.99), relTol2, absTol2), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.999), relTol2, absTol2), false) + assert.strictEqual(nearlyEqual(new BigNumber(1), new BigNumber(0.9999), relTol2, absTol2), true) }) it('should test whether a positive and negative number are nearly equal', function () { - const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(-1.2), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(-1.2), epsilon, epsilon * 1e-3), true) + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(1.2), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(-1.2), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(1.2), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(-1.2), new BigNumber(-1.2), relTol, absTol), true) }) it('should test whether two large numbers are nearly equal', function () { - const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.90e500'), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.95e500'), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.98e500'), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.99e500'), epsilon, epsilon * 1e-3), true) + const relTol = 1e-2 + const absTol = 1e-5 + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.90e500'), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.95e500'), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.98e500'), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber('1e500'), new BigNumber('0.99e500'), relTol, absTol), true) }) it('should test whether two small numbers are nearly equal (always true)', function () { - const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('0.99e-200'), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('10e-200'), epsilon, epsilon * 1e-3), true) + const relTol = 1e-2 + const absTol = 1e-5 + assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('0.99e-200'), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber('1e-200'), new BigNumber('10e-200'), relTol, absTol), true) }) it('should compare with zero', function () { - const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(0), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(-0), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e30), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e-3), epsilon, epsilon * 1e-3), false) + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(0), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(-0), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1.2), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e30), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(0), new BigNumber(1e-3), relTol, absTol), false) }) it('should compare with Infinity', function () { - const epsilon = 1e-3 - - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(Infinity), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(Infinity), epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(-Infinity), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(Infinity), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(-Infinity), epsilon, epsilon * 1e-3), true) + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(Infinity), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(1.2), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(Infinity), relTol, absTol), true) + assert.strictEqual(nearlyEqual(new BigNumber(Infinity), new BigNumber(-Infinity), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(Infinity), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(-Infinity), new BigNumber(-Infinity), relTol, absTol), true) }) it('should compare with NaN', function () { - const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(NaN), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(1.2), epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(NaN), epsilon, epsilon * 1e-3), false) + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(new BigNumber(1.2), new BigNumber(NaN), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(1.2), relTol, absTol), false) + assert.strictEqual(nearlyEqual(new BigNumber(NaN), new BigNumber(NaN), relTol, absTol), false) }) it('should use default values when absTol and relTol are undefined', function () { diff --git a/test/unit-tests/utils/number.test.js b/test/unit-tests/utils/number.test.js index 1233e21fa9..f1e39c3a76 100644 --- a/test/unit-tests/utils/number.test.js +++ b/test/unit-tests/utils/number.test.js @@ -505,22 +505,24 @@ describe('number', function () { describe('nearlyEqual', function () { it('should test whether two numbers are nearly equal', function () { - const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(1, 0.9, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 0.95, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 0.98, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 0.991, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(1, 1.1, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 1.05, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 1.02, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 1.01, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(1, 1, epsilon, epsilon * 1e-3), true) - - // smaller epsilon - const epsilon2 = 1e-4 - assert.strictEqual(nearlyEqual(1, 0.99, epsilon2, epsilon2 * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 0.999, epsilon2, epsilon2 * 1e-3), false) - assert.strictEqual(nearlyEqual(1, 0.9999, epsilon2, epsilon2 * 1e-3), true) + const relTol = 1e-2 + const absTol = 1e-5 + assert.strictEqual(nearlyEqual(1, 0.9, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1, 0.95, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1, 0.98, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1, 0.991, relTol, absTol), true) + assert.strictEqual(nearlyEqual(1, 1.1, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1, 1.05, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1, 1.02, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1, 1.01, relTol, absTol), true) + assert.strictEqual(nearlyEqual(1, 1, relTol, absTol), true) + + // smaller absTol and relTol + const relTol2 = 1e-4 + const absTol2 = 1e-7 + assert.strictEqual(nearlyEqual(1, 0.99, relTol2, absTol2), false) + assert.strictEqual(nearlyEqual(1, 0.999, relTol2, absTol2), false) + assert.strictEqual(nearlyEqual(1, 0.9999, relTol2, absTol2), true) // test one of these famous round-off errors assert.strictEqual((0.1 + 0.2) === 0.3, false) @@ -528,52 +530,58 @@ describe('number', function () { }) it('should test whether a positive and negative number are nearly equal', function () { - const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(1.2, 1.2, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(1.2, -1.2, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(-1.2, 1.2, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(-1.2, -1.2, epsilon, epsilon * 1e-3), true) + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(1.2, 1.2, relTol, absTol), true) + assert.strictEqual(nearlyEqual(1.2, -1.2, relTol, absTol), false) + assert.strictEqual(nearlyEqual(-1.2, 1.2, relTol, absTol), false) + assert.strictEqual(nearlyEqual(-1.2, -1.2, relTol, absTol), true) }) it('should test whether two large numbers are nearly equal', function () { - const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(1e200, 0.90e200, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1e200, 0.95e200, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1e200, 0.98e200, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(1e200, 0.99e200, epsilon, epsilon * 1e-3), true) + const relTol = 1e-2 + const absTol = 1e-5 + assert.strictEqual(nearlyEqual(1e200, 0.90e200, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1e200, 0.95e200, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1e200, 0.98e200, relTol, absTol), false) + assert.strictEqual(nearlyEqual(1e200, 0.99e200, relTol, absTol), true) }) it('should test whether two small numbers are nearly equal (always true)', function () { - const epsilon = 1e-2 - assert.strictEqual(nearlyEqual(1e-200, 0.99e-200, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(1e-200, 10e-200, epsilon, epsilon * 1e-3), true) // FIXME: why is this true? + const relTol = 1e-2 + const absTol = 1e-5 + assert.strictEqual(nearlyEqual(1e-200, 0.99e-200, relTol, absTol), true) + assert.strictEqual(nearlyEqual(1e-200, 10e-200, relTol, absTol), true) // FIXME: why is this true? }) it('should compare with zero', function () { - const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(0, 0, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(0, -0, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(0, 1.2, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(0, 1e30, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(0, 1e-30, epsilon, epsilon * 1e-3), true) // FIXME: why is this true? + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(0, 0, relTol, absTol), true) + assert.strictEqual(nearlyEqual(0, -0, relTol, absTol), true) + assert.strictEqual(nearlyEqual(0, 1.2, relTol, absTol), false) + assert.strictEqual(nearlyEqual(0, 1e30, relTol, absTol), false) + assert.strictEqual(nearlyEqual(0, 1e-30, relTol, absTol), true) // FIXME: why is this true? }) it('should compare with Infinity', function () { - const epsilon = 1e-3 - - assert.strictEqual(nearlyEqual(1.2, Infinity, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(Infinity, 1.2, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(Infinity, Infinity, epsilon, epsilon * 1e-3), true) - assert.strictEqual(nearlyEqual(Infinity, -Infinity, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(-Infinity, Infinity, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(-Infinity, -Infinity, epsilon, epsilon * 1e-3), true) + const relTol = 1e-3 + const absTol = 1e-6 + + assert.strictEqual(nearlyEqual(1.2, Infinity, relTol, absTol), false) + assert.strictEqual(nearlyEqual(Infinity, 1.2, relTol, absTol), false) + assert.strictEqual(nearlyEqual(Infinity, Infinity, relTol, absTol), true) + assert.strictEqual(nearlyEqual(Infinity, -Infinity, relTol, absTol), false) + assert.strictEqual(nearlyEqual(-Infinity, Infinity, relTol, absTol), false) + assert.strictEqual(nearlyEqual(-Infinity, -Infinity, relTol, absTol), true) }) it('should compare with NaN', function () { - const epsilon = 1e-3 - assert.strictEqual(nearlyEqual(1.2, NaN, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(NaN, 1.2, epsilon, epsilon * 1e-3), false) - assert.strictEqual(nearlyEqual(NaN, NaN, epsilon, epsilon * 1e-3), false) + const relTol = 1e-3 + const absTol = 1e-6 + assert.strictEqual(nearlyEqual(1.2, NaN, relTol, absTol), false) + assert.strictEqual(nearlyEqual(NaN, 1.2, relTol, absTol), false) + assert.strictEqual(nearlyEqual(NaN, NaN, relTol, absTol), false) }) it('should use default values when absTol and relTol are undefined', function () { diff --git a/types/index.d.ts b/types/index.d.ts index 3c46acb9e8..92a4e334f5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -544,7 +544,9 @@ export interface MathJsInstance extends MathJsFactory { /** * Set configuration options for math.js, and get current options. Will * emit a ‘config’ event, with arguments (curr, prev, changes). - * @param options Available options: {number} epsilon Minimum relative + * @param options Available options: {number} relTol Minimum relative + * difference between two compared values, used by all comparison + * functions. {number} absTol Minimum absolute * difference between two compared values, used by all comparison * functions. {string} matrix A string ‘Matrix’ (default) or ‘Array’. * {string} number A string ‘number’ (default), ‘BigNumber’, or @@ -2318,7 +2320,7 @@ export interface MathJsInstance extends MathJsFactory { /** * Compare two values. Returns 1 when x > y, -1 when x < y, and 0 when x * == y. x and y are considered equal when the relative difference - * between x and y is smaller than the configured epsilon. The function + * between x and y is smaller than the configured relTol and absTol. The function * cannot be used to compare values smaller than approximately 2.22e-16. * For matrices, the function is evaluated element wise. * @param x First value to compare @@ -2372,7 +2374,7 @@ export interface MathJsInstance extends MathJsFactory { * Test whether two values are equal. * * The function tests whether the relative difference between x and y is - * smaller than the configured epsilon. The function cannot be used to + * smaller than the configured relTol and absTol. The function cannot be used to * compare values smaller than approximately 2.22e-16. For matrices, the * function is evaluated element wise. In case of complex numbers, x.re * must equal y.re, and x.im must equal y.im. Values null and undefined @@ -2400,7 +2402,7 @@ export interface MathJsInstance extends MathJsFactory { /** * Test whether value x is larger than y. The function returns true when * x is larger than y and the relative difference between x and y is - * larger than the configured epsilon. The function cannot be used to + * larger than the configured relTol and absTol. The function cannot be used to * compare values smaller than approximately 2.22e-16. For matrices, the * function is evaluated element wise. * @param x First value to compare @@ -2412,7 +2414,7 @@ export interface MathJsInstance extends MathJsFactory { /** * Test whether value x is larger or equal to y. The function returns * true when x is larger than y or the relative difference between x and - * y is smaller than the configured epsilon. The function cannot be used + * y is smaller than the configured relTol and absTol. The function cannot be used * to compare values smaller than approximately 2.22e-16. For matrices, * the function is evaluated element wise. * @param x First value to compare @@ -2425,7 +2427,7 @@ export interface MathJsInstance extends MathJsFactory { /** * Test whether value x is smaller than y. The function returns true * when x is smaller than y and the relative difference between x and y - * is smaller than the configured epsilon. The function cannot be used + * is smaller than the configured relTol and absTol. The function cannot be used * to compare values smaller than approximately 2.22e-16. For matrices, * the function is evaluated element wise. * @param x First value to compare @@ -2437,7 +2439,7 @@ export interface MathJsInstance extends MathJsFactory { /** * Test whether value x is smaller or equal to y. The function returns * true when x is smaller than y or the relative difference between x - * and y is smaller than the configured epsilon. The function cannot be + * and y is smaller than the configured relTol and absTol. The function cannot be * used to compare values smaller than approximately 2.22e-16. For * matrices, the function is evaluated element wise. * @param x First value to compare @@ -2468,7 +2470,7 @@ export interface MathJsInstance extends MathJsFactory { /** * Test whether two values are unequal. The function tests whether the * relative difference between x and y is larger than the configured - * epsilon. The function cannot be used to compare values smaller than + * relTol and absTol. The function cannot be used to compare values smaller than * approximately 2.22e-16. For matrices, the function is evaluated * element wise. In case of complex numbers, x.re must unequal y.re, or * x.im must unequal y.im. Values null and undefined are compared @@ -4292,7 +4294,8 @@ export interface Help { } export interface ConfigOptions { - epsilon?: number + relTol?: number + absTol?: number matrix?: 'Matrix' | 'Array' number?: 'number' | 'BigNumber' | 'Fraction' precision?: number @@ -5763,7 +5766,7 @@ export interface MathJsChain { /** * Compare two values. Returns 1 when x > y, -1 when x < y, and 0 when x * == y. x and y are considered equal when the relative difference - * between x and y is smaller than the configured epsilon. The function + * between x and y is smaller than the configured relTol and absTol. The function * cannot be used to compare values smaller than approximately 2.22e-16. * For matrices, the function is evaluated element wise. * @param y Second value to compare @@ -5805,7 +5808,7 @@ export interface MathJsChain { * Test whether two values are equal. * * The function tests whether the relative difference between x and y is - * smaller than the configured epsilon. The function cannot be used to + * smaller than the configured relTol and absTol. The function cannot be used to * compare values smaller than approximately 2.22e-16. For matrices, the * function is evaluated element wise. In case of complex numbers, x.re * must equal y.re, and x.im must equal y.im. Values null and undefined @@ -5831,7 +5834,7 @@ export interface MathJsChain { /** * Test whether value x is larger than y. The function returns true when * x is larger than y and the relative difference between x and y is - * larger than the configured epsilon. The function cannot be used to + * larger than the configured relTol and absTol. The function cannot be used to * compare values smaller than approximately 2.22e-16. For matrices, the * function is evaluated element wise. * @param y Second value to compare @@ -5844,7 +5847,7 @@ export interface MathJsChain { /** * Test whether value x is larger or equal to y. The function returns * true when x is larger than y or the relative difference between x and - * y is smaller than the configured epsilon. The function cannot be used + * y is smaller than the configured relTol and absTol. The function cannot be used * to compare values smaller than approximately 2.22e-16. For matrices, * the function is evaluated element wise. * @param y Second value to vcompare @@ -5857,7 +5860,7 @@ export interface MathJsChain { /** * Test whether value x is smaller than y. The function returns true * when x is smaller than y and the relative difference between x and y - * is smaller than the configured epsilon. The function cannot be used + * is smaller than the configured relTol and absTol. The function cannot be used * to compare values smaller than approximately 2.22e-16. For matrices, * the function is evaluated element wise. * @param y Second value to vcompare @@ -5870,7 +5873,7 @@ export interface MathJsChain { /** * Test whether value x is smaller or equal to y. The function returns * true when x is smaller than y or the relative difference between x - * and y is smaller than the configured epsilon. The function cannot be + * and y is smaller than the configured relTol and absTol. The function cannot be * used to compare values smaller than approximately 2.22e-16. For * matrices, the function is evaluated element wise. * @param y Second value to compare @@ -5898,7 +5901,7 @@ export interface MathJsChain { /** * Test whether two values are unequal. The function tests whether the * relative difference between x and y is larger than the configured - * epsilon. The function cannot be used to compare values smaller than + * relTol and absTol. The function cannot be used to compare values smaller than * approximately 2.22e-16. For matrices, the function is evaluated * element wise. In case of complex numbers, x.re must unequal y.re, or * x.im must unequal y.im. Values null and undefined are compared From 8fc4c4dc4c559f2de4a4e13fc6d4df7509886c5d Mon Sep 17 00:00:00 2001 From: David Contreras Date: Thu, 11 Apr 2024 21:16:39 -0600 Subject: [PATCH 11/14] Added warning for config.epsilon --- docs/core/configuration.md | 2 +- src/core/function/config.js | 32 +++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/core/configuration.md b/docs/core/configuration.md index f1223d7734..7c0424f08a 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -9,7 +9,7 @@ import { create, all } from 'mathjs' // create a mathjs instance with configuration const config = { relTol: 1e-12, - absTol: 1e.15, + absTol: 1e-15, matrix: 'Matrix', number: 'number', precision: 64, diff --git a/src/core/function/config.js b/src/core/function/config.js index 9eebdfeead..f8d06b1524 100644 --- a/src/core/function/config.js +++ b/src/core/function/config.js @@ -52,23 +52,33 @@ export function configFactory (config, emit) { */ function _config (options) { if (options) { - const prev = mapObject(config, clone) + if (options.epsilon !== undefined) { + // backwards compatibility + console.warn('Warning: The configuration option "epsilon" is deprecated. Use "relTol" and "absTol" instead.') + const optionsFix = mapObject(options, clone) + optionsFix.relTol = options.epsilon + optionsFix.absTol = options.epsilon * 1e-3 + delete optionsFix.epsilon + return _config(optionsFix) + } else { + const prev = mapObject(config, clone) - // validate some of the options - validateOption(options, 'matrix', MATRIX_OPTIONS) - validateOption(options, 'number', NUMBER_OPTIONS) + // validate some of the options + validateOption(options, 'matrix', MATRIX_OPTIONS) + validateOption(options, 'number', NUMBER_OPTIONS) - // merge options - deepExtend(config, options) + // merge options + deepExtend(config, options) - const curr = mapObject(config, clone) + const curr = mapObject(config, clone) - const changes = mapObject(options, clone) + const changes = mapObject(options, clone) - // emit 'config' event - emit('config', curr, prev, changes) + // emit 'config' event + emit('config', curr, prev, changes) - return curr + return curr + } } else { return mapObject(config, clone) } From a4ac4a808f4d208e3c4f4fdc9f2a19a496135533 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 26 Apr 2024 20:58:47 -0600 Subject: [PATCH 12/14] Fix warning in zeta.test --- test/unit-tests/function/special/zeta.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-tests/function/special/zeta.test.js b/test/unit-tests/function/special/zeta.test.js index 41cabb9769..5b26d06310 100644 --- a/test/unit-tests/function/special/zeta.test.js +++ b/test/unit-tests/function/special/zeta.test.js @@ -35,7 +35,7 @@ describe('Riemann Zeta', function () { const digits = Math.abs(Math.log10(bigEpsilon)) const math2 = math.create() - math2.config({ epsilon: bigEpsilon }) + math2.config({ relTol: bigEpsilon, absTol: bigEpsilon * 1e-3 }) function bigApproxEqual (a, b) { assert.strictEqual( From b1646420c76bd756f06720fe8f1593f01518294b Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 26 Apr 2024 23:20:57 -0600 Subject: [PATCH 13/14] Added config test --- src/core/function/config.js | 33 ++++++++++++++--------------- test/unit-tests/core/config.test.js | 6 ++++++ types/index.d.ts | 1 + 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/core/function/config.js b/src/core/function/config.js index f8d06b1524..9efd7fb6d1 100644 --- a/src/core/function/config.js +++ b/src/core/function/config.js @@ -1,4 +1,4 @@ -import { clone, mapObject, deepExtend } from '../../utils/object.js' +import { clone, deepExtend } from '../../utils/object.js' import { DEFAULT_CONFIG } from '../config.js' export const MATRIX_OPTIONS = ['Matrix', 'Array'] // valid values for option matrix @@ -53,34 +53,33 @@ export function configFactory (config, emit) { function _config (options) { if (options) { if (options.epsilon !== undefined) { - // backwards compatibility + // this if is only for backwards compatibility, it can be removed in the future. console.warn('Warning: The configuration option "epsilon" is deprecated. Use "relTol" and "absTol" instead.') - const optionsFix = mapObject(options, clone) + const optionsFix = clone(options) optionsFix.relTol = options.epsilon optionsFix.absTol = options.epsilon * 1e-3 delete optionsFix.epsilon return _config(optionsFix) - } else { - const prev = mapObject(config, clone) + } + const prev = clone(config) - // validate some of the options - validateOption(options, 'matrix', MATRIX_OPTIONS) - validateOption(options, 'number', NUMBER_OPTIONS) + // validate some of the options + validateOption(options, 'matrix', MATRIX_OPTIONS) + validateOption(options, 'number', NUMBER_OPTIONS) - // merge options - deepExtend(config, options) + // merge options + deepExtend(config, options) - const curr = mapObject(config, clone) + const curr = clone(config) - const changes = mapObject(options, clone) + const changes = clone(options) - // emit 'config' event - emit('config', curr, prev, changes) + // emit 'config' event + emit('config', curr, prev, changes) - return curr - } + return curr } else { - return mapObject(config, clone) + return clone(config) } } diff --git a/test/unit-tests/core/config.test.js b/test/unit-tests/core/config.test.js index 519ce76526..45802b4cdd 100644 --- a/test/unit-tests/core/config.test.js +++ b/test/unit-tests/core/config.test.js @@ -17,4 +17,10 @@ describe('config', function () { }) // TODO: test function config + + it('should work with config epsilon during depercation', function () { + const math2 = math.create() + assert.doesNotThrow(function () { math2.config({ epsilon: 1e-5 }) }) + assert.strictEqual(math2.config().relTol, 1e-5) + }) }) diff --git a/types/index.d.ts b/types/index.d.ts index 67a22b6d42..fbd6bfe181 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -4300,6 +4300,7 @@ export interface Help { export interface ConfigOptions { relTol?: number absTol?: number + epsilon?: number matrix?: 'Matrix' | 'Array' number?: 'number' | 'BigNumber' | 'Fraction' precision?: number From 8ac25480a03eeadc1a608c58e8c7ae995885fd8a Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sun, 12 May 2024 10:34:15 -0600 Subject: [PATCH 14/14] Added sinon to test console.warn --- package-lock.json | 225 ++++++++++++++++++++++++++++ package.json | 1 + test/unit-tests/core/config.test.js | 14 ++ types/index.d.ts | 3 + 4 files changed, 243 insertions(+) diff --git a/package-lock.json b/package-lock.json index 300129238d..bc948f8247 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "fraction.js": "4.3.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", + "sinon": "^17.0.1", "tiny-emitter": "^2.1.0", "typed-function": "^4.1.1" }, @@ -2382,6 +2383,45 @@ "node": ">= 8" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -8692,6 +8732,11 @@ "node": ">=0.6.0" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==" + }, "node_modules/karma": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", @@ -9028,6 +9073,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9710,6 +9760,18 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -10193,6 +10255,11 @@ "node": ">=0.10.0" } }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -11036,6 +11103,50 @@ "dev": true, "optional": true }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -12037,6 +12148,14 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -14701,6 +14820,47 @@ } } }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -19438,6 +19598,11 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==" + }, "karma": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", @@ -19707,6 +19872,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -20227,6 +20397,18 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -20586,6 +20768,11 @@ "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true }, + "path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -21221,6 +21408,39 @@ "dev": true, "optional": true }, + "sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -21998,6 +22218,11 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", diff --git a/package.json b/package.json index 2cfe5e443b..05eb793d4b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "fraction.js": "4.3.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", + "sinon": "^17.0.1", "tiny-emitter": "^2.1.0", "typed-function": "^4.1.1" }, diff --git a/test/unit-tests/core/config.test.js b/test/unit-tests/core/config.test.js index 45802b4cdd..2e044c5a96 100644 --- a/test/unit-tests/core/config.test.js +++ b/test/unit-tests/core/config.test.js @@ -1,5 +1,6 @@ import assert from 'assert' import math from '../../../src/defaultInstance.js' +import sinon from 'sinon' describe('config', function () { it('should allow setting config after having overwritten import', function () { @@ -20,7 +21,20 @@ describe('config', function () { it('should work with config epsilon during depercation', function () { const math2 = math.create() + // Add a spy to temporary disable console.warn + const warnStub = sinon.stub(console, 'warn') + + // Set epsilon to throw a warning and set relTol and absTol assert.doesNotThrow(function () { math2.config({ epsilon: 1e-5 }) }) + + // Check if epsilon is set as relTol and absTol assert.strictEqual(math2.config().relTol, 1e-5) + assert.strictEqual(math2.config().absTol, 1e-8) + + // Check if console.warn was called + assert.strictEqual(warnStub.callCount, 1) + + // Restore console.warn + warnStub.restore() }) }) diff --git a/types/index.d.ts b/types/index.d.ts index fbd6bfe181..6f7eb8a765 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -4300,6 +4300,9 @@ export interface Help { export interface ConfigOptions { relTol?: number absTol?: number + /** + * @deprecated Use `relTol` and `absTol` instead + */ epsilon?: number matrix?: 'Matrix' | 'Array' number?: 'number' | 'BigNumber' | 'Fraction'