From dc78998115d539f8f7d8329844c804aba836b782 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 9 Oct 2024 16:22:27 +0200 Subject: [PATCH 1/2] feat: upgrade to `fraction.js@5` --- package-lock.json | 12 +++++------ package.json | 2 +- src/core/function/typed.js | 2 +- src/function/algebra/simplify.js | 8 ++++--- src/function/algebra/simplifyConstant.js | 21 +++++++++---------- src/function/arithmetic/fix.js | 4 ++-- src/function/arithmetic/pow.js | 4 ++-- src/function/arithmetic/sign.js | 2 +- src/function/utils/isInteger.js | 2 +- src/function/utils/isNegative.js | 2 +- src/function/utils/isPositive.js | 2 +- src/type/bignumber/function/bignumber.js | 4 ++-- src/type/fraction/Fraction.js | 8 +++---- src/type/fraction/function/fraction.js | 5 +++++ src/type/unit/Unit.js | 2 +- src/utils/string.js | 8 +++---- .../function/algebra/simplify.test.js | 1 + test/unit-tests/json/replacer.test.js | 2 +- test/unit-tests/json/reviver.test.js | 14 ++++++++++++- .../unit-tests/type/fraction/Fraction.test.js | 4 ++-- test/unit-tests/type/numeric.test.js | 6 ++++-- test/unit-tests/type/unit/Unit.test.js | 2 +- types/index.d.ts | 7 ++++--- 23 files changed, 73 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 655d3ff0d8..0e696cb293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "complex.js": "^2.2.5", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^4.3.7", + "fraction.js": "^5.0.3", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", @@ -7220,15 +7220,15 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.0.3.tgz", + "integrity": "sha512-KcwJhP1eVCT0iuWo6r1XJ6sXMNLvGw8Et1TqPLKi/PzJx+xO7FC6fUyiE7HZZgL1rvYHpNdqAyKOdgocN1voeQ==", "license": "MIT", "engines": { - "node": "*" + "node": ">= 12" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, diff --git a/package.json b/package.json index 80e6cfcd1e..f05dc4c49e 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "complex.js": "^2.2.5", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^4.3.7", + "fraction.js": "^5.0.3", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", diff --git a/src/core/function/typed.js b/src/core/function/typed.js index 966a1a4f78..8c527d29ad 100644 --- a/src/core/function/typed.js +++ b/src/core/function/typed.js @@ -233,7 +233,7 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi throwNoFraction(x) } - return new Fraction(x.toString()) + return new Fraction(x) } }, { from: 'Fraction', diff --git a/src/function/algebra/simplify.js b/src/function/algebra/simplify.js index bb5fa94d1e..4c7dffe8ad 100644 --- a/src/function/algebra/simplify.js +++ b/src/function/algebra/simplify.js @@ -21,7 +21,8 @@ const dependencies = [ 'ObjectNode', 'OperatorNode', 'ParenthesisNode', - 'SymbolNode' + 'SymbolNode', + 'replacer' ] export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( @@ -40,7 +41,8 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( ObjectNode, OperatorNode, ParenthesisNode, - SymbolNode + SymbolNode, + replacer } ) => { const { hasProperty, isCommutative, isAssociative, mergeContext, flatten, unflattenr, unflattenl, createMakeNodeFunction, defaultContext, realContext, positiveContext } = @@ -816,7 +818,7 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( const uniqueSets = [] const unique = {} for (let i = 0; i < sets.length; i++) { - const s = JSON.stringify(sets[i]) + const s = JSON.stringify(sets[i], replacer) if (!unique[s]) { unique[s] = true uniqueSets.push(sets[i]) diff --git a/src/function/algebra/simplifyConstant.js b/src/function/algebra/simplifyConstant.js index 1820f3caaf..06232c9716 100644 --- a/src/function/algebra/simplifyConstant.js +++ b/src/function/algebra/simplifyConstant.js @@ -211,18 +211,17 @@ export const createSimplifyConstant = /* #__PURE__ */ factory(name, dependencies } function _fractionToNode (f) { - let n - const vn = f.s * f.n - if (vn < 0) { - n = new OperatorNode('-', 'unaryMinus', [new ConstantNode(-vn)]) - } else { - n = new ConstantNode(vn) - } + // note: we convert await from bigint values, because bigint values gives issues with divisions: 1n/2n=0n and not 0.5 + const fromBigInt = (value) => config.number === 'BigNumber' && bignumber ? bignumber(value) : Number(value) - if (f.d === 1) { - return n - } - return new OperatorNode('/', 'divide', [n, new ConstantNode(f.d)]) + const numeratorValue = f.s * f.n + const numeratorNode = (numeratorValue < 0n) + ? new OperatorNode('-', 'unaryMinus', [new ConstantNode(-fromBigInt(numeratorValue))]) + : new ConstantNode(fromBigInt(numeratorValue)) + + return (f.d === 1n) + ? numeratorNode + : new OperatorNode('/', 'divide', [numeratorNode, new ConstantNode(fromBigInt(f.d))]) } /* Handles constant indexing of ArrayNodes, matrices, and ObjectNodes */ diff --git a/src/function/arithmetic/fix.js b/src/function/arithmetic/fix.js index f0e8a39aaa..48c4848657 100644 --- a/src/function/arithmetic/fix.js +++ b/src/function/arithmetic/fix.js @@ -96,11 +96,11 @@ export const createFix = /* #__PURE__ */ factory(name, dependencies, ({ typed, C }, Fraction: function (x) { - return x.s < 0 ? x.ceil() : x.floor() + return x.s < 0n ? x.ceil() : x.floor() }, 'Fraction, number | BigNumber': function (x, n) { - return x.s < 0 ? ceil(x, n) : floor(x, n) + return x.s < 0n ? ceil(x, n) : floor(x, n) }, 'Array | Matrix': typed.referToSelf(self => (x) => { diff --git a/src/function/arithmetic/pow.js b/src/function/arithmetic/pow.js index d68deb545c..f7dfba37d5 100644 --- a/src/function/arithmetic/pow.js +++ b/src/function/arithmetic/pow.js @@ -120,8 +120,8 @@ export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, c const yFrac = fraction(y) const yNum = number(yFrac) if (y === yNum || Math.abs((y - yNum) / y) < 1e-14) { - if (yFrac.d % 2 === 1) { - return (yFrac.n % 2 === 0 ? 1 : -1) * Math.pow(-x, y) + if (yFrac.d % 2n === 1n) { + return ((yFrac.n % 2n === 0n) ? 1 : -1) * Math.pow(-x, y) } } } catch (ex) { diff --git a/src/function/arithmetic/sign.js b/src/function/arithmetic/sign.js index 56a728b04e..b8a09f57d8 100644 --- a/src/function/arithmetic/sign.js +++ b/src/function/arithmetic/sign.js @@ -52,7 +52,7 @@ export const createSign = /* #__PURE__ */ factory(name, dependencies, ({ typed, }, Fraction: function (x) { - return new Fraction(x.s, 1) + return new Fraction(x.s) }, // deep map collection, skip zeros since sign(0) = 0 diff --git a/src/function/utils/isInteger.js b/src/function/utils/isInteger.js index f6814d18ab..058eb8f536 100644 --- a/src/function/utils/isInteger.js +++ b/src/function/utils/isInteger.js @@ -47,7 +47,7 @@ export const createIsInteger = /* #__PURE__ */ factory(name, dependencies, ({ ty }, Fraction: function (x) { - return x.d === 1 && isFinite(x.n) + return x.d === 1n }, 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) diff --git a/src/function/utils/isNegative.js b/src/function/utils/isNegative.js index 95126a8715..6b647674f5 100644 --- a/src/function/utils/isNegative.js +++ b/src/function/utils/isNegative.js @@ -46,7 +46,7 @@ export const createIsNegative = /* #__PURE__ */ factory(name, dependencies, ({ t bigint: x => x < 0n, - Fraction: x => x.s < 0, // It's enough to decide on the sign + Fraction: x => x.s < 0n, // It's enough to decide on the sign Unit: typed.referToSelf(self => x => typed.find(self, x.valueType())(x.value)), diff --git a/src/function/utils/isPositive.js b/src/function/utils/isPositive.js index 9ce3d3c56a..3d188f63ad 100644 --- a/src/function/utils/isPositive.js +++ b/src/function/utils/isPositive.js @@ -49,7 +49,7 @@ export const createIsPositive = /* #__PURE__ */ factory(name, dependencies, ({ t bigint: x => x > 0n, - Fraction: x => x.s > 0 && x.n > 0, + Fraction: x => x.s > 0n && x.n > 0n, Unit: typed.referToSelf(self => x => typed.find(self, x.valueType())(x.value)), diff --git a/src/type/bignumber/function/bignumber.js b/src/type/bignumber/function/bignumber.js index 49391bebaa..a38a84ae28 100644 --- a/src/type/bignumber/function/bignumber.js +++ b/src/type/bignumber/function/bignumber.js @@ -76,10 +76,10 @@ export const createBignumber = /* #__PURE__ */ factory(name, dependencies, ({ ty }), Fraction: function (x) { - return new BigNumber(x.n).div(x.d).times(x.s) + return new BigNumber(String(x.n)).div(String(x.d)).times(String(x.s)) }, - null: function (x) { + null: function (_x) { return new BigNumber(0) }, diff --git a/src/type/fraction/Fraction.js b/src/type/fraction/Fraction.js index be84135d1c..7a0cb7e9d6 100644 --- a/src/type/fraction/Fraction.js +++ b/src/type/fraction/Fraction.js @@ -16,20 +16,20 @@ export const createFractionClass = /* #__PURE__ */ factory(name, dependencies, ( /** * Get a JSON representation of a Fraction containing type information * @returns {Object} Returns a JSON object structured as: - * `{"mathjs": "Fraction", "n": 3, "d": 8}` + * `{"mathjs": "Fraction", "n": "3", "d": "8"}` */ Fraction.prototype.toJSON = function () { return { mathjs: 'Fraction', - n: this.s * this.n, - d: this.d + n: String(this.s * this.n), + d: String(this.d) } } /** * Instantiate a Fraction from a JSON object * @param {Object} json a JSON object structured as: - * `{"mathjs": "Fraction", "n": 3, "d": 8}` + * `{"mathjs": "Fraction", "n": "3", "d": "8"}` * @return {BigNumber} */ Fraction.fromJSON = function (json) { diff --git a/src/type/fraction/function/fraction.js b/src/type/fraction/function/fraction.js index 1dfb88030a..51da13e239 100644 --- a/src/type/fraction/function/fraction.js +++ b/src/type/fraction/function/fraction.js @@ -59,6 +59,11 @@ export const createFraction = /* #__PURE__ */ factory(name, dependencies, ({ typ return new Fraction(numerator, denominator) }, + 'bigint, bigint': function (numerator, denominator) { + // TODO: remove the conversion from bigint to string as soon as Fraction.js supports that + return new Fraction(String(numerator), String(denominator)) + }, + null: function (x) { return new Fraction(0) }, diff --git a/src/type/unit/Unit.js b/src/type/unit/Unit.js index 4f85e74ae3..1fe99826df 100644 --- a/src/type/unit/Unit.js +++ b/src/type/unit/Unit.js @@ -2951,7 +2951,7 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ */ Unit.typeConverters = { BigNumber: function (x) { - if (x?.isFraction) return new BigNumber(x.n).div(x.d).times(x.s) + if (x?.isFraction) return new BigNumber(String(x.n)).div(String(x.d)).times(String(x.s)) return new BigNumber(x + '') // stringify to prevent constructor error }, diff --git a/src/utils/string.js b/src/utils/string.js index 01cafa309a..710c5df0ba 100644 --- a/src/utils/string.js +++ b/src/utils/string.js @@ -74,7 +74,7 @@ function _format (value, options) { if (looksLikeFraction(value)) { if (!options || options.fraction !== 'decimal') { // output as ratio, like '1/3' - return (value.s * value.n) + '/' + value.d + return `${value.s * value.n}/${value.d}` } else { // output as decimal, like '0.(3)' return value.toString() @@ -191,9 +191,9 @@ function formatArray (array, options) { function looksLikeFraction (value) { return (value && typeof value === 'object' && - typeof value.s === 'number' && - typeof value.n === 'number' && - typeof value.d === 'number') || false + typeof value.s === 'bigint' && + typeof value.n === 'bigint' && + typeof value.d === 'bigint') || false } /** diff --git a/test/unit-tests/function/algebra/simplify.test.js b/test/unit-tests/function/algebra/simplify.test.js index 6327f46011..00fefdb428 100644 --- a/test/unit-tests/function/algebra/simplify.test.js +++ b/test/unit-tests/function/algebra/simplify.test.js @@ -283,6 +283,7 @@ describe('simplify', function () { const bigmath = math.create({ number: 'bigint' }) assert.deepStrictEqual(bigmath.simplify('70000000000000000123 + 1').evaluate(), 70000000000000000124n) assert.deepStrictEqual(bigmath.simplify('70000000000000000123 + 5e3').evaluate(), 70000000000000010000) + assert.deepStrictEqual(bigmath.simplify('70000000000000000123 + bigint(5000)').evaluate(), 70000000000000005123n) }) it('should not change the value of numbers when converting to fractions (1)', function () { diff --git a/test/unit-tests/json/replacer.test.js b/test/unit-tests/json/replacer.test.js index c03c46be74..1284aaa080 100644 --- a/test/unit-tests/json/replacer.test.js +++ b/test/unit-tests/json/replacer.test.js @@ -42,7 +42,7 @@ describe('replacer', function () { it('should stringify a Fraction', function () { const b = new math.Fraction(0.375) - const json = '{"mathjs":"Fraction","n":3,"d":8}' + const json = '{"mathjs":"Fraction","n":"3","d":"8"}' assert.deepStrictEqual(JSON.stringify(b), json) assert.deepStrictEqual(JSON.stringify(b, replacer), json) diff --git a/test/unit-tests/json/reviver.test.js b/test/unit-tests/json/reviver.test.js index ae0fc3505c..9945198bbc 100644 --- a/test/unit-tests/json/reviver.test.js +++ b/test/unit-tests/json/reviver.test.js @@ -44,7 +44,7 @@ describe('reviver', function () { assert.deepStrictEqual(JSON.parse(json, reviver), 12345678901234567890n) }) - it('should parse a stringified Fraction', function () { + it('should parse a stringified Fraction (fraction.js v4 with numbers)', function () { const json = '{"mathjs":"Fraction","n":3,"d":8}' const b = new math.Fraction(0.375) @@ -56,6 +56,18 @@ describe('reviver', function () { assert.strictEqual(obj.d, b.d) }) + it('should parse a stringified Fraction', function () { + const json = '{"mathjs":"Fraction","n":{"mathjs":"bigint","value":"3"},"d":{"mathjs":"bigint","value":"8"}}' + const b = new math.Fraction(0.375) + + const obj = JSON.parse(json, reviver) + + assert(obj instanceof math.Fraction) + assert.strictEqual(obj.s, b.s) + assert.strictEqual(obj.n, b.n) + assert.strictEqual(obj.d, b.d) + }) + it('should parse a stringified Range', function () { const json = '{"mathjs":"Range","start":2,"end":10}' const r = new math.Range(2, 10) diff --git a/test/unit-tests/type/fraction/Fraction.test.js b/test/unit-tests/type/fraction/Fraction.test.js index afa21e4ff9..d06f4ee86c 100644 --- a/test/unit-tests/type/fraction/Fraction.test.js +++ b/test/unit-tests/type/fraction/Fraction.test.js @@ -18,8 +18,8 @@ describe('Fraction', function () { }) it('toJSON', function () { - assert.deepStrictEqual(new math.Fraction(0.375).toJSON(), { mathjs: 'Fraction', n: 3, d: 8 }) - assert.deepStrictEqual(new math.Fraction(-0.375).toJSON(), { mathjs: 'Fraction', n: -3, d: 8 }) + assert.deepStrictEqual(new math.Fraction(0.375).toJSON(), { mathjs: 'Fraction', n: '3', d: '8' }) + assert.deepStrictEqual(new math.Fraction(-0.375).toJSON(), { mathjs: 'Fraction', n: '-3', d: '8' }) }) it('fromJSON', function () { diff --git a/test/unit-tests/type/numeric.test.js b/test/unit-tests/type/numeric.test.js index 1c244d083d..5b3eb82424 100644 --- a/test/unit-tests/type/numeric.test.js +++ b/test/unit-tests/type/numeric.test.js @@ -38,8 +38,10 @@ describe('numeric', function () { }) it('should convert a BigNumber to a Fraction', function () { - assert.deepStrictEqual(numeric(math.bignumber(-0.125), 'Fraction'), math.fraction(-1, 8)) - assert.deepStrictEqual(numeric(math.bignumber('0.142857142857142857142857'), 'Fraction'), math.fraction(1, 7)) + assert.deepStrictEqual(numeric(math.bignumber('-0.125'), 'Fraction'), math.fraction(-1, 8)) + assert.deepStrictEqual(numeric(math.bignumber('0.142857142857142857142857'), 'Fraction'), + math.fraction(142857142857142857142857n, 1000000000000000000000000n) + ) }) it('should convert a BigNumber to a number', function () { diff --git a/test/unit-tests/type/unit/Unit.test.js b/test/unit-tests/type/unit/Unit.test.js index 871d518dbf..d936337920 100644 --- a/test/unit-tests/type/unit/Unit.test.js +++ b/test/unit-tests/type/unit/Unit.test.js @@ -672,7 +672,7 @@ describe('Unit', function () { }) const str = JSON.stringify(new Unit(math.fraction(0.375), 'cm')) - assert.deepStrictEqual(str, '{"mathjs":"Unit","value":{"mathjs":"Fraction","n":3,"d":8},"unit":"cm","fixPrefix":false}') + assert.deepStrictEqual(str, '{"mathjs":"Unit","value":{"mathjs":"Fraction","n":"3","d":"8"},"unit":"cm","fixPrefix":false}') const cmpx = JSON.stringify(new Unit(math.complex(2, 4), 'g')) assert.strictEqual(cmpx, '{"mathjs":"Unit","value":{"mathjs":"Complex","re":2,"im":4},"unit":"g","fixPrefix":false}') diff --git a/types/index.d.ts b/types/index.d.ts index 16f4d16192..8dd7006f67 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -719,6 +719,7 @@ export interface MathJsInstance extends MathJsFactory { * fraction * @returns Returns a fraction */ + fraction(numerator: bigint, denominator: bigint): Fraction fraction(numerator: number, denominator: number): Fraction /** @@ -4016,9 +4017,9 @@ export interface MatrixCtor { export interface BigNumber extends Decimal {} export interface Fraction { - s: number - n: number - d: number + s: bigint + n: bigint + d: bigint } export interface Complex { From a1a781a28ad909be7c79d8f9c52cabc0bc817c61 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Thu, 10 Oct 2024 14:44:26 +0200 Subject: [PATCH 2/2] fix: upgrade to `fraction.js@5.0.4` --- package-lock.json | 8 ++++---- package.json | 2 +- src/type/fraction/function/fraction.js | 3 +-- test/unit-tests/type/fraction/function/fraction.test.js | 6 +++++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e696cb293..7c8cc35c3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "complex.js": "^2.2.5", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^5.0.3", + "fraction.js": "^5.0.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", @@ -7220,9 +7220,9 @@ } }, "node_modules/fraction.js": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.0.3.tgz", - "integrity": "sha512-KcwJhP1eVCT0iuWo6r1XJ6sXMNLvGw8Et1TqPLKi/PzJx+xO7FC6fUyiE7HZZgL1rvYHpNdqAyKOdgocN1voeQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.0.4.tgz", + "integrity": "sha512-5b1NochKz6Hi46IXReWs8L+RteW/OQ6OMoLGLm/Zy7ZsPwH+to52ZrjcdRFAOTdlO/Uifyjn8HgiNnliz9KDeg==", "license": "MIT", "engines": { "node": ">= 12" diff --git a/package.json b/package.json index f05dc4c49e..94df08de9e 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "complex.js": "^2.2.5", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^5.0.3", + "fraction.js": "^5.0.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", diff --git a/src/type/fraction/function/fraction.js b/src/type/fraction/function/fraction.js index 51da13e239..e97e155a5d 100644 --- a/src/type/fraction/function/fraction.js +++ b/src/type/fraction/function/fraction.js @@ -60,8 +60,7 @@ export const createFraction = /* #__PURE__ */ factory(name, dependencies, ({ typ }, 'bigint, bigint': function (numerator, denominator) { - // TODO: remove the conversion from bigint to string as soon as Fraction.js supports that - return new Fraction(String(numerator), String(denominator)) + return new Fraction(numerator, denominator) }, null: function (x) { diff --git a/test/unit-tests/type/fraction/function/fraction.test.js b/test/unit-tests/type/fraction/function/fraction.test.js index edf852654b..765085226d 100644 --- a/test/unit-tests/type/fraction/function/fraction.test.js +++ b/test/unit-tests/type/fraction/function/fraction.test.js @@ -33,7 +33,11 @@ describe('fraction', function () { }) it('should create a fraction from a bigint', function () { - equalFraction(math.fraction(42n), new Fraction('42')) + equalFraction(math.fraction(42), new Fraction(42)) + equalFraction(math.fraction(42n), new Fraction(42)) + equalFraction(math.fraction(1n, 3n), new Fraction(1, 3)) + equalFraction(math.fraction(1n, 3), new Fraction(1, 3)) + equalFraction(math.fraction(1, 3n), new Fraction(1, 3)) }) it('should convert the number value of a Unit to Fraction', function () {