Skip to content

Commit

Permalink
feat: extend function round with support for units (#3095)
Browse files Browse the repository at this point in the history
* fix #2761: implement support for units in function `round` (WIP)

* fix #2761: extend function `round` with support for units

* docs: describe all signatures in the docs of function round

* chore: fix linting issue

* chore: remove less-useful signatures for round with units and matrices
  • Loading branch information
josdejong authored Nov 17, 2023
1 parent a1f3b7c commit b934672
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 24 deletions.
8 changes: 6 additions & 2 deletions src/expression/embeddedDocs/function/arithmetic/round.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ export const roundDocs = {
category: 'Arithmetic',
syntax: [
'round(x)',
'round(x, n)'
'round(x, n)',
'round(unit, valuelessUnit)',
'round(unit, n, valuelessUnit)'
],
description:
'round a value towards the nearest integer.If x is complex, both real and imaginary part are rounded towards the nearest integer. When n is specified, the value is rounded to n decimals.',
Expand All @@ -13,7 +15,9 @@ export const roundDocs = {
'round(-4.2)',
'round(-4.8)',
'round(pi, 3)',
'round(123.45678, 2)'
'round(123.45678, 2)',
'round(3.241cm, 2, cm)',
'round([3.2, 3.8, -4.7])'
],
seealso: ['ceil', 'floor', 'fix']
}
63 changes: 46 additions & 17 deletions src/function/arithmetic/round.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed,
*
* math.round(x)
* math.round(x, n)
* math.round(unit, valuelessUnit)
* math.round(unit, n, valuelessUnit)
*
* Examples:
*
Expand All @@ -47,14 +49,21 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed,
* const c = math.complex(3.2, -2.7)
* math.round(c) // returns Complex 3 - 3i
*
* const unit = math.unit('3.241 cm')
* const cm = math.unit('cm')
* const mm = math.unit('mm')
* math.round(unit, 1, cm) // returns Unit 3.2 cm
* math.round(unit, 1, mm) // returns Unit 32.4 mm
*
* math.round([3.2, 3.8, -4.7]) // returns Array [3, 4, -5]
*
* See also:
*
* ceil, fix, floor
*
* @param {number | BigNumber | Fraction | Complex | Array | Matrix} x Number to be rounded
* @param {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} x Value to be rounded
* @param {number | BigNumber | Array} [n=0] Number of decimals
* @param {Unit} [valuelessUnit] A valueless unit
* @return {number | BigNumber | Fraction | Complex | Array | Matrix} Rounded value
*/
return typed(name, {
Expand Down Expand Up @@ -109,43 +118,63 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed,
return x.round(n.toNumber())
},

// deep map collection, skip zeros since round(0) = 0
'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self, true)),
'Unit, number, Unit': typed.referToSelf(self => function (x, n, unit) {
const valueless = x.toNumeric(unit)
return unit.multiply(self(valueless, n))
}),

'Unit, BigNumber, Unit': typed.referToSelf(self => (x, n, unit) => self(x, n.toNumber(), unit)),

'Unit, Unit': typed.referToSelf(self => (x, unit) => self(x, 0, unit)),

'Array | Matrix, number, Unit': typed.referToSelf(self => (x, n, unit) => {
// deep map collection, skip zeros since round(0) = 0
return deepMap(x, (value) => self(value, n, unit), true)
}),

'Array | Matrix, BigNumber, Unit': typed.referToSelf(self => (x, n, unit) => self(x, n.toNumber(), unit)),

'Array | Matrix, Unit': typed.referToSelf(self => (x, unit) => self(x, 0, unit)),

'Array | Matrix': typed.referToSelf(self => x => {
// deep map collection, skip zeros since round(0) = 0
return deepMap(x, self, true)
}),

'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => {
return matAlgo11xS0s(x, y, self, false)
'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, n) => {
return matAlgo11xS0s(x, n, self, false)
}),

