From db3e62e5c34ee010213315109d6c79618df1aac4 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Mon, 3 Apr 2023 13:48:24 +0200 Subject: [PATCH] feat: extend functions `fraction`, `bignumber`, and `number` with support for units, see #2918 (#2926) * feat: extend function `fraction` with support for units (see #2918) * fix: update the TypeScript definitions with the new `math.fraction(value: Unit)` support * feat: implement `math.bignumber(value: Unit)` * feat: update type definitions of function `math.bignumber` * fix: linting issue * feat: implement support for `math.number(unit)` (was formerly throwing an exception) --- docs/datatypes/units.md | 20 +++++++++++++++++++ src/type/bignumber/function/bignumber.js | 6 ++++++ src/type/fraction/function/fraction.js | 8 +++++++- src/type/number.js | 8 +++++--- .../type/bignumber/function/bignumber.test.js | 14 +++++++++++++ .../type/fraction/function/fraction.test.js | 14 +++++++++++++ test/unit-tests/type/number.test.js | 6 +++++- types/index.d.ts | 10 +++++----- 8 files changed, 76 insertions(+), 10 deletions(-) diff --git a/docs/datatypes/units.md b/docs/datatypes/units.md index af96b24a0..374099af1 100644 --- a/docs/datatypes/units.md +++ b/docs/datatypes/units.md @@ -203,6 +203,26 @@ math.createUnit('θ', '1 rad') math.evaluate('1θ + 3 deg').toNumber('deg') // 60.29577951308232 ``` +## Numeric type of the value of a unit + +The built-in units are always created with a value being a `number`. To turn the value into for example a `BigNumber` or `Fraction`, you can convert the value using the function `math.fraction` and `math.bignumber`: + +```js +math.unit(math.fraction(10), 'inch').toNumeric('cm') // Fraction 127/5 +math.fraction(math.unit(10, 'inch')).toNumeric('cm') // Fraction 127/5 + +math.bignumber(math.unit(10, 'inch')).toNumeric('cm') // BigNumber 25.4 +math.unit(math.bignumber(10), 'inch').toNumeric('cm') // BigNumber 25.4 +``` + +When using the expression parser, it is possible to configure numeric values to be parsed as `Fraction` or `BigNumber`: + +```js +math.config({ number: 'Fraction' }) +math.evaluate('10 inch').toNumeric('cm') // Fraction 127/5 +``` + + ## API A `Unit` object contains the following functions: diff --git a/src/type/bignumber/function/bignumber.js b/src/type/bignumber/function/bignumber.js index 8a8c475c0..e469563b1 100644 --- a/src/type/bignumber/function/bignumber.js +++ b/src/type/bignumber/function/bignumber.js @@ -65,6 +65,12 @@ export const createBignumber = /* #__PURE__ */ factory(name, dependencies, ({ ty return x }, + Unit: typed.referToSelf(self => (x) => { + const clone = x.clone() + clone.value = self(x.value) + return clone + }), + Fraction: function (x) { return new BigNumber(x.n).div(x.d).times(x.s) }, diff --git a/src/type/fraction/function/fraction.js b/src/type/fraction/function/fraction.js index 39fde6c34..02e748d3b 100644 --- a/src/type/fraction/function/fraction.js +++ b/src/type/fraction/function/fraction.js @@ -37,7 +37,7 @@ export const createFraction = /* #__PURE__ */ factory(name, dependencies, ({ typ * * bignumber, number, string, unit * - * @param {number | string | Fraction | BigNumber | Array | Matrix} [args] + * @param {number | string | Fraction | BigNumber | Unit | Array | Matrix} [args] * Arguments specifying the value, or numerator and denominator of * the fraction * @return {Fraction | Array | Matrix} Returns a fraction @@ -71,6 +71,12 @@ export const createFraction = /* #__PURE__ */ factory(name, dependencies, ({ typ return x // fractions are immutable }, + Unit: typed.referToSelf(self => (x) => { + const clone = x.clone() + clone.value = self(x.value) + return clone + }), + Object: function (x) { return new Fraction(x) }, diff --git a/src/type/number.js b/src/type/number.js index 407b9593b..b1f2883e7 100644 --- a/src/type/number.js +++ b/src/type/number.js @@ -116,9 +116,11 @@ export const createNumber = /* #__PURE__ */ factory(name, dependencies, ({ typed return x.valueOf() }, - Unit: function (x) { - throw new Error('Second argument with valueless unit expected') - }, + Unit: typed.referToSelf(self => (x) => { + const clone = x.clone() + clone.value = self(x.value) + return clone + }), null: function (x) { return 0 diff --git a/test/unit-tests/type/bignumber/function/bignumber.test.js b/test/unit-tests/type/bignumber/function/bignumber.test.js index 97a16475a..5e07d69b1 100644 --- a/test/unit-tests/type/bignumber/function/bignumber.test.js +++ b/test/unit-tests/type/bignumber/function/bignumber.test.js @@ -71,6 +71,20 @@ describe('bignumber', function () { assert.strictEqual(b.toString(), '0.6666666666666666666666666666666666666666666666666666666666666667') }) + it('should convert the number value of a Unit to BigNumber', function () { + const b = math.bignumber(math.unit(10, 'inch')).toNumeric('cm') + + assert.ok(b instanceof BigNumber) + assert.strictEqual(b.valueOf(), '25.4') + }) + + it('should convert the Fraction value of a Unit to BigNumber', function () { + const b = math.bignumber(math.unit(math.fraction(1, 2), 'cm')).toNumeric('cm') + + assert.ok(b instanceof BigNumber) + assert.strictEqual(b.valueOf(), '0.5') + }) + it('should apply precision setting to bignumbers', function () { const mymath = math.create({ precision: 32 diff --git a/test/unit-tests/type/fraction/function/fraction.test.js b/test/unit-tests/type/fraction/function/fraction.test.js index 8bad11163..00cd5d9c5 100644 --- a/test/unit-tests/type/fraction/function/fraction.test.js +++ b/test/unit-tests/type/fraction/function/fraction.test.js @@ -11,6 +11,10 @@ describe('fraction', function () { equalFraction(math.fraction(null), new Fraction(0)) }) + it('should create a fraction from an irrational number', function () { + equalFraction(math.fraction(math.pi), new Fraction(11985110, 3814979)) + }) + it('should fail to create a fraction in case of non-integer quotient', function () { assert.throws(() => math.fraction(4, 5.1), /Parameters must be integer/) assert.throws(() => math.fraction(62.8, 10), /Parameters must be integer/) @@ -28,6 +32,16 @@ describe('fraction', function () { equalFraction(f, new Fraction('0.6666666666666666666666666666666666666666666666666666666666666667')) }) + it('should convert the number value of a Unit to Fraction', function () { + equalFraction(math.fraction(math.unit(0.5, 'cm')).toNumeric('cm'), new Fraction(1, 2)) + equalFraction(math.fraction(math.unit(10, 'inch')).toNumeric('cm'), new Fraction(127, 5)) + }) + + it('should convert the BigNumber value of a Unit to Fraction', function () { + equalFraction(math.fraction(math.unit(math.bignumber(0.5), 'cm')).toNumeric('cm'), new Fraction(1, 2)) + equalFraction(math.fraction(math.unit(math.bignumber(10), 'inch')).toNumeric('cm'), new Fraction(127, 5)) + }) + it('should clone a fraction', function () { const a = math.fraction(1, 3) const b = math.fraction(a) diff --git a/test/unit-tests/type/number.test.js b/test/unit-tests/type/number.test.js index d44a41c9b..06a64a1b4 100644 --- a/test/unit-tests/type/number.test.js +++ b/test/unit-tests/type/number.test.js @@ -35,6 +35,11 @@ describe('number', function () { approx.equal(number(math.unit('52cm'), 'm'), 0.52) }) + it('should convert the value of a unit to a number', function () { + const value = number(math.unit(math.bignumber(52), 'cm')) + assert.strictEqual(value.toNumeric('cm'), 52) + }) + it('should parse the string if called with a valid string', function () { approx.equal(number('2.1e3'), 2100) approx.equal(number(' 2.1e-3 '), 0.0021) @@ -64,7 +69,6 @@ describe('number', function () { }) it('should throw an error with wrong type of arguments', function () { - assert.throws(function () { number(math.unit('5cm')) }, /Second argument with valueless unit expected/) // assert.throws(function () {number(math.unit('5cm'), 2)}, TypeError); // FIXME: this should also throw an error assert.throws(function () { number(math.unit('5cm'), new Date()) }, TypeError) assert.throws(function () { number('23', 2) }, TypeError) diff --git a/types/index.d.ts b/types/index.d.ts index 1bf4f5909..e7d7e211e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -585,7 +585,7 @@ declare namespace math { * @returns The created bignumber */ bignumber( - x?: number | string | Fraction | BigNumber | boolean | Fraction | null + x?: number | string | Fraction | BigNumber | Unit | boolean | null ): BigNumber bignumber(x: T): T @@ -664,12 +664,12 @@ declare namespace math { /** * Create a fraction convert a value to a fraction. - * @param args Arguments specifying the numerator and denominator of the + * @param value Arguments specifying the numerator and denominator of the * fraction * @returns Returns a fraction */ fraction( - value: number | string | BigNumber | Fraction | FractionDefinition + value: number | string | BigNumber | Unit | Fraction | FractionDefinition ): Fraction fraction(values: MathCollection): MathCollection /** @@ -4148,7 +4148,7 @@ declare namespace math { */ bignumber( this: MathJsChain< - number | string | Fraction | BigNumber | boolean | Fraction | null + number | string | Fraction | BigNumber | Unit | boolean | null > ): MathJsChain bignumber(this: MathJsChain): MathJsChain @@ -4214,7 +4214,7 @@ declare namespace math { */ fraction( this: MathJsChain< - number | string | BigNumber | Fraction | FractionDefinition + number | string | BigNumber | Unit | Fraction | FractionDefinition >, denominator?: number ): MathJsChain