Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend function round with support for units #3095

Merged
merged 6 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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