'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => {
return matAlgo14xDs(x, y, self, false)
'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, n) => {
return matAlgo14xDs(x, n, self, false)
}),

'Array, number | BigNumber': typed.referToSelf(self => (x, y) => {
'Array, number | BigNumber': typed.referToSelf(self => (x, n) => {
// use matrix implementation
return matAlgo14xDs(matrix(x), y, self, false).valueOf()
return matAlgo14xDs(matrix(x), n, self, false).valueOf()
}),

'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf(self => (x, y) => {
'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf(self => (x, n) => {
// check scalar is zero
if (equalScalar(x, 0)) {
// do not execute algorithm, result will be a zero matrix
return zeros(y.size(), y.storage())
return zeros(n.size(), n.storage())
}
return matAlgo12xSfs(y, x, self, true)
return matAlgo12xSfs(n, x, self, true)
}),

'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf(self => (x, y) => {
'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf(self => (x, n) => {
// check scalar is zero
if (equalScalar(x, 0)) {
// do not execute algorithm, result will be a zero matrix
return zeros(y.size(), y.storage())
return zeros(n.size(), n.storage())
}
return matAlgo14xDs(y, x, self, true)
return matAlgo14xDs(n, x, self, true)
}),

'number | Complex | BigNumber | Fraction, Array': typed.referToSelf(self => (x, y) => {
'number | Complex | BigNumber | Fraction, Array': typed.referToSelf(self => (x, n) => {
// use matrix implementation
return matAlgo14xDs(matrix(y), x, self, true).valueOf()
return matAlgo14xDs(matrix(n), x, self, true).valueOf()
})
})
})
2 changes: 1 addition & 1 deletion test/node-tests/doc.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const knownProblems = new Set([
'mod', 'invmod', 'floor', 'fix', 'expm1', 'exp', 'dotPow', 'dotMultiply',
'dotDivide', 'divide', 'ceil', 'cbrt', 'add', 'usolveAll', 'usolve', 'slu',
'rationalize', 'qr', 'lusolve', 'lup', 'lsolveAll', 'lsolve', 'derivative',
'symbolicEqual', 'map', 'schur', 'sylvester', 'freqz'
'symbolicEqual', 'map', 'schur', 'sylvester', 'freqz', 'round'
])

function maybeCheckExpectation (name, expected, expectedFrom, got, gotFrom) {
Expand Down
13 changes: 13 additions & 0 deletions test/typescript-tests/testTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,9 @@ Chaining examples
expectTypeOf(math.chain([1]).round()).toMatchTypeOf<
MathJsChain<MathCollection>
>()
expectTypeOf(
math.chain(math.unit('5.2cm')).round(math.unit('cm'))
).toMatchTypeOf<MathJsChain<Unit>>()

// cube
expectTypeOf(math.chain(1).cube()).toMatchTypeOf<MathJsChain<number>>()
Expand Down Expand Up @@ -1904,6 +1907,16 @@ Function round examples
math.complex(3.2, -2.7)
)

// unit input
assert.deepStrictEqual(
math.round(math.unit('5.21 cm'), math.unit('cm')),
math.unit('5 cm')
)
assert.deepStrictEqual(
math.round(math.unit('5.21 cm'), 1, math.unit('cm')),
math.unit('5.2 cm')
)

// array input
assert.deepStrictEqual(math.round([3.2, 3.8, -4.7]), [3, 4, -5])
assert.deepStrictEqual(math.round([3.21, 3.82, -4.71], 1), [3.2, 3.8, -4.7])
Expand Down
30 changes: 26 additions & 4 deletions test/unit-tests/function/arithmetic/round.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const fraction = math.fraction
const matrix = math.matrix
const sparse = math.sparse
const round = math.round
const unit = math.unit

