diff --git a/src/expression/embeddedDocs/embeddedDocs.js b/src/expression/embeddedDocs/embeddedDocs.js index 92dd9036d4..5c8bfb2267 100644 --- a/src/expression/embeddedDocs/embeddedDocs.js +++ b/src/expression/embeddedDocs/embeddedDocs.js @@ -84,6 +84,7 @@ import { subsetDocs } from './function/matrix/subset' import { squeezeDocs } from './function/matrix/squeeze' import { sortDocs } from './function/matrix/sort' import { sizeDocs } from './function/matrix/size' +import { diffDocs } from './function/matrix/diff' import { reshapeDocs } from './function/matrix/reshape' import { resizeDocs } from './function/matrix/resize' import { rangeDocs } from './function/matrix/range' @@ -402,6 +403,7 @@ export const embeddedDocs = { ctranspose: ctransposeDocs, det: detDocs, diag: diagDocs, + diff: diffDocs, dot: dotDocs, getMatrixDataType: getMatrixDataTypeDocs, identity: identityDocs, diff --git a/src/expression/embeddedDocs/function/matrix/diff.js b/src/expression/embeddedDocs/function/matrix/diff.js new file mode 100644 index 0000000000..21ebb66fa6 --- /dev/null +++ b/src/expression/embeddedDocs/function/matrix/diff.js @@ -0,0 +1,29 @@ +export const diffDocs = { + name: 'diff', + category: 'Matrix', + syntax: [ + 'diff(arr)', + 'diff(arr, dim)' + ], + description: [ + 'Create a new matrix or array with the difference of the passed matrix or array.', + 'Dim parameter is optional and used to indicant the dimension of the array/matrix to apply the difference', + 'If no dimension parameter is passed it is assumed as dimension 0', + 'Dimension is zero-based in javascript and one-based in the parser', + 'Arrays must be \'rectangular\' meaning arrays like [1, 2]', + 'If something is passed as a matrix it will be returned as a matrix but other than that all matrices are converted to arrays' + ], + examples: [ + 'diff([1, 2, 4, 7, 0])', + 'diff([1, 2, 4, 7, 0], 0)', + 'diff(matrix([1, 2, 4, 7, 0]))', + 'diff([[1, 2], [3, 4]])', + 'diff([[1, 2], [3, 4]], 0)', + 'diff([[1, 2], [3, 4]], 1)', + 'diff([[1, 2], [3, 4]], bignumber(1))', + 'diff(matrix([[1, 2], [3, 4]]), 1)', + 'diff([[1, 2], matrix([3, 4])], 1)' + + ], + seealso: ['subtract', 'partitionSelect'] +} diff --git a/src/expression/transform/diff.transform.js b/src/expression/transform/diff.transform.js new file mode 100644 index 0000000000..cf111af0e8 --- /dev/null +++ b/src/expression/transform/diff.transform.js @@ -0,0 +1,37 @@ +import { isBigNumber, isCollection, isNumber } from '../../utils/is' +import { factory } from '../../utils/factory' +import { errorTransform } from './utils/errorTransform' +import { createDiff } from '../../function/matrix/diff' + +const name = 'diff' +const dependencies = ['typed', 'matrix', 'subtract', 'number', 'bignumber'] + +export const createDiffTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, subtract, number, bignumber }) => { + const diff = createDiff({ typed, matrix, subtract, number, bignumber }) + + /** + * Attach a transform function to math.diff + * Adds a property transform containing the transform function. + * + * This transform creates a range which includes the end value + */ + return typed(name, { + '...any': function (args) { + // change last argument dim from one-based to zero-based + if (args.length === 2 && isCollection(args[0])) { + const dim = args[1] + if (isNumber(dim)) { + args[1] = dim - 1 + } else if (isBigNumber(dim)) { + args[1] = dim.minus(1) + } + } + + try { + return diff.apply(null, args) + } catch (err) { + throw errorTransform(err) + } + } + }) +}, { isTransformFunction: true }) diff --git a/src/factoriesAny.js b/src/factoriesAny.js index 0b000f8279..de60e761aa 100644 --- a/src/factoriesAny.js +++ b/src/factoriesAny.js @@ -73,6 +73,7 @@ export { createGetMatrixDataType } from './function/matrix/getMatrixDataType' export { createIdentity } from './function/matrix/identity' export { createKron } from './function/matrix/kron' export { createMap } from './function/matrix/map' +export { createDiff } from './function/matrix/diff' export { createOnes } from './function/matrix/ones' export { createRange } from './function/matrix/range' export { createReshape } from './function/matrix/reshape' @@ -319,6 +320,7 @@ export { createRangeTransform } from './expression/transform/range.transform' export { createRowTransform } from './expression/transform/row.transform' export { createSubsetTransform } from './expression/transform/subset.transform' export { createConcatTransform } from './expression/transform/concat.transform' +export { createDiffTransform } from './expression/transform/diff.transform' export { createStdTransform } from './expression/transform/std.transform' export { createSumTransform } from './expression/transform/sum.transform' export { createVarianceTransform } from './expression/transform/variance.transform' diff --git a/src/function/matrix/diff.js b/src/function/matrix/diff.js new file mode 100644 index 0000000000..cb02de954d --- /dev/null +++ b/src/function/matrix/diff.js @@ -0,0 +1,170 @@ +import { factory } from '../../utils/factory' +import { isInteger } from '../../utils/number' +import { isMatrix } from '../../utils/is' + +const name = 'diff' +const dependencies = ['typed', 'matrix', 'subtract', 'number', 'bignumber'] + +export const createDiff = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, subtract, number, bignumber }) => { + /** + * Create a new matrix or array of the difference between elements of the given array + * The optional dim parameter lets you specify the dimension to evaluate the difference of + * If no dimension parameter is passed it is assumed as dimension 0 + * + * Dimension is zero-based in javascript and one-based in the parser and can be a number or bignumber + * Arrays must be 'rectangular' meaning arrays like [1, 2] + * If something is passed as a matrix it will be returned as a matrix but other than that all matrices are converted to arrays + * + * Syntax: + * + * math.diff(arr) + * math.diff(arr, dim) + * + * Examples: + * + * const arr = [1, 2, 4, 7, 0] + * math.diff(arr) // returns [1, 2, 3, -7] (no dimension passed so 0 is assumed) + * math.diff(math.matrix(arr)) // returns math.matrix([1, 2, 3, -7]) + * + * const arr = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [9, 8, 7, 6, 4]] + * math.diff(arr) // returns [[0, 0, 0, 0, 0], [8, 6, 4, 2, -1]] + * math.diff(arr, 0) // returns [[0, 0, 0, 0, 0], [8, 6, 4, 2, -1]] + * math.diff(arr, 1) // returns [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]] + * math.diff(arr, math.bignumber(1)) // returns [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]] + * + * math.diff(arr, 2) // throws RangeError as arr is 2 dimensional not 3 + * math.diff(arr, -1) // throws RangeError as negative dimensions are not allowed + * + * // These will all produce the same result + * math.diff([[1, 2], [3, 4]]) + * math.diff([math.matrix([1, 2]), math.matrix([3, 4])]) + * math.diff([[1, 2], math.matrix([3, 4])]) + * math.diff([math.matrix([1, 2]), [3, 4]]) + * // They do not produce the same result as math.diff(math.matrix([[1, 2], [3, 4]])) as this returns a matrix + * + * See Also: + * + * Subtract + * PartitionSelect + * + * @param {Array | Matrix} arr An array or matrix + * @param {number} dim Dimension + * @return {Array | Matrix} Difference between array elements in given dimension + */ + return typed(name, { + 'Array | Matrix': function (arr) { // No dimension specified => assume dimension 0 + if (isMatrix(arr)) { + return matrix(_diff(arr.toArray())) + } else { + return _diff(arr) + } + }, + 'Array | Matrix, number': function (arr, dim) { + if (!isInteger(dim)) throw RangeError('Dimension must be a whole number') + if (isMatrix(arr)) { + return matrix(_recursive(arr.toArray(), dim)) + } else { + return _recursive(arr, dim) + } + }, + 'Array | Matrix, BigNumber': function (arr, dim) { + const maxInt = bignumber(Number.MAX_SAFE_INTEGER) + const minInt = bignumber(Number.MIN_SAFE_INTEGER) + if (dim > maxInt || dim < minInt) throw RangeError('The array does not have more than 2^53 dimensions') + if (!dim.isInt()) throw RangeError('Dimension must be a whole number') + if (isMatrix(arr)) { + return matrix(_recursive(arr.toArray(), number(dim))) + } else { + return _recursive(arr, number(dim)) + } + } + }) + + /** + * Recursively find the correct dimension in the array/matrix + * Then Apply _diff to that dimension + * + * @param {Array} arr The array + * @param {number} dim Dimension + * @return {Array} resulting array + */ + function _recursive (arr, dim) { + if (isMatrix(arr)) { + arr = arr.toArray() // Makes sure arrays like [ matrix([0, 1]), matrix([1, 0]) ] are processed properly + } + if (!Array.isArray(arr)) { + throw RangeError('Array/Matrix does not have that many dimensions') + } + if (dim > 0) { + const result = [] + arr.forEach(element => { + result.push(_recursive(element, dim - 1)) + }) + return result + } else if (dim === 0) { + return _diff(arr) + } else { + throw RangeError('Cannot have negative dimension') + } + } + + /** + * Difference between elements in the array + * + * @param {Array} arr An array + * @return {Array} resulting array + */ + function _diff (arr) { + const result = [] + const size = arr.length + if (size < 2) { + return arr + } + for (let i = 1; i < size; i++) { + result.push(_ElementDiff(arr[i - 1], arr[i])) + } + return result + } + + /** + * Difference between 2 objects + * + * @param {Object} obj1 First object + * @param {Object} obj2 Second object + * @return {Array} resulting array + */ + function _ElementDiff (obj1, obj2) { + // Convert matrices to arrays + if (isMatrix(obj1)) obj1 = obj1.toArray() + if (isMatrix(obj2)) obj2 = obj2.toArray() + + const obj1IsArray = Array.isArray(obj1) + const obj2IsArray = Array.isArray(obj2) + if (obj1IsArray && obj2IsArray) { + return _ArrayDiff(obj1, obj2) + } + if (!obj1IsArray && !obj2IsArray) { + return subtract(obj2, obj1) // Difference is (second - first) NOT (first - second) + } + throw TypeError('Cannot calculate difference between 1 array and 1 non-array') + } + + /** + * Difference of elements in 2 arrays + * + * @param {Array} arr1 Array 1 + * @param {Array} arr2 Array 2 + * @return {Array} resulting array + */ + function _ArrayDiff (arr1, arr2) { + if (arr1.length !== arr2.length) { + throw RangeError('Not all sub-arrays have the same length') + } + const result = [] + const size = arr1.length + for (let i = 0; i < size; i++) { + result.push(_ElementDiff(arr1[i], arr2[i])) + } + return result + } +}) diff --git a/test/unit-tests/expression/transform/diff.transform.test.js b/test/unit-tests/expression/transform/diff.transform.test.js new file mode 100644 index 0000000000..3b3964ef12 --- /dev/null +++ b/test/unit-tests/expression/transform/diff.transform.test.js @@ -0,0 +1,36 @@ +import assert from 'assert' +import math from '../../../../src/bundleAny' + +const diff = math.expression.transform.diff + +describe('diff.transform', function () { + it('Should use one-based indexing for dimensions with arrays', function () { + // With Dim = 1 + assert.deepStrictEqual(diff([1, 2, 4, 7, 0], 1), [1, 2, 3, -7]) + assert.deepStrictEqual(diff([1, 2, 4, 7, 0], math.bignumber(1)), [1, 2, 3, -7]) + + // Without Dim = 1 + assert.deepStrictEqual(diff([1, 2, 4, 7, 0]), [1, 2, 3, -7]) + }) + + it('Should use one-based indexing for dimensions with matrices', function () { + // With Dim = 1 + assert.deepStrictEqual(diff(math.matrix([1, 2, 4, 7, 0]), 1), math.matrix([1, 2, 3, -7])) + assert.deepStrictEqual(diff(math.matrix([1, 2, 4, 7, 0]), math.bignumber(1)), math.matrix([1, 2, 3, -7])) + + // Without Dim = 1 + assert.deepStrictEqual(diff(math.matrix([1, 2, 4, 7, 0])), math.matrix([1, 2, 3, -7])) + }) + + it('should throw an error if the dimension is below the range for one based indices', function () { + assert.throws(function () { diff(math.matrix([1, 2, 4, 7, 0]), 0) }, Error) + }) + + it('should throw an error if the dimension is above the range for one based indices', function () { + assert.throws(function () { diff(math.matrix([1, 2, 4, 7, 0]), math.bignumber(0)) }, Error) + }) + + it('should work with the parser', function () { + assert.deepStrictEqual(math.evaluate('diff([1, 2, 4, 7, 0], 1)'), math.matrix([1, 2, 3, -7])) + }) +}) diff --git a/test/unit-tests/function/matrix/diff.test.js b/test/unit-tests/function/matrix/diff.test.js new file mode 100644 index 0000000000..265c8952c4 --- /dev/null +++ b/test/unit-tests/function/matrix/diff.test.js @@ -0,0 +1,192 @@ +import assert from 'assert' +import approx from '../../../../tools/approx' +import math from '../../../../src/bundleAny' + +// Parsing tests are inside diff.transform.test + +const matrix = math.matrix +const diff = math.diff + +const smallTestArray = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [9, 8, 7, 6, 4]] + +const smallTestArrayDimension0 = [[0, 0, 0, 0, 0], [8, 6, 4, 2, -1]] +const smallTestArrayDimension1 = [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]] + +// largeTestArrayDimension0-3 were generated from largeTestArray using numpy.diff() for consistency. Please dont change them because they are a pain to get linted correctly +const largeTestArray = [[[[1, 2, 3], [2, 3, 4], [3, 4, 5]], [[3, 2, 1], [2, 3, 4], [5, 4, 3]], [[5, 2, 1], [5, 2, 1], [5, 2, 1]]], [[[8, 12, 345], [23, 35, 23], [123, 234, 12]], [[1, 2, 3], [1, 3, 5], [5, 6, 7]], [[66, 55, 44], [32, 32, 1], [0, 1, 2]]], [[[1, 64, 3], [2, 34, 61], [128, 92, 64]], [[12, 2, 1], [6, 8, 9], [2, 7, 3]], [[52, 22, 21], [55, 52, 51], [111, 12, 11]]]] + +const largeTestArrayDimension0 = [[[[7, 10, 342], [21, 32, 19], [120, 230, 7]], [[-2, 0, 2], [-1, 0, 1], [0, 2, 4]], [[61, 53, 43], [27, 30, 0], [-5, -1, 1]]], [[[-7, 52, -342], [-21, -1, 38], [5, -142, 52]], [[11, 0, -2], [5, 5, 4], [-3, 1, -4]], [[-14, -33, -23], [23, 20, 50], [111, 11, 9]]]] +const largeTestArrayDimension1 = [[[[2, 0, -2], [0, 0, 0], [2, 0, -2]], [[2, 0, 0], [3, -1, -3], [0, -2, -2]]], [[[-7, -10, -342], [-22, -32, -18], [-118, -228, -5]], [[65, 53, 41], [31, 29, -4], [-5, -5, -5]]], [[[11, -62, -2], [4, -26, -52], [-126, -85, -61]], [[40, 20, 20], [49, 44, 42], [109, 5, 8]]]] +const largeTestArrayDimension2 = [[[[1, 1, 1], [1, 1, 1]], [[-1, 1, 3], [3, 1, -1]], [[0, 0, 0], [0, 0, 0]]], [[[15, 23, -322], [100, 199, -11]], [[0, 1, 2], [4, 3, 2]], [[-34, -23, -43], [-32, -31, 1]]], [[[1, -30, 58], [126, 58, 3]], [[-6, 6, 8], [-4, -1, -6]], [[3, 30, 30], [56, -40, -40]]]] +const largeTestArrayDimension3 = [[[[1, 1], [1, 1], [1, 1]], [[-1, -1], [1, 1], [-1, -1]], [[-3, -1], [-3, -1], [-3, -1]]], [[[4, 333], [12, -12], [111, -222]], [[1, 1], [2, 2], [1, 1]], [[-11, -11], [0, -31], [1, 1]]], [[[63, -61], [32, 27], [-36, -28]], [[-10, -1], [2, 1], [5, -4]], [[-30, -1], [-3, -1], [-99, -1]]]] + +describe('diff', function () { + it('should return original array/matrix for less than 2 elements, with and without specified dimension', function () { + // With Dim = 0 specified + assert.deepStrictEqual(diff([], 0), []) + assert.deepStrictEqual(diff(matrix([]), 0), matrix([])) + assert.deepStrictEqual(diff([2], 0), [2]) + assert.deepStrictEqual(diff(matrix([2]), 0), matrix([2])) + + // Without Dim = 0 specified + assert.deepStrictEqual(diff([]), []) + assert.deepStrictEqual(diff(matrix([])), matrix([])) + assert.deepStrictEqual(diff([2]), [2]) + assert.deepStrictEqual(diff(matrix([2])), matrix([2])) + }) + + it('should return difference between elements of a 1-dimensional array, with and without specified dimension', function () { + // With Dim = 0 + assert.deepStrictEqual(diff([1, 2, 4, 7, 0], 0), [1, 2, 3, -7]) + + // Without Dim = 0 + assert.deepStrictEqual(diff([1, 2, 4, 7, 0]), [1, 2, 3, -7]) + }) + + it('should return difference between elements of a 1-dimensional matrix, with and without specified dimension', function () { + // With Dim = 0 + assert.deepStrictEqual(diff(matrix([1, 2, 4, 7, 0]), 0), matrix([1, 2, 3, -7])) + + // Without Dim = 0 + assert.deepStrictEqual(diff(matrix([1, 2, 4, 7, 0])), matrix([1, 2, 3, -7])) + }) + + it('should return difference between elements of a 2-dimensional array, with and without specified dimension', function () { + // With Dim specified + assert.deepStrictEqual(diff(smallTestArray, 0), smallTestArrayDimension0) + assert.deepStrictEqual(diff(smallTestArray, 1), smallTestArrayDimension1) + assert.deepStrictEqual(diff(smallTestArray, math.bignumber(1)), smallTestArrayDimension1) + + // Without Dim specified + assert.deepStrictEqual(diff(smallTestArray), smallTestArrayDimension0) + }) + + it('should return difference between elements of a 2-dimensional matrix, with and without specified dimension', function () { + // With Dim specified + assert.deepStrictEqual(diff(matrix(smallTestArray), 0), matrix(smallTestArrayDimension0)) + assert.deepStrictEqual(diff(matrix(smallTestArray), 1), matrix(smallTestArrayDimension1)) + + // Without Dim specified + assert.deepStrictEqual(diff(matrix(smallTestArray)), matrix(smallTestArrayDimension0)) + }) + + it('should return difference between elements of a 4-dimensional array, with and without specified dimension', function () { + // With Dim specified + assert.deepStrictEqual(diff(largeTestArray, 0), largeTestArrayDimension0) + assert.deepStrictEqual(diff(largeTestArray, 1), largeTestArrayDimension1) + assert.deepStrictEqual(diff(largeTestArray, 2), largeTestArrayDimension2) + assert.deepStrictEqual(diff(largeTestArray, 3), largeTestArrayDimension3) + assert.deepStrictEqual(diff(largeTestArray, math.bignumber(1)), largeTestArrayDimension1) + assert.deepStrictEqual(diff(largeTestArray, math.bignumber(2)), largeTestArrayDimension2) + assert.deepStrictEqual(diff(largeTestArray, math.bignumber(3)), largeTestArrayDimension3) + + // Without Dim specified + assert.deepStrictEqual(diff(largeTestArray), largeTestArrayDimension0) + }) + + it('should return difference between elements of a 4-dimensional matrix, with and without specified dimension', function () { + // With Dim specified + assert.deepStrictEqual(diff(matrix(largeTestArray), 0), matrix(largeTestArrayDimension0)) + assert.deepStrictEqual(diff(matrix(largeTestArray), 1), matrix(largeTestArrayDimension1)) + assert.deepStrictEqual(diff(matrix(largeTestArray), 2), matrix(largeTestArrayDimension2)) + assert.deepStrictEqual(diff(matrix(largeTestArray), 3), matrix(largeTestArrayDimension3)) + + // Without Dim specified + assert.deepStrictEqual(diff(matrix(largeTestArray)), matrix(largeTestArrayDimension0)) + }) + + it('should treat an array of matrices as an array of arrays', function () { + // With Dim = 0 + assert.deepStrictEqual(diff([matrix([1, 2]), matrix([3, 4])], 0), [[2, 2]]) + assert.deepStrictEqual(diff([matrix([1, 2]), matrix([3, 4])], 1), [[1], [1]]) + assert.deepStrictEqual(diff([[1, 2], matrix([3, 4])], 0), [[2, 2]]) + assert.deepStrictEqual(diff([[1, 2], matrix([3, 4])], 1), [[1], [1]]) + assert.deepStrictEqual(diff([matrix([1, 2]), [3, 4]], 0), [[2, 2]]) + assert.deepStrictEqual(diff([matrix([1, 2]), [3, 4]], 1), [[1], [1]]) + + // Without Dim = 0 + assert.deepStrictEqual(diff([matrix([1, 2]), matrix([3, 4])]), [[2, 2]]) + assert.deepStrictEqual(diff([[1, 2], matrix([3, 4])]), [[2, 2]]) + assert.deepStrictEqual(diff([matrix([1, 2]), [3, 4]]), [[2, 2]]) + }) + + it('should be consistent with bignumber', function () { + // 4-dim array but done with bignumber + assert.deepStrictEqual(diff(math.bignumber(largeTestArray), 0), math.bignumber(largeTestArrayDimension0)) + assert.deepStrictEqual(diff(math.bignumber(largeTestArray), 1), math.bignumber(largeTestArrayDimension1)) + assert.deepStrictEqual(diff(math.bignumber(largeTestArray), 2), math.bignumber(largeTestArrayDimension2)) + assert.deepStrictEqual(diff(math.bignumber(largeTestArray), 3), math.bignumber(largeTestArrayDimension3)) + + // Without Dim specified + assert.deepStrictEqual(diff(math.bignumber(largeTestArray)), math.bignumber(largeTestArrayDimension0)) + }) + + it('should be consistent with fraction', function () { + // 4-dim array but done with bignumber + assert.deepStrictEqual(diff(math.fraction(largeTestArray), 0), math.fraction(largeTestArrayDimension0)) + assert.deepStrictEqual(diff(math.fraction(largeTestArray), 1), math.fraction(largeTestArrayDimension1)) + assert.deepStrictEqual(diff(math.fraction(largeTestArray), 2), math.fraction(largeTestArrayDimension2)) + assert.deepStrictEqual(diff(math.fraction(largeTestArray), 3), math.fraction(largeTestArrayDimension3)) + + // Without Dim specified + assert.deepStrictEqual(diff(math.fraction(largeTestArray)), math.fraction(largeTestArrayDimension0)) + }) + + it('should be consistent with units', function () { + // Derived from previous smallTestArray + const smallUnitsArray = [[math.unit('1 cm'), math.unit('2 cm'), math.unit('3 cm'), math.unit('4 cm'), math.unit('5 cm')], [math.unit('1 cm'), math.unit('2 cm'), math.unit('3 cm'), math.unit('4 cm'), math.unit('5 cm')], [math.unit('9 cm'), math.unit('8 cm'), math.unit('7 cm'), math.unit('6 cm'), math.unit('4 cm')]] + + const smallUnitsArrayDimension0 = [[math.unit('0 cm'), math.unit('0 cm'), math.unit('0 cm'), math.unit('0 cm'), math.unit('0 cm')], [math.unit('8 cm'), math.unit('6 cm'), math.unit('4 cm'), math.unit('2 cm'), math.unit('-1 cm')]] + const smallUnitsArrayDimension1 = [[math.unit('1 cm'), math.unit('1 cm'), math.unit('1 cm'), math.unit('1 cm')], [math.unit('1 cm'), math.unit('1 cm'), math.unit('1 cm'), math.unit('1 cm')], [math.unit('-1 cm'), math.unit('-1 cm'), math.unit('-1 cm'), math.unit('-2 cm')]] + + // With Dim specified + approx.deepEqual(diff(smallUnitsArray, 0), smallUnitsArrayDimension0) + approx.deepEqual(diff(smallUnitsArray, 1), smallUnitsArrayDimension1) + + // Without Dim specified + approx.deepEqual(diff(smallUnitsArray), smallUnitsArrayDimension0) + }) + + it('should throw if input is not an array or matrix', function () { + assert.throws(function () { diff(1, 0) }, TypeError) + }) + + it('should throw if dimension is too large, negative or not an integer', function () { + // Not enough dimensions in the array + assert.throws(function () { diff([1, 2, 3, 4], 1) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), 1) }, RangeError) + + // No negative dimensions + assert.throws(function () { diff([1, 2, 3, 4], -1) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), -1) }, RangeError) + + // No decimal dimensions + assert.throws(function () { diff(matrix([1, 2, 3, 4]), 0.5) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), -0.5) }, RangeError) + }) + + it('should throw if bignumber is not a valid index', function () { + // Not enough dimensions in the array + assert.throws(function () { diff([1, 2, 3, 4], math.bignumber(1)) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), math.bignumber(1)) }, RangeError) + + // No negative dimensions + assert.throws(function () { diff([1, 2, 3, 4], math.bignumber(-1)) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), math.bignumber(-1)) }, RangeError) + + // No decimal dimensions + assert.throws(function () { diff(matrix([1, 2, 3, 4]), math.bignumber(0.5)) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), math.bignumber(-0.5)) }, RangeError) + + // Unfortunately we will never know if these work properly + assert.throws(function () { diff(matrix([1, 2, 3, 4]), math.bignumber(Number.MAX_SAFE_INTEGER).plus(1)) }, RangeError) + assert.throws(function () { diff(matrix([1, 2, 3, 4]), math.bignumber(Number.MIN_SAFE_INTEGER).minus(1)) }, RangeError) + }) + + it('should throw if array is not \'rectangular\'', function () { + // Matrices are already 'rectangular' so this error doesnt apply to them + // The first one throws TypeError for trying to do 2 - [3,4] whereas the second one throws RangeError as [1,2].length != [3,4,3].length + assert.throws(function () { diff([1, 2, [3, 4]], 0) }, TypeError) + assert.throws(function () { diff([[1, 2], [3, 4, 3]], 0) }, RangeError) + }) +})