describe('round', function () {
it('should round a number to te given number of decimals', function () {
Expand Down Expand Up @@ -118,10 +119,31 @@ describe('round', function () {
assert.deepStrictEqual(round(complex(2.157, math.pi), bignumber(2)), complex(2.16, 3.14))
})

it('should throw an error if used with a unit', function () {
assert.throws(function () { round(math.unit('5cm')) }, TypeError, 'Function round(unit) not supported')
assert.throws(function () { round(math.unit('5cm'), 2) }, TypeError, 'Function round(unit) not supported')
assert.throws(function () { round(math.unit('5cm'), bignumber(2)) }, TypeError, 'Function round(unit) not supported')
it('should round units', function () {
assert.deepStrictEqual(round(unit('3.12345 cm'), 3, unit('cm')), unit('3.123 cm'))
assert.deepStrictEqual(round(unit('3.12345 cm'), unit('cm')), unit('3 cm'))
assert.deepStrictEqual(round(unit('2 inch'), unit('cm')), unit('5 cm'))
assert.deepStrictEqual(round(unit('2 inch'), 1, unit('cm')), unit('5.1 cm'))

// bignumber values
assert.deepStrictEqual(round(unit('3.12345 cm'), bignumber(2), unit('cm')), unit('3.12 cm'))
assert.deepStrictEqual(round(unit(bignumber('2'), 'inch'), unit('cm')), unit(bignumber('5'), 'cm'))
assert.deepStrictEqual(round(unit(bignumber('2'), 'inch'), bignumber(1), unit('cm')), unit(bignumber('5.1'), 'cm'))

// first argument is a collection
assert.deepStrictEqual(round([unit('2 inch'), unit('3 inch')], unit('cm')), [unit('5 cm'), unit('8 cm')])
assert.deepStrictEqual(round(matrix([unit('2 inch'), unit('3 inch')]), unit('cm')), matrix([unit('5 cm'), unit('8 cm')]))
})

it('should throw an error if used with a unit without valueless unit', function () {
assert.throws(function () { round(unit('5cm')) }, TypeError, 'Function round(unit) not supported')
assert.throws(function () { round(unit('5cm'), 2) }, TypeError, 'Function round(unit) not supported')
assert.throws(function () { round(unit('5cm'), bignumber(2)) }, TypeError, 'Function round(unit) not supported')
})

it('should throw an error if used with a unit with a second unit that is not valueless', function () {
assert.throws(function () { round(unit('2 inch'), 1, unit('10 cm')) }, Error)
assert.throws(function () { round(unit('2 inch'), unit('10 cm')) }, Error)
})

it('should convert to a number when used with a string', function () {
Expand Down
23 changes: 23 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,10 @@ export interface MathJsInstance extends MathJsFactory {
n?: number | BigNumber
): NoLiteralType<T>
round<U extends MathCollection>(x: MathNumericType, n: U): U
round<U extends MathCollection>(x: U, unit: Unit): U
round(x: Unit, unit: Unit): Unit
round(x: Unit, n: number | BigNumber, unit: Unit): Unit
round<U extends MathCollection>(x: U, n: number | BigNumber, unit: Unit): U

// End of group of rounding functions

Expand Down Expand Up @@ -4740,6 +4744,25 @@ export interface MathJsChain<TValue> {
this: MathJsChain<T>,
n?: number | BigNumber | MathCollection
): MathJsChain<T>
round<U extends MathCollection>(
this: MathJsChain<MathNumericType | U>,
n: U
): MathJsChain<U>
round(this: MathJsChain<Unit>, unit: Unit): MathJsChain<Unit>
round<U extends MathCollection>(
this: MathJsChain<U>,
unit: Unit
): MathJsChain<U>
round(
this: MathJsChain<Unit>,
n: number | BigNumber,
unit: Unit
): MathJsChain<Unit>
round<U extends MathCollection>(
this: MathJsChain<U>,
n: number | BigNumber,
unit: Unit
): MathJsChain<U>

// End of rounding group

Expand Down

0 comments on commit b934672

Please sign in to comment.