/** * math.js * https://github.com/josdejong/mathjs * * Math.js is an extensive math library for JavaScript and Node.js, * It features real and complex numbers, units, matrices, a large set of * mathematical functions, and a flexible expression parser. * * @version 0.7.1-SNAPSHOT * @date 2013-04-26 * * @license * Copyright (C) 2013 Jos de Jong * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ (function() { // TODO: put "use strict"; here (but right now webstorms inspector starts // complaining on this issue: http://youtrack.jetbrains.com/issue/WEB-7485) /** * Define namespace */ var math = { type: {}, expr: { node: {} }, options: { precision: 5 // number of digits in formatted output } }; /** * CommonJS module exports */ if ((typeof module !== 'undefined') && (typeof module.exports !== 'undefined')) { module.exports = math; } if (typeof exports !== 'undefined') { exports = math; } /** * AMD module exports */ if (typeof(require) != 'undefined' && typeof(define) != 'undefined') { define(function () { return math; }); } /** * Browser exports */ if (typeof(window) != 'undefined') { window['math'] = math; } // utility methods for strings, objects, and arrays var util = (function () { var util = {}; /** * Convert a number to a formatted string representation. * @param {Number} value The value to be formatted * @param {Number} [digits] number of digits * @return {String} formattedValue The formatted value */ util.formatNumber = function formatNumber(value, digits) { if (value === Infinity) { return 'Infinity'; } else if (value === -Infinity) { return '-Infinity'; } else if (value === NaN) { return 'NaN'; } // TODO: what is a nice limit for non-scientific values? var abs = Math.abs(value); if ( (abs > 0.001 && abs < 100000) || abs == 0.0 ) { // round the value to a limited number of digits return util.toPrecision(value, digits); } else { // scientific notation var exp = Math.round(Math.log(abs) / Math.LN10); var v = value / (Math.pow(10.0, exp)); return util.toPrecision(v, digits) + 'e' + exp; } }; /** * Round a value to a maximum number of digits. Trailing zeros will be * removed. * @param {Number} value * @param {Number} [digits] * @returns {string} str */ util.toPrecision = function (value, digits) { if (digits == undefined) { digits = math.options.precision; } return value.toPrecision(digits).replace(_trailingZeros, function (a, b, c) { return a.substring(0, a.length - (b.length ? 0 : 1) - c.length); }); }; /** @private */ var _trailingZeros = /\.(\d*?)(0+)$/g; /** * Recursively format an n-dimensional matrix * Example output: "[[1, 2], [3, 4]]" * @param {Array} array * @returns {String} str */ util.formatArray = function formatArray (array) { if (array instanceof Array) { var str = '['; var len = array.length; for (var i = 0; i < len; i++) { if (i != 0) { str += ', '; } str += util.formatArray(array[i]); } str += ']'; return str; } else { return math.format(array); } }; /** * Recursively format an n-dimensional array, output looks like * "[1, 2, 3]" * @param {Array} array * @returns {string} str */ util.formatArray2d = function formatArray2d (array) { var str = '['; var s = util.size(array); if (s.length != 2) { throw new RangeError('Array must be two dimensional (size: ' + util.formatArray(s) + ')'); } var rows = s[0]; var cols = s[1]; for (var r = 0; r < rows; r++) { if (r != 0) { str += '; '; } var row = array[r]; for (var c = 0; c < cols; c++) { if (c != 0) { str += ', '; } var cell = row[c]; if (cell != undefined) { str += math.format(cell); } } } str += ']'; return str; }; /** * Convert function arguments to an array. Arguments can have the following * signature: * fn() * fn(n) * fn(m, n, p, ...) * fn([m, n, p, ...]) * @param {...Number | Array | Matrix} args * @returns {Array} array */ util.argsToArray = function argsToArray(args) { var array; if (args.length == 0) { // fn() array = []; } else if (args.length == 1) { // fn(n) // fn([m, n, p, ...]) array = args[0]; if (array instanceof Matrix) { array = array.toVector(); } if (array instanceof Range) { array = array.valueOf(); } if (!(array instanceof Array)) { array = [array]; } } else { // fn(m, n, p, ...) array = []; for (var i = 0; i < args.length; i++) { array[i] = args[i]; } } return array; }; /** * Check if a text ends with a certain string. * @param {String} text * @param {String} search */ util.endsWith = function(text, search) { var start = text.length - search.length; var end = text.length; return (text.substring(start, end) === search); }; /** * Extend object a with the properties of object b * @param {Object} a * @param {Object} b * @return {Object} a */ util.extend = function (a, b) { for (var prop in b) { if (b.hasOwnProperty(prop)) { a[prop] = b[prop]; } } return a; }; /** * Create a semi UUID * source: http://stackoverflow.com/a/105074/1262753 * @return {String} uuid */ util.randomUUID = function randomUUID() { var S4 = function () { return Math.floor( Math.random() * 0x10000 /* 65536 */ ).toString(16); }; return ( S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4() ); }; /** * Execute function fn element wise for each element in array. * Returns an array with the results * @param {Array | Matrix | Range} array * @param {function} fn * @return {Array | Matrix} res */ util.map = function map(array, fn) { if (array instanceof Array || array instanceof Matrix || array instanceof Range) { return array.map(function (x) { return fn(x); }); } else { throw new TypeError('Array expected'); } }; /** * Execute function fn element wise for each entry in two given arrays, or * for a (scalar) object and array pair. Returns an array with the results * @param {Array | Matrix | Range | Object} array1 * @param {Array | Matrix | Range | Object} array2 * @param {function} fn * @return {Array | Matrix} res */ util.map2 = function map2(array1, array2, fn) { var res, len, i; // handle Matrix if (array1 instanceof Matrix || array2 instanceof Matrix) { return new Matrix(util.map2(array1.valueOf(), array2.valueOf(), fn)); } // handle Range if (array1 instanceof Range || array2 instanceof Range) { // TODO: util.map2 does not utilize Range.map return util.map2(array1.valueOf(), array2.valueOf(), fn); } if (array1 instanceof Array) { if (array2 instanceof Array) { // fn(array, array) if (array1.length != array2.length) { throw new RangeError('Dimension mismatch ' + '(' + array1.length + ' != ' + array2.length + ')'); } res = []; len = array1.length; for (i = 0; i < len; i++) { res[i] = fn(array1[i], array2[i]); } } else { // fn(array, object) res = []; len = array1.length; for (i = 0; i < len; i++) { res[i] = fn(array1[i], array2); } } } else { if (array2 instanceof Array) { // fn(object, array) res = []; len = array2.length; for (i = 0; i < len; i++) { res[i] = fn(array1, array2[i]); } } else { // fn(object, object) res = fn(array1, array2); } } return res; }; /** * For each method for objects and arrays. * In case of an object, the method loops over all properties of the object. * In case of an array, the method loops over all indexes of the array. * @param {Object | Array} object The object * @param {function} callback Callback method, called for each item in * the object or array with three parameters: * callback(value, index, object) */ util.forEach = function forEach (object, callback) { if (object instanceof Array) { object.forEach(callback); } else { for (var key in object) { if (object.hasOwnProperty(key)) { callback(object[key], key, object); } } } }; /** * Creates a new object with the results of calling a provided function on * every property in the object. * @param {Object} object The object. * @param {function} callback Mapping function * @return {Object | Array} mappedObject */ util.mapObject = function mapObject (object, callback) { var m = {}; for (var key in object) { if (object.hasOwnProperty(key)) { m[key] = callback(object[key]); } } return m; }; /** * Deep test equality of all fields in two pairs of arrays or objects. * @param {Array | Object} a * @param {Array | Object} b * @returns {boolean} */ util.deepEqual = function (a, b) { var prop, i, len; if (a instanceof Array) { if (!(b instanceof Array)) { return false; } for (i = 0, len = a.length; i < len; i++) { if (!util.deepEqual(a[i], b[i])) { return false; } } return true; } else if (a instanceof Object) { if (b instanceof Array || !(b instanceof Object)) { return false; } for (prop in a) { if (a.hasOwnProperty(prop)) { if (!util.deepEqual(a[prop], b[prop])) { return false; } } } for (prop in b) { if (b.hasOwnProperty(prop)) { if (!util.deepEqual(a[prop], b[prop])) { return false; } } } return true; } else { return (a.valueOf() == b.valueOf()); } }; /** * Recursively calculate the size of a multi dimensional array. * @param {Array} x * @Return {Number[]} size * @throws RangeError */ function _size(x) { if (x instanceof Array) { var sizeX = x.length; if (sizeX) { var size0 = _size(x[0]); if (size0[0] == 0) { return [0].concat(size0); } else { return [sizeX].concat(size0); } } else { return [sizeX]; } } else { return []; } } /** * Calculate the size of a multi dimensional array. * All elements in the array are checked for matching dimensions using the * method validate * @param {Array} x * @Return {Number[]} size * @throws RangeError */ util.size = function size (x) { // calculate the size var s = _size(x); // verify the size util.validate(x, s); return s; }; /** * Recursively validate whether each element in a multi dimensional array * has a size corresponding to the provided size array. * @param {Array} array Array to be validated * @param {Number[]} size Array with the size of each dimension * @param {Number} dim Current dimension * @throws RangeError */ function _validate(array, size, dim) { var i; var len = array.length; if (len != size[dim]) { throw new RangeError('Dimension mismatch (' + len + ' != ' + size[dim] + ')'); } if (dim < size.length - 1) { // recursively validate each child array var dimNext = dim + 1; for (i = 0; i < len; i++) { var child = array[i]; if (!(child instanceof Array)) { throw new RangeError('Dimension mismatch ' + '(' + (size.length - 1) + ' < ' + size.length + ')'); } _validate(array[i], size, dimNext); } } else { // last dimension. none of the childs may be an array for (i = 0; i < len; i++) { if (array[i] instanceof Array) { throw new RangeError('Dimension mismatch ' + '(' + (size.length + 1) + ' > ' + size.length + ')'); } } } } /** * Recursively validate whether each array in a multi dimensional array * is empty (zero size) and has the correct number dimensions. * @param {Array} array Array to be validated * @param {Number[]} size Array with the size of each dimension * @param {Number} dim Current dimension * @throws RangeError */ function _validateEmpty(array, size, dim) { if (dim < size.length - 1) { var child = array[0]; if (array.length != 1 || !(child instanceof Array)) { throw new RangeError('Dimension mismatch ' + '(' + array.length + ' > 0)'); } _validateEmpty(child, size, dim + 1); } else { // last dimension. test if empty if (array.length) { throw new RangeError('Dimension mismatch ' + '(' + array.length + ' > 0)'); } } } /** * Validate whether each element in a multi dimensional array has * a size corresponding to the provided size array. * @param {Array} array Array to be validated * @param {Number[]} size Array with the size of each dimension * @throws RangeError */ util.validate = function validate(array, size) { var isScalar = (size.length == 0); if (isScalar) { // scalar if (array instanceof Array) { throw new RangeError('Dimension mismatch (' + array.length + ' != 0)'); } return; } var hasZeros = (size.indexOf(0) != -1); if (hasZeros) { // array where all dimensions are zero size.forEach(function (value) { if (value != 0) { throw new RangeError('Invalid size, all dimensions must be ' + 'either zero or non-zero (size: ' + util.formatArray(size) + ')'); } }); _validateEmpty(array, size, 0); } else { _validate(array, size, 0); } }; /** * Recursively resize a multi dimensional array * @param {Array} array Array to be resized * @param {Number[]} size Array with the size of each dimension * @param {Number} dim Current dimension * @param {*} [defaultValue] Value to be filled in in new entries, * 0 by default. * @private */ function _resize (array, size, dim, defaultValue) { if (!(array instanceof Array)) { throw new TypeError('Array expected'); } var len = array.length, newLen = size[dim]; if (len != newLen) { if(newLen > array.length) { // enlarge for (var i = array.length; i < newLen; i++) { array[i] = defaultValue ? math.clone(defaultValue) : 0; } } else { // shrink array.length = size[dim]; } len = array.length; } if (dim < size.length - 1) { // recursively validate each child array var dimNext = dim + 1; for (i = 0; i < len; i++) { child = array[i]; if (!(child instanceof Array)) { child = [child]; array[i] = child; } _resize(child, size, dimNext, defaultValue); } } else { // last dimension for (i = 0; i < len; i++) { var child = array[i]; while (child instanceof Array) { child = child[0]; } array[i] = child; } } } /** * Resize a multi dimensional array * @param {Array} array Array to be resized * @param {Number[]} size Array with the size of each dimension * @param {*} [defaultValue] Value to be filled in in new entries, * 0 by default */ util.resize = function resize(array, size, defaultValue) { // TODO: what to do with scalars, when size=[] ? // check the type of size if (!(size instanceof Array)) { throw new TypeError('Size must be an array (size is ' + math['typeof'](size) + ')'); } // check whether size contains positive integers size.forEach(function (value) { if (!isNumber(value) || !isInteger(value) || value < 0) { throw new TypeError('Invalid size, must contain positive integers ' + '(size: ' + util.formatArray(size) + ')'); } }); var hasZeros = (size.indexOf(0) != -1); if (hasZeros) { // array where all dimensions are zero size.forEach(function (value) { if (value != 0) { throw new RangeError('Invalid size, all dimensions must be ' + 'either zero or non-zero (size: ' + util.formatArray(size) + ')'); } }); } // recursively resize _resize(array, size, 0, defaultValue); }; // Internet Explorer 8 and older does not support Array.indexOf, // so we define it here in that case. // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/ if(!Array.prototype.indexOf) { Array.prototype.indexOf = function(obj){ for(var i = 0; i < this.length; i++){ if(this[i] == obj){ return i; } } return -1; }; } // Internet Explorer 8 and older does not support Array.forEach, // so we define it here in that case. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach if (!Array.prototype.forEach) { Array.prototype.forEach = function(fn, scope) { for(var i = 0, len = this.length; i < len; ++i) { fn.call(scope || this, this[i], i, this); } } } // Internet Explorer 8 and older does not support Array.map, // so we define it here in that case. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map // Production steps of ECMA-262, Edition 5, 15.4.4.19 // Reference: http://es5.github.com/#x15.4.4.19 if (!Array.prototype.map) { Array.prototype.map = function(callback, thisArg) { var T, A, k; if (this == null) { throw new TypeError(" this is null or not defined"); } // 1. Let O be the result of calling ToObject passing the |this| value as the argument. var O = Object(this); // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". // 3. Let len be ToUint32(lenValue). var len = O.length >>> 0; // 4. If IsCallable(callback) is false, throw a TypeError exception. // See: http://es5.github.com/#x9.11 if (typeof callback !== "function") { throw new TypeError(callback + " is not a function"); } // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. if (thisArg) { T = thisArg; } // 6. Let A be a new array created as if by the expression new Array(len) where Array is // the standard built-in constructor with that name and len is the value of len. A = new Array(len); // 7. Let k be 0 k = 0; // 8. Repeat, while k < len while(k < len) { var kValue, mappedValue; // a. Let Pk be ToString(k). // This is implicit for LHS operands of the in operator // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. // This step can be combined with c // c. If kPresent is true, then if (k in O) { // i. Let kValue be the result of calling the Get internal method of O with argument Pk. kValue = O[ k ]; // ii. Let mappedValue be the result of calling the Call internal method of callback // with T as the this value and argument list containing kValue, k, and O. mappedValue = callback.call(T, kValue, k, O); // iii. Call the DefineOwnProperty internal method of A with arguments // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true}, // and false. // In browsers that support Object.defineProperty, use the following: // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); // For best browser support, use the following: A[ k ] = mappedValue; } // d. Increase k by 1. k++; } // 9. return A return A; }; } // Internet Explorer 8 and older does not support Array.every, // so we define it here in that case. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every if (!Array.prototype.every) { Array.prototype.every = function(fun /*, thisp */) { "use strict"; if (this == null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (typeof fun != "function") { throw new TypeError(); } var thisp = arguments[1]; for (var i = 0; i < len; i++) { if (i in t && !fun.call(thisp, t[i], i, t)) { return false; } } return true; }; } // Internet Explorer 8 and older does not support Array.some, // so we define it here in that case. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/some if (!Array.prototype.some) { Array.prototype.some = function(fun /*, thisp */) { "use strict"; if (this == null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (typeof fun != "function") { throw new TypeError(); } var thisp = arguments[1]; for (var i = 0; i < len; i++) { if (i in t && fun.call(thisp, t[i], i, t)) { return true; } } return false; }; } return util; })(); /** * Utility functions for Booleans */ /** * Test whether value is a Boolean * @param {*} value * @return {Boolean} isBoolean */ function isBoolean(value) { return (value instanceof Boolean) || (typeof value == 'boolean'); } /** * @constructor Complex * * A complex value can be constructed in the following ways: * var a = new Complex(); * var b = new Complex(re, im); * var c = Complex.parse(str); * * Example usage: * var a = new Complex(3, -4); // 3 - 4i * a.re = 5; // a = 5 - 4i * var i = a.im; // -4; * var b = Complex.parse('2 + 6i'); // 2 + 6i * var c = new Complex(); // 0 + 0i * var d = math.add(a, b); // 5 + 2i * * @param {Number} re The real part of the complex value * @param {Number} [im] The imaginary part of the complex value */ function Complex(re, im) { if (!(this instanceof Complex)) { throw new SyntaxError( 'Complex constructor must be called with the new operator'); } if ((re != null && !isNumber(re)) || (im != null && !isNumber(im))) { throw new TypeError( 'Two numbers or a single string expected in Complex constructor'); } this.re = re || 0; this.im = im || 0; } math.type.Complex = Complex; // Complex parser methods in a closure (function () { var text, index, c; function skipWhitespace() { while (c == ' ' || c == '\t') { next(); } } function isDigitDot (c) { return ((c >= '0' && c <= '9') || c == '.'); } function isDigit (c) { return ((c >= '0' && c <= '9')); } function next() { index++; c = text.charAt(index); } function revert(oldIndex) { index = oldIndex; c = text.charAt(index); } function parseNumber () { var number = ''; var oldIndex = index; if (c == '+') { next(); } else if (c == '-') { number += c; next(); } if (!isDigitDot(c)) { // a + or - must be followed by a digit revert(oldIndex); return null; } // TODO only allow a single dot, and enforce at least one digit before or after the dot while (isDigitDot(c)) { number += c; next(); } // check for scientific notation like "2.3e-4" or "1.23e50" if (c == 'E' || c == 'e') { number += c; next(); if (c == '+' || c == '-') { number += c; next(); } // Scientific notation MUST be followed by an exponent if (!isDigit(c)) { // this is no legal number, exponent is missing. revert(oldIndex); return null; } while (isDigit(c)) { number += c; next(); } } return number; } function parseComplex () { // check for 'i', '-i', '+i' var cnext = text.charAt(index + 1); if (c == 'I' || c == 'i') { next(); return '1'; } else if ((c == '+' || c == '-') && (cnext == 'I' || cnext == 'i')) { var number = (c == '+') ? '1' : '-1'; next(); next(); return number; } return null; } /** * Parse a complex number from a string. For example Complex.parse("2 + 3i") * will return a Complex value where re = 2, im = 3. * Returns null if provided string does not contain a valid complex number. * @param {String} str * @returns {Complex | null} complex */ Complex.parse = function parse(str) { text = str; index = -1; c = ''; if (!isString(text)) { return null; } next(); skipWhitespace(); var first = parseNumber(); if (first) { if (c == 'I' || c == 'i') { // pure imaginary number next(); skipWhitespace(); if (c) { // garbage at the end. not good. return null; } return new Complex(0, Number(first)); } else { // complex and real part skipWhitespace(); var separator = c; if (separator != '+' && separator != '-') { // pure real number skipWhitespace(); if (c) { // garbage at the end. not good. return null; } return new Complex(Number(first), 0); } else { // complex and real part next(); skipWhitespace(); var second = parseNumber(); if (second) { if (c != 'I' && c != 'i') { // 'i' missing at the end of the complex number return null; } next(); } else { second = parseComplex(); if (!second) { // imaginary number missing after separator return null; } } if (separator == '-') { if (second[0] == '-') { second = '+' + second.substring(1); } else { second = '-' + second; } } next(); skipWhitespace(); if (c) { // garbage at the end. not good. return null; } return new Complex(Number(first), Number(second)); } } } else { // check for 'i', '-i', '+i' first = parseComplex(); if (first) { skipWhitespace(); if (c) { // garbage at the end. not good. return null; } return new Complex(0, Number(first)); } } return null; }; })(); /** * Test whether value is a Complex value * @param {*} value * @return {Boolean} isComplex */ function isComplex(value) { return (value instanceof Complex); } /** * Create a copy of the complex value * @return {Complex} clone */ Complex.prototype.clone = function () { return new Complex(this.re, this.im); }; /** * Get string representation of the Complex value * @return {String} str */ Complex.prototype.toString = function () { var str = ''; var strRe = util.formatNumber(this.re); var strIm = util.formatNumber(this.im); if (this.im == 0) { // real value str = strRe; } else if (this.re == 0) { // purely complex value if (this.im == 1) { str = 'i'; } else if (this.im == -1) { str = '-i'; } else { str = strIm + 'i'; } } else { // complex value if (this.im > 0) { if (this.im == 1) { str = strRe + ' + i'; } else { str = strRe + ' + ' + strIm + 'i'; } } else { if (this.im == -1) { str = strRe + ' - i'; } else { str = strRe + ' - ' + util.formatNumber(Math.abs(this.im)) + 'i'; } } } return str; }; /** * @constructor Matrix * * A Matrix is a wrapper around an Array. A matrix can hold a multi dimensional * array. A matrix can be constructed as: * var matrix = new Matrix(data) * * Matrix contains the functions to resize, get and set values, get the size, * clone the matrix and to convert the matrix to a vector, array, or scalar. * Furthermore, one can iterate over the matrix using map and forEach. * The internal Array of the Matrix can be accessed using the method valueOf. * * Example usage: * var matrix = new Matrix([[1, 2], [3, 4]); * matix.size(); // [2, 2] * matrix.resize([3, 2], 5); * matrix.valueOf(); // [[1, 2], [3, 4], [5, 5]] * matrix.get([2,1]) // 3 * * @param {Array | Matrix} [data] A multi dimensional array */ function Matrix(data) { if (!(this instanceof Matrix)) { throw new SyntaxError( 'Matrix constructor must be called with the new operator'); } if (data instanceof Matrix || data instanceof Range) { // clone data from a Matrix or Range this._data = data.toArray(); } else if (data instanceof Array) { // use array as is this._data = data; } else if (data != null) { // unsupported type throw new TypeError('Unsupported type of data (' + math['typeof'](data) + ')'); } else { // nothing provided this._data = []; } // verify the size of the array this._size = util.size(this._data); } math.type.Matrix = Matrix; /** * Get a value or a submatrix of the matrix. * @param {Array | Matrix} index One-based index */ Matrix.prototype.get = function (index) { var isScalar; if (index instanceof Matrix) { isScalar = index.isVector(); index = index.valueOf(); } else if (index instanceof Array) { isScalar = !index.some(function (i) { return (i.forEach); // an Array or Range }); } else { throw new TypeError('Unsupported type of index ' + math['typeof'](index)); } if (index.length != this._size.length) { throw new RangeError('Dimension mismatch ' + '(' + index.length + ' != ' + this._size.length + ')'); } if (isScalar) { // return a single value switch (index.length) { case 1: return _get(this._data, index[0]); case 2: return _get(_get(this._data, index[0]), index[1]); default: return _getScalar(this._data, index); } } else { // return a submatrix switch (index.length) { case 1: return new Matrix(_getSubmatrix1D(this._data, index)); case 2: return new Matrix(_getSubmatrix2D(this._data, index)); default: return new Matrix(_getSubmatrix(this._data, index, 0)); } // TODO: more efficient when creating an empty matrix and setting _data and _size manually } }; /** * Test whether index is an integer number with index >= 1 and index <= max * @param {*} index One-based index * @param {Number} [max] One-based maximum value * @private */ function _validateIndex(index, max) { if (!isNumber(index) || !isInteger(index)) { throw new TypeError('Index must be an integer (value: ' + index + ')'); } if (index < 1) { throw new RangeError('Index out of range (' + index + ' < 1)'); } if (max && index > max) { throw new RangeError('Index out of range (' + index + ' > ' + max + ')'); } } /** * Get a single value from an array. The method tests whether: * - index is a non-negative integer * - index does not exceed the dimensions of array * @param {Array} array * @param {Number} index One-based index * @return {*} value * @private */ function _get (array, index) { _validateIndex(index, array.length); return array[index - 1]; // one-based index } /** * Get a single value from the matrix. The value will be a copy of the original * value in the matrix. * Index is not checked for correct number of dimensions. * @param {Array} data * @param {Number[]} index One-based index * @return {*} scalar * @private */ function _getScalar (data, index) { index.forEach(function (i) { data = _get(data, i); }); return math.clone(data); } /** * Get a submatrix of a one dimensional matrix. * Index is not checked for correct number of dimensions. * @param {Array} data * @param {Array} index One-based index * @return {Array} submatrix * @private */ function _getSubmatrix1D (data, index) { var current = index[0]; if (current.map) { // array or Range return current.map(function (i) { return _get(data, i); }); } else { // scalar return [ _get(data, current) ]; } } /** * Get a submatrix of a 2 dimensional matrix. * Index is not checked for correct number of dimensions. * @param {Array} data * @param {Array} index One-based index * @return {Array} submatrix * @private */ function _getSubmatrix2D (data, index) { var rows = index[0]; var cols = index[1]; if (rows.map) { if (cols.map) { return rows.map(function (row) { var child = _get(data, row); return cols.map(function (col) { return _get(child, col); }); }); } else { return rows.map(function (row) { return [ _get(_get(data, row), cols) ]; }); } } else { if (cols.map) { var child = _get(data, rows); return [ cols.map(function (col) { return _get(child, col); }) ] } else { return [ [ _get(_get(data, rows), cols) ] ]; } } } /** * Get a submatrix of a multi dimensional matrix. * Index is not checked for correct number of dimensions. * @param {Array} data * @param {Array} index One-based index * @param {number} dim * @return {Array} submatrix * @private */ function _getSubmatrix (data, index, dim) { var last = (dim == index.length - 1); var current = index[dim]; var recurse = function (i) { var child = _get(data, i); return last ? child : _getSubmatrix(child, index, dim + 1); }; if (current.map) { // array or Range return current.map(recurse); } else { // scalar return [ recurse(current) ]; } } /** * Replace a value or a submatrix in the matrix. * Indexes are one-based. * @param {Array | Matrix} index One-based index * @param {*} submatrix * @return {Matrix} itself */ Matrix.prototype.set = function (index, submatrix) { var isScalar; if (index instanceof Matrix) { isScalar = index.isVector(); index = index.valueOf(); } else if (index instanceof Array) { isScalar = !index.some(function (i) { return (i.forEach); // an Array or Range }); } else { throw new TypeError('Unsupported type of index ' + math['typeof'](index)); } if (submatrix instanceof Matrix || submatrix instanceof Range) { submatrix = submatrix.valueOf(); } if (index.length < this._size.length) { throw new RangeError('Dimension mismatch ' + '(' + index.length + ' != ' + this._size.length + ')'); } if (isScalar) { // set a scalar // check whether submatrix is no matrix/array if (math.size(submatrix).length != 0) { throw new TypeError('Scalar value expected'); } switch (index.length) { case 1: _setScalar1D(this._data, this._size, index, submatrix); break; case 2: _setScalar2D(this._data, this._size, index, submatrix); break; default: _setScalar(this._data, this._size, index, submatrix); break; } } else { // set a submatrix var size = this._size.concat(); _setSubmatrix (this._data, size, index, 0, submatrix); if (!util.deepEqual(this._size, size)) { _init(this._data); this.resize(size); } } return this; }; /** * Replace a single value in an array. The method tests whether index is a * non-negative integer * @param {Array} array * @param {Number} index One-based index * @param {*} value * @private */ function _set (array, index, value) { _validateIndex(index); if (value instanceof Array) { throw new TypeError('Dimension mismatch, value expected instead of array'); } array[index - 1] = value; // one-based index } /** * Replace a single value in a multi dimensional matrix * @param {Array} data * @param {Number[]} size * @param {Number[]} index One-based index * @param {*} value * @private */ function _setScalar (data, size, index, value) { var resized = false; if (index.length > size.length) { // dimension added resized = true; } for (var i = 0; i < index.length; i++) { var index_i = index[i]; _validateIndex(index_i); if ((size[i] == null) || (index_i > size[i])) { size[i] = index_i; resized = true; } } if (resized) { util.resize(data, size, 0); } var len = size.length; index.forEach(function (v, i) { if (i < len - 1) { data = data[v - 1]; // one-based index } else { data[v - 1] = value; // one-based index } }); } /** * Replace a single value in a one dimensional matrix * @param {Array} data * @param {Number[]} size * @param {Number[]} index One-based index * @param {*} value * @private */ function _setScalar1D (data, size, index, value) { var row = index[0]; _validateIndex(row); if (row > size[0]) { util.resize(data, [row], 0); } data[row - 1] = value; // one-based index } /** * Replace a single value in a two dimensional matrix * @param {Array} data * @param {Number[]} size * @param {Number[]} index One-based index * @param {*} value * @private */ function _setScalar2D (data, size, index, value) { var row = index[0]; var col = index[1]; _validateIndex(row); _validateIndex(col); var resized = false; if (row > (size[0] || 0)) { size[0] = row; resized = true; } if (col > (size[1] || 0)) { size[1] = col; resized = true; } if (resized) { util.resize(data, size, 0); } data[row - 1][col - 1] = value; // one-based index } /** * Replace a submatrix of a multi dimensional matrix. * @param {Array} data * @param {Array} size * @param {Array} index One-based index * @param {number} dim * @param {Array} submatrix * @private */ function _setSubmatrix (data, size, index, dim, submatrix) { var last = (dim == index.length - 1); var current = index[dim]; var recurse = function (v, i) { if (last) { _set(data, v, submatrix[i]); if (data.length > (size[dim] || 0)) { size[dim] = data.length; } } else { var child = data[v - 1]; // one-based index if (!(child instanceof Array)) { data[v - 1] = child = [child]; // one-based index if (data.length > (size[dim] || 0)) { size[dim] = data.length; } } _setSubmatrix(child, size, index, dim + 1, submatrix[i]); } }; if (current.map) { // array or Range var len = (current.size && current.size() || current.length); if (len != submatrix.length) { throw new RangeError('Dimensions mismatch ' + '(' + len + ' != '+ submatrix.length + ')'); } current.map(recurse); } else { // scalar recurse(current, 0) } } /** * Recursively initialize all undefined values in the array with zeros * @param array * @private */ function _init(array) { for (var i = 0, len = array.length; i < len; i++) { var value = array[i]; if (value instanceof Array) { _init(value); } else if (value == undefined) { array[i] = 0; } } } /** * Resize the matrix * @param {Number[]} size * @param {*} [defaultValue] Default value, filled in on new entries. * If not provided, the vector will be filled * with zeros. */ Matrix.prototype.resize = function (size, defaultValue) { util.resize(this._data, size, defaultValue); this._size = math.clone(size); }; /** * Create a clone of the matrix * @return {Matrix} clone */ Matrix.prototype.clone = function () { var matrix = new Matrix(); matrix._data = math.clone(this._data); matrix._size = math.clone(this._size); return matrix; }; /** * Retrieve the size of the matrix. * The size of the matrix will be validated too * @returns {Number[]} size */ Matrix.prototype.size = function () { return this._size; }; /** * Create a new matrix with the results of the callback function executed on * each entry of the matrix. * @param {function} callback The callback method is invoked with three * parameters: the value of the element, the index * of the element, and the Matrix being traversed. * @return {Matrix} matrix */ Matrix.prototype.map = function (callback) { var me = this; var matrix = new Matrix(); var index = []; var recurse = function (value, dim) { if (value instanceof Array) { return value.map(function (child, i) { index[dim] = i + 1; // one-based index return recurse(child, dim + 1); }); } else { return callback(value, index, me); } }; matrix._data = recurse(this._data, 0); matrix._size = math.clone(this._size); return matrix; }; /** * Execute a callback method on each entry of the matrix. * @param {function} callback The callback method is invoked with three * parameters: the value of the element, the index * of the element, and the Matrix being traversed. */ Matrix.prototype.forEach = function (callback) { var me = this; var index = []; var recurse = function (value, dim) { if (value instanceof Array) { value.forEach(function (child, i) { index[dim] = i + 1; // one-based index recurse(child, dim + 1); }); } else { callback(value, index, me); } }; recurse(this._data, 0); }; /** * Create a scalar with a copy of the data of the Matrix * Will return null if the matrix does not consist of a scalar value * @return {* | null} scalar */ Matrix.prototype.toScalar = function () { var scalar = this._data; while (scalar instanceof Array && scalar.length == 1) { scalar = scalar[0]; } if (scalar instanceof Array) { return null; } else { return math.clone(scalar); } }; /** * Test whether the matrix is a scalar. * @return {boolean} isScalar */ Matrix.prototype.isScalar = function () { return this._size.every(function (s) { return (s <= 1); }); }; /** * Create a vector with a copy of the data of the Matrix * Returns null if the Matrix does not contain a vector * * A matrix is a vector when it has 0 or 1 dimensions, or has multiple * dimensions where maximum one of the dimensions has a size larger than 1. * return {Array | null} vector */ Matrix.prototype.toVector = function () { var count = 0; var dim = undefined; var index = []; this._size.forEach(function (length, i) { if (length > 1) { count++; dim = i; } index[i] = 0; }); if (count == 0) { // scalar or empty var scalar = this.toScalar(); if (scalar) { return [scalar]; } else { return []; } } else if (count == 1) { // valid vector var vector = []; var recurse = function (data) { if (data instanceof Array) { data.forEach(recurse); } else { vector.push(data); } }; recurse(this._data); return vector; } else { // count > 1, this is no vector return null; } }; /** * Test if the matrix contains a vector. * A matrix is a vector when it has 0 or 1 dimensions, or has multiple * dimensions where maximum one of the dimensions has a size larger than 1. * return {boolean} isVector */ Matrix.prototype.isVector = function () { var count = 0; this._size.forEach(function (length) { if (length > 1) { count++; } }); return (count <= 1); }; /** * Create an Array with a copy of the data of the Matrix * @returns {Array} array */ Matrix.prototype.toArray = function () { return math.clone(this._data); }; /** * Get the primitive value of the Matrix: a multidimensional array * @returns {Array} array */ Matrix.prototype.valueOf = function () { return this._data; }; /** * Get a string representation of the matrix * @returns {String} str */ Matrix.prototype.toString = function () { return math.format(this._data); }; /** * Utility functions for Numbers */ /** * Test whether value is a Number * @param {*} value * @return {Boolean} isNumber */ function isNumber(value) { return (value instanceof Number) || (typeof value == 'number'); } /** * Check if a number is integer * @param {Number} value * @return {Boolean} isInteger */ function isInteger(value) { return (value == Math.round(value)); } /** * @constructor Range * Create a range. A range works similar to an Array, with functions like * forEach and map. However, a Range object is very cheap to create compared to * a large Array with indexes, as it stores only a start, step and end value of * the range. * * A range can be constructed as: * var a = new Range(start, step, end); * * To get the result of the range: * range.forEach(function (x) { * console.log(x); * }); * range.map(function (x) { * return math.sin(x); * }); * range.toArray(); * * Example usage: * var c = new Range(2, 1, 5); // 2:1:5 * c.toArray(); // [2, 3, 4, 5] * var d = new Range(2, -1, -2); // 2:-1:-2 * d.toArray(); // [2, 1, 0, -1, -2] * * @param {Number} start * @param {Number} step * @param {Number} end */ function Range(start, step, end) { if (!(this instanceof Range)) { throw new SyntaxError( 'Range constructor must be called with the new operator'); } if (start != null && !isNumber(start)) { throw new TypeError('Parameter start must be a number'); } if (end != null && !isNumber(end)) { throw new TypeError('Parameter end must be a number'); } if (step != null && !isNumber(step)) { throw new TypeError('Parameter step must be a number'); } this.start = (start != null) ? start : 0; this.end = (end != null) ? end : 0; this.step = (step != null) ? step : 1; } math.type.Range = Range; /** * Parse a string into a range, * The string contains the start, optional step, and end, separated by a colon. * If the string does not contain a valid range, null is returned. * For example str='0:2:10'. * @param {String} str * @return {Range | null} range */ Range.parse = function (str) { if (!isString(str)) { return null; } var args = str.split(':'); var nums = args.map(function (arg) { return Number(arg); }); var invalid = nums.some(function (num) { return isNaN(num); }); if(invalid) { return null; } switch (nums.length) { case 2: return new Range(nums[0], 1, nums[1]); case 3: return new Range(nums[0], nums[1], nums[2]); default: return null; } }; /** * Create a clone of the range * @return {Range} clone */ Range.prototype.clone = function () { return new Range(this.start, this.step, this.end); }; /** * Retrieve the size of the range. * @returns {Number[]} size */ Range.prototype.size = function () { var len = 0, start = Number(this.start), step = Number(this.step), end = Number(this.end), diff = end - start; if (math.sign(step) == math.sign(diff)) { len = Math.floor((diff) / step) + 1; } else if (diff == 0) { len = 1; } if (isNaN(len)) { len = 0; } return [len]; }; /** * Execute a callback function for each value in the range. * @param {function} callback The callback method is invoked with three * parameters: the value of the element, the index * of the element, and the Matrix being traversed. */ Range.prototype.forEach = function (callback) { var x = Number(this.start); var step = Number(this.step); var end = Number(this.end); var i = 0; if (step > 0) { while (x <= end) { callback(x, i, this); x += step; i++; } } else if (step < 0) { while (x >= end) { callback(x, i, this); x += step; i++; } } }; /** * Execute a callback function for each value in the Range, and return the * results as an array * @param {function} callback The callback method is invoked with three * parameters: the value of the element, the index * of the element, and the Matrix being traversed. * @returns {Array} array */ Range.prototype.map = function (callback) { var array = []; this.forEach(function (value, index, obj) { array[index] = callback(value, index, obj); }); return array; }; /** * Create a Matrix with a copy of the Ranges data * @return {Matrix} matrix */ Range.prototype.toMatrix = function () { return new Matrix(this.toArray()); }; /** * Create an Array with a copy of the Ranges data * @returns {Array} array */ Range.prototype.toArray = function () { var array = []; this.forEach(function (value, index) { array[index] = value; }); return array; }; /** * Create an array with a copy of the Ranges data. * This method is equal to Range.toArray, and is available for compatibility * with Matrix. * @return {Array} vector */ Range.prototype.toVector = Range.prototype.toArray; /** * Test if the range contains a vector. For a range, this is always the case * return {boolean} isVector */ Range.prototype.isVector = function () { return true; }; /** * Create a scalar with a copy of the data of the Range * Will return null if the range does not consist of a scalar value * @return {* | null} scalar */ Range.prototype.toScalar = function () { var array = this.toArray(); if (array.length == 1) { return array[0]; } else { return null; } }; /** * Test whether the matrix is a scalar. * @return {boolean} isScalar */ Range.prototype.isScalar = function () { return (this.size()[0] == 1); }; /** * Get the primitive value of the Range, a one dimensional array * @returns {Array} array */ Range.prototype.valueOf = function () { // TODO: implement a caching mechanism for range.valueOf() return this.toArray(); }; /** * Get the string representation of the range, for example '2:5' or '0:0.2:10' * @returns {String} str */ Range.prototype.toString = function () { var str = math.format(Number(this.start)); if (this.step != 1) { str += ':' + math.format(Number(this.step)); } str += ':' + math.format(Number(this.end)); return str; }; /** * @constructor math.type.Selector * Wrap any value in a Selector, allowing to perform chained operations on * the value. * * All methods available in the math.js library can be called upon the selector, * and then will be evaluated with the value itself as first argument. * The selector can be closed by executing selector.done(), which will return * the final value. * * The Selector has a number of special functions: * - done() Finalize the chained operation and return the selectors value. * - valueOf() The same as done() * - toString() Executes math.format() onto the selectors value, returning * a string representation of the value. * - get(...) Get a subselection of the selectors value. Only applicable when * the value has a method get, for example when value is a Matrix * or Array. * - set(...) Replace a subselection of the selectors value. Only applicable * when the value has a method get, for example when value is a * Matrix or Array. * * @param {*} [value] */ math.type.Selector = function Selector (value) { if (!(this instanceof math.type.Selector)) { throw new SyntaxError( 'Selector constructor must be called with the new operator'); } if (value instanceof math.type.Selector) { this.value = value.value; } else { this.value = value || undefined; } }; math.type.Selector.prototype = { /** * Close the selector. Returns the final value. * Does the same as method valueOf() * @returns {*} value */ done: function () { return this.value; }, /** * Get a submatrix or subselection from current value. * Only applicable when the current value has a method get. */ get: function () { var value = this.value; if (!value) { throw Error('Selector value is undefined'); } if (value.get) { return new math.type.Selector(value.get.apply(value, arguments)); } if (value instanceof Array) { // convert to matrix, evaluate, and then back to Array value = new Matrix(value); return new math.type.Selector( value.get.apply(value, arguments).valueOf() ); } throw Error('Selector value has no method get'); }, /** * Set a submatrix or subselection on current value. * Only applicable when the current value has a method set. */ set: function () { var value = this.value; if (!value) { throw Error('Selector value is undefined'); } if (value.set) { return new math.type.Selector(value.set.apply(value, arguments)); } if (value instanceof Array) { // convert to matrix, evaluate, and then back to Array value = new Matrix(value); return new math.type.Selector( value.set.apply(value, arguments).valueOf() ); } throw Error('Selector value has no method set'); }, /** * Close the selector. Returns the final value. * Does the same as method done() * @returns {*} value */ valueOf: function () { return this.value; }, /** * Get the string representation of the value in the selector * @returns {String} */ toString: function () { return math.format(this.value); } }; /** * Create a proxy method for the selector * @param {String} name * @param {*} value The value or function to be proxied */ function createSelectorProxy(name, value) { var Selector = math.type.Selector; var slice = Array.prototype.slice; if (typeof value === 'function') { // a function Selector.prototype[name] = function () { var args = [this.value].concat(slice.call(arguments, 0)); return new Selector(value.apply(this, args)); } } else { // a constant Selector.prototype[name] = new Selector(value); } } /** * Utility functions for Strings */ /** * Test whether value is a String * @param {*} value * @return {Boolean} isString */ function isString(value) { return (value instanceof String) || (typeof value == 'string'); } /** * @constructor Unit * * A unit can be constructed in the following ways: * var a = new Unit(value, unit); * var b = new Unit(null, unit); * var c = Unit.parse(str); * * Example usage: * var a = new Unit(5, 'cm'); // 50 mm * var b = Unit.parse('23 kg'); // 23 kg * var c = math.in(a, new Unit(null, 'm'); // 0.05 m * * @param {Number} [value] A value like 5.2 * @param {String} [unit] A unit like "cm" or "inch" */ function Unit(value, unit) { if (!(this instanceof Unit)) { throw new Error('Unit constructor must be called with the new operator'); } if (value != null && !isNumber(value)) { throw new Error('First parameter in Unit constructor must be a number'); } if (unit != null && !isString(unit)) { throw new Error('Second parameter in Unit constructor must be a string'); } if (unit != null) { // find the unit and prefix from the string var res = _findUnit(unit); if (!res) { throw new Error('String "' + unit + '" is no unit'); } this.unit = res.unit; this.prefix = res.prefix; } else { this.unit = Unit.UNIT_NONE; this.prefix = Unit.PREFIX_NONE; // link to a list with supported prefixes } if (value != null) { this.value = this._normalize(value); this.fixPrefix = false; // is set true by the methods Unit.in and math.in } else { this.value = null; this.fixPrefix = true; } } math.type.Unit = Unit; (function() { var text, index, c; function skipWhitespace() { while (c == ' ' || c == '\t') { next(); } } function isDigitDot (c) { return ((c >= '0' && c <= '9') || c == '.'); } function isDigit (c) { return ((c >= '0' && c <= '9')); } function next() { index++; c = text.charAt(index); } function revert(oldIndex) { index = oldIndex; c = text.charAt(index); } function parseNumber () { var number = ''; var oldIndex = index; if (c == '+') { next(); } else if (c == '-') { number += c; next(); } if (!isDigitDot(c)) { // a + or - must be followed by a digit revert(oldIndex); return null; } // TODO only allow a single dot, and enforce at least one digit before or after the dot while (isDigitDot(c)) { number += c; next(); } // check for scientific notation like "2.3e-4" or "1.23e50" if (c == 'E' || c == 'e') { number += c; next(); if (c == '+' || c == '-') { number += c; next(); } // Scientific notation MUST be followed by an exponent if (!isDigit(c)) { // this is no legal number, exponent is missing. revert(oldIndex); return null; } while (isDigit(c)) { number += c; next(); } } return number; } function parseUnit() { var unit = ''; skipWhitespace(); while (c && c != ' ' && c != '\t') { unit += c; next(); } return unit || null; } /** * Parse a string into a unit. Returns null if the provided string does not * contain a valid unit. * @param {String} str A string like "5.2 inch", "4e2 kg" * @return {Unit | null} unit */ Unit.parse = function parse(str) { text = str; index = -1; c = ''; if (!isString(text)) { return null; } next(); skipWhitespace(); var value = parseNumber(); var unit; if (value) { unit = parseUnit(); next(); skipWhitespace(); if (c) { // garbage at the end. not good. return null; } if (value && unit) { return new Unit(Number(value), unit); } } else { unit = parseUnit(); next(); skipWhitespace(); if (c) { // garbage at the end. not good. return null; } return new Unit(null, unit) } return null; }; })(); /** * Test whether value is of type Unit * @param {*} value * @return {Boolean} isUnit */ function isUnit(value) { return (value instanceof Unit); } /** * create a copy of this unit * @return {Unit} clone */ Unit.prototype.clone = function () { var clone = new Unit(); for (var p in this) { if (this.hasOwnProperty(p)) { clone[p] = this[p]; } } return clone; }; /** * Normalize a value, based on its currently set unit * @param {Number} value * @return {Number} normalized value * @private */ Unit.prototype._normalize = function(value) { return (value + this.unit.offset) * this.unit.value * this.prefix.value; }; /** * Unnormalize a value, based on its currently set unit * @param {Number} value * @param {Number} [prefixValue] Optional prefix value to be used * @return {Number} unnormalized value * @private */ Unit.prototype._unnormalize = function (value, prefixValue) { if (prefixValue == undefined) { return value / this.unit.value / this.prefix.value - this.unit.offset; } else { return value / this.unit.value / prefixValue - this.unit.offset; } }; /** * Find a unit from a string * @param {String} str A string like 'cm' or 'inch' * @returns {Object | null} result When found, an object with fields unit and * prefix is returned. Else, null is returned. * @private */ function _findUnit(str) { var UNITS = Unit.UNITS; for (var i = 0, iMax = UNITS.length; i < iMax; i++) { var UNIT = UNITS[i]; if (util.endsWith(str, UNIT.name) ) { var prefixLen = (str.length - UNIT.name.length); var prefixName = str.substring(0, prefixLen); var prefix = UNIT.prefixes[prefixName]; if (prefix !== undefined) { // store unit, prefix, and value return { unit: UNIT, prefix: prefix }; } } } return null; } /** * Test if the given expression is a unit. * The unit can have a prefix but cannot have a value. * @param {String} unit A plain unit without value. Can have prefix, like "cm" * @return {Boolean} true if the given string is a unit */ Unit.isPlainUnit = function (unit) { return (_findUnit(unit) != null); }; /** * check if this unit has given base unit * @param {Unit.BASE_UNITS} base */ Unit.prototype.hasBase = function(base) { if (this.unit.base === undefined) { return (base === undefined); } return (this.unit.base === base); }; /** * Check if this unit has a base equal to another base * @param {Unit} other * @return {Boolean} true if equal base */ Unit.prototype.equalBase = function(other) { return (this.unit.base === other.unit.base); }; /** * Check if this unit equals another unit * @param {Unit} other * @return {Boolean} true if both units are equal */ Unit.prototype.equals = function(other) { return (this.equalBase(other) && this.value == other.value); }; /** * Create a clone of this unit with a representation * @param {String | Unit} plainUnit A plain unit, without value. Can have prefix, like "cm" * @returns {Unit} unit having fixed, specified unit */ Unit.prototype['in'] = function (plainUnit) { var other; if (isString(plainUnit)) { other = new Unit(null, plainUnit); if (!this.equalBase(other)) { throw new Error('Units do not match'); } other.value = this.value; return other; } else if (plainUnit instanceof Unit) { if (!this.equalBase(plainUnit)) { throw new Error('Units do not match'); } if (plainUnit.value != null) { throw new Error('Cannot convert to a unit with a value'); } if (plainUnit.unit == null) { throw new Error('Unit expected on the right hand side of function in'); } other = plainUnit.clone(); other.value = this.value; other.fixPrefix = true; return other; } else { throw new Error('String or Unit expected as parameter'); } }; /** * Return the value of the unit when represented with given plain unit * @param {String | Unit} plainUnit For example 'cm' or 'inch' * @return {Number} value */ Unit.prototype.toNumber = function (plainUnit) { var other = this['in'](plainUnit); var prefix = this.fixPrefix ? other._bestPrefix() : other.prefix; return other._unnormalize(other.value, prefix.value); }; /** * Get string representation * @return {String} */ Unit.prototype.toString = function() { var value, str; if (!this.fixPrefix) { var bestPrefix = this._bestPrefix(); value = this._unnormalize(this.value, bestPrefix.value); str = (this.value != null) ? util.formatNumber(value) + ' ' : ''; str += bestPrefix.name + this.unit.name; } else { value = this._unnormalize(this.value); str = (this.value != null) ? util.formatNumber(value) + ' ' : ''; str += this.prefix.name + this.unit.name; } return str; }; /** * Calculate the best prefix using current value. * @returns {Object} prefix * @private */ Unit.prototype._bestPrefix = function () { // find the best prefix value (resulting in the value of which // the absolute value of the log10 is closest to zero, // though with a little offset of 1.2 for nicer values: you get a // sequence 1mm 100mm 500mm 0.6m 1m 10m 100m 500m 0.6km 1km ... var absValue = Math.abs(this.value / this.unit.value); var bestPrefix = Unit.PREFIX_NONE; var bestDiff = Math.abs( Math.log(absValue / bestPrefix.value) / Math.LN10 - 1.2); var prefixes = this.unit.prefixes; for (var p in prefixes) { if (prefixes.hasOwnProperty(p)) { var prefix = prefixes[p]; if (prefix.scientific) { var diff = Math.abs( Math.log(absValue / prefix.value) / Math.LN10 - 1.2); if (diff < bestDiff) { bestPrefix = prefix; bestDiff = diff; } } } } return bestPrefix; }; Unit.PREFIXES = { 'NONE': { '': {'name': '', 'value': 1, 'scientific': true} }, 'SHORT': { '': {'name': '', 'value': 1, 'scientific': true}, 'da': {'name': 'da', 'value': 1e1, 'scientific': false}, 'h': {'name': 'h', 'value': 1e2, 'scientific': false}, 'k': {'name': 'k', 'value': 1e3, 'scientific': true}, 'M': {'name': 'M', 'value': 1e6, 'scientific': true}, 'G': {'name': 'G', 'value': 1e9, 'scientific': true}, 'T': {'name': 'T', 'value': 1e12, 'scientific': true}, 'P': {'name': 'P', 'value': 1e15, 'scientific': true}, 'E': {'name': 'E', 'value': 1e18, 'scientific': true}, 'Z': {'name': 'Z', 'value': 1e21, 'scientific': true}, 'Y': {'name': 'Y', 'value': 1e24, 'scientific': true}, 'd': {'name': 'd', 'value': 1e-1, 'scientific': false}, 'c': {'name': 'c', 'value': 1e-2, 'scientific': false}, 'm': {'name': 'm', 'value': 1e-3, 'scientific': true}, // 'µ': {'name': 'µ', 'value': 1e-6, 'scientific': true}, 'u': {'name': 'u', 'value': 1e-6, 'scientific': true}, 'n': {'name': 'n', 'value': 1e-9, 'scientific': true}, 'p': {'name': 'p', 'value': 1e-12, 'scientific': true}, 'f': {'name': 'f', 'value': 1e-15, 'scientific': true}, 'a': {'name': 'a', 'value': 1e-18, 'scientific': true}, 'z': {'name': 'z', 'value': 1e-21, 'scientific': true}, 'y': {'name': 'y', 'value': 1e-24, 'scientific': true} }, 'LONG': { '': {'name': '', 'value': 1, 'scientific': true}, 'deca': {'name': 'deca', 'value': 1e1, 'scientific': false}, 'hecto': {'name': 'hecto', 'value': 1e2, 'scientific': false}, 'kilo': {'name': 'kilo', 'value': 1e3, 'scientific': true}, 'mega': {'name': 'mega', 'value': 1e6, 'scientific': true}, 'giga': {'name': 'giga', 'value': 1e9, 'scientific': true}, 'tera': {'name': 'tera', 'value': 1e12, 'scientific': true}, 'peta': {'name': 'peta', 'value': 1e15, 'scientific': true}, 'exa': {'name': 'exa', 'value': 1e18, 'scientific': true}, 'zetta': {'name': 'zetta', 'value': 1e21, 'scientific': true}, 'yotta': {'name': 'yotta', 'value': 1e24, 'scientific': true}, 'deci': {'name': 'deci', 'value': 1e-1, 'scientific': false}, 'centi': {'name': 'centi', 'value': 1e-2, 'scientific': false}, 'milli': {'name': 'milli', 'value': 1e-3, 'scientific': true}, 'micro': {'name': 'micro', 'value': 1e-6, 'scientific': true}, 'nano': {'name': 'nano', 'value': 1e-9, 'scientific': true}, 'pico': {'name': 'pico', 'value': 1e-12, 'scientific': true}, 'femto': {'name': 'femto', 'value': 1e-15, 'scientific': true}, 'atto': {'name': 'atto', 'value': 1e-18, 'scientific': true}, 'zepto': {'name': 'zepto', 'value': 1e-21, 'scientific': true}, 'yocto': {'name': 'yocto', 'value': 1e-24, 'scientific': true} }, 'BINARY_SHORT': { '': {'name': '', 'value': 1, 'scientific': true}, 'k': {'name': 'k', 'value': 1024, 'scientific': true}, 'M': {'name': 'M', 'value': Math.pow(1024, 2), 'scientific': true}, 'G': {'name': 'G', 'value': Math.pow(1024, 3), 'scientific': true}, 'T': {'name': 'T', 'value': Math.pow(1024, 4), 'scientific': true}, 'P': {'name': 'P', 'value': Math.pow(1024, 5), 'scientific': true}, 'E': {'name': 'E', 'value': Math.pow(1024, 6), 'scientific': true}, 'Z': {'name': 'Z', 'value': Math.pow(1024, 7), 'scientific': true}, 'Y': {'name': 'Y', 'value': Math.pow(1024, 8), 'scientific': true}, 'Ki': {'name': 'Ki', 'value': 1024, 'scientific': true}, 'Mi': {'name': 'Mi', 'value': Math.pow(1024, 2), 'scientific': true}, 'Gi': {'name': 'Gi', 'value': Math.pow(1024, 3), 'scientific': true}, 'Ti': {'name': 'Ti', 'value': Math.pow(1024, 4), 'scientific': true}, 'Pi': {'name': 'Pi', 'value': Math.pow(1024, 5), 'scientific': true}, 'Ei': {'name': 'Ei', 'value': Math.pow(1024, 6), 'scientific': true}, 'Zi': {'name': 'Zi', 'value': Math.pow(1024, 7), 'scientific': true}, 'Yi': {'name': 'Yi', 'value': Math.pow(1024, 8), 'scientific': true} }, 'BINARY_LONG': { '': {'name': '', 'value': 1, 'scientific': true}, 'kilo': {'name': 'kilo', 'value': 1024, 'scientific': true}, 'mega': {'name': 'mega', 'value': Math.pow(1024, 2), 'scientific': true}, 'giga': {'name': 'giga', 'value': Math.pow(1024, 3), 'scientific': true}, 'tera': {'name': 'tera', 'value': Math.pow(1024, 4), 'scientific': true}, 'peta': {'name': 'peta', 'value': Math.pow(1024, 5), 'scientific': true}, 'exa': {'name': 'exa', 'value': Math.pow(1024, 6), 'scientific': true}, 'zetta': {'name': 'zetta', 'value': Math.pow(1024, 7), 'scientific': true}, 'yotta': {'name': 'yotta', 'value': Math.pow(1024, 8), 'scientific': true}, 'kibi': {'name': 'kibi', 'value': 1024, 'scientific': true}, 'mebi': {'name': 'mebi', 'value': Math.pow(1024, 2), 'scientific': true}, 'gibi': {'name': 'gibi', 'value': Math.pow(1024, 3), 'scientific': true}, 'tebi': {'name': 'tebi', 'value': Math.pow(1024, 4), 'scientific': true}, 'pebi': {'name': 'pebi', 'value': Math.pow(1024, 5), 'scientific': true}, 'exi': {'name': 'exi', 'value': Math.pow(1024, 6), 'scientific': true}, 'zebi': {'name': 'zebi', 'value': Math.pow(1024, 7), 'scientific': true}, 'yobi': {'name': 'yobi', 'value': Math.pow(1024, 8), 'scientific': true} } }; Unit.PREFIX_NONE = {'name': '', 'value': 1, 'scientific': true}; Unit.BASE_UNITS = { 'NONE': {}, 'LENGTH': {}, // meter 'MASS': {}, // kilogram 'TIME': {}, // second 'CURRENT': {}, // ampere 'TEMPERATURE': {}, // kelvin 'LUMINOUS_INTENSITY': {}, // candela 'AMOUNT_OF_SUBSTANCE': {}, // mole 'FORCE': {}, // Newton 'SURFACE': {}, // m2 'VOLUME': {}, // m3 'ANGLE': {}, // rad 'BIT': {} // bit (digital) }; var BASE_UNITS = Unit.BASE_UNITS; var PREFIXES = Unit.PREFIXES; Unit.BASE_UNIT_NONE = {}; Unit.UNIT_NONE = {'name': '', 'base': Unit.BASE_UNIT_NONE, 'value': 1, 'offset': 0}; Unit.UNITS = [ // length {'name': 'meter', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.LONG, 'value': 1, 'offset': 0}, {'name': 'inch', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.0254, 'offset': 0}, {'name': 'foot', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.3048, 'offset': 0}, {'name': 'yard', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.9144, 'offset': 0}, {'name': 'mile', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 1609.344, 'offset': 0}, {'name': 'link', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.201168, 'offset': 0}, {'name': 'rod', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 5.029210, 'offset': 0}, {'name': 'chain', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 20.1168, 'offset': 0}, {'name': 'angstrom', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 1e-10, 'offset': 0}, {'name': 'm', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.SHORT, 'value': 1, 'offset': 0}, //{'name': 'in', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.0254, 'offset': 0}, not supported, In is an operator {'name': 'ft', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.3048, 'offset': 0}, {'name': 'yd', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.9144, 'offset': 0}, {'name': 'mi', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 1609.344, 'offset': 0}, {'name': 'li', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.201168, 'offset': 0}, {'name': 'rd', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 5.029210, 'offset': 0}, {'name': 'ch', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 20.1168, 'offset': 0}, {'name': 'mil', 'base': BASE_UNITS.LENGTH, 'prefixes': PREFIXES.NONE, 'value': 0.0000254, 'offset': 0}, // 1/1000 inch // Surface {'name': 'm2', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.SHORT, 'value': 1, 'offset': 0}, {'name': 'sqin', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 0.00064516, 'offset': 0}, // 645.16 mm2 {'name': 'sqft', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 0.09290304, 'offset': 0}, // 0.09290304 m2 {'name': 'sqyd', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 0.83612736, 'offset': 0}, // 0.83612736 m2 {'name': 'sqmi', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 2589988.110336, 'offset': 0}, // 2.589988110336 km2 {'name': 'sqrd', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 25.29295, 'offset': 0}, // 25.29295 m2 {'name': 'sqch', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 404.6873, 'offset': 0}, // 404.6873 m2 {'name': 'sqmil', 'base': BASE_UNITS.SURFACE, 'prefixes': PREFIXES.NONE, 'value': 6.4516e-10, 'offset': 0}, // 6.4516 * 10^-10 m2 // Volume {'name': 'm3', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.SHORT, 'value': 1, 'offset': 0}, {'name': 'L', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.SHORT, 'value': 0.001, 'offset': 0}, // litre {'name': 'litre', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.LONG, 'value': 0.001, 'offset': 0}, {'name': 'cuin', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 1.6387064e-5, 'offset': 0}, // 1.6387064e-5 m3 {'name': 'cuft', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.028316846592, 'offset': 0}, // 28.316 846 592 L {'name': 'cuyd', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.764554857984, 'offset': 0}, // 764.554 857 984 L {'name': 'teaspoon', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.000005, 'offset': 0}, // 5 mL {'name': 'tablespoon', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.000015, 'offset': 0}, // 15 mL //{'name': 'cup', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.000240, 'offset': 0}, // 240 mL // not possible, we have already another cup // Liquid volume {'name': 'minim', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.00000006161152, 'offset': 0}, // 0.06161152 mL {'name': 'fluiddram', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0000036966911, 'offset': 0}, // 3.696691 mL {'name': 'fluidounce', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.00002957353, 'offset': 0}, // 29.57353 mL {'name': 'gill', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0001182941, 'offset': 0}, // 118.2941 mL {'name': 'cup', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0002365882, 'offset': 0}, // 236.5882 mL {'name': 'pint', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0004731765, 'offset': 0}, // 473.1765 mL {'name': 'quart', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0009463529, 'offset': 0}, // 946.3529 mL {'name': 'gallon', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.003785412, 'offset': 0}, // 3.785412 L {'name': 'beerbarrel', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.1173478, 'offset': 0}, // 117.3478 L {'name': 'oilbarrel', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.1589873, 'offset': 0}, // 158.9873 L {'name': 'hogshead', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.2384810, 'offset': 0}, // 238.4810 L //{'name': 'min', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.00000006161152, 'offset': 0}, // 0.06161152 mL // min is already in use as minute {'name': 'fldr', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0000036966911, 'offset': 0}, // 3.696691 mL {'name': 'floz', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.00002957353, 'offset': 0}, // 29.57353 mL {'name': 'gi', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0001182941, 'offset': 0}, // 118.2941 mL {'name': 'cp', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0002365882, 'offset': 0}, // 236.5882 mL {'name': 'pt', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0004731765, 'offset': 0}, // 473.1765 mL {'name': 'qt', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.0009463529, 'offset': 0}, // 946.3529 mL {'name': 'gal', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.003785412, 'offset': 0}, // 3.785412 L {'name': 'bbl', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.1173478, 'offset': 0}, // 117.3478 L {'name': 'obl', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.1589873, 'offset': 0}, // 158.9873 L //{'name': 'hogshead', 'base': BASE_UNITS.VOLUME, 'prefixes': PREFIXES.NONE, 'value': 0.2384810, 'offset': 0}, // 238.4810 L // TODO: hh? // Mass {'name': 'g', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.SHORT, 'value': 0.001, 'offset': 0}, {'name': 'gram', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.LONG, 'value': 0.001, 'offset': 0}, {'name': 'ton', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.SHORT, 'value': 907.18474, 'offset': 0}, {'name': 'tonne', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.SHORT, 'value': 1000, 'offset': 0}, {'name': 'grain', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 64.79891e-6, 'offset': 0}, {'name': 'dram', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 1.7718451953125e-3, 'offset': 0}, {'name': 'ounce', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 28.349523125e-3, 'offset': 0}, {'name': 'poundmass', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 453.59237e-3, 'offset': 0}, {'name': 'hundredweight', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 45.359237, 'offset': 0}, {'name': 'stick', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 115e-3, 'offset': 0}, {'name': 'gr', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 64.79891e-6, 'offset': 0}, {'name': 'dr', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 1.7718451953125e-3, 'offset': 0}, {'name': 'oz', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 28.349523125e-3, 'offset': 0}, {'name': 'lbm', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 453.59237e-3, 'offset': 0}, {'name': 'cwt', 'base': BASE_UNITS.MASS, 'prefixes': PREFIXES.NONE, 'value': 45.359237, 'offset': 0}, // Time {'name': 's', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.SHORT, 'value': 1, 'offset': 0}, {'name': 'min', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 60, 'offset': 0}, {'name': 'h', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 3600, 'offset': 0}, {'name': 'seconds', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.LONG, 'value': 1, 'offset': 0}, {'name': 'second', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.LONG, 'value': 1, 'offset': 0}, {'name': 'sec', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.LONG, 'value': 1, 'offset': 0}, {'name': 'minutes', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 60, 'offset': 0}, {'name': 'minute', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 60, 'offset': 0}, {'name': 'hours', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 3600, 'offset': 0}, {'name': 'hour', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 3600, 'offset': 0}, {'name': 'day', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 86400, 'offset': 0}, {'name': 'days', 'base': BASE_UNITS.TIME, 'prefixes': PREFIXES.NONE, 'value': 86400, 'offset': 0}, // Angles {'name': 'rad', 'base': BASE_UNITS.ANGLE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, {'name': 'deg', 'base': BASE_UNITS.ANGLE, 'prefixes': PREFIXES.NONE, 'value': 0.017453292519943295769236907684888, 'offset': 0}, // deg = rad / (2*pi) * 360 = rad / 0.017453292519943295769236907684888 {'name': 'grad', 'base': BASE_UNITS.ANGLE, 'prefixes': PREFIXES.NONE, 'value': 0.015707963267948966192313216916399, 'offset': 0}, // grad = rad / (2*pi) * 400 = rad / 0.015707963267948966192313216916399 {'name': 'cycle', 'base': BASE_UNITS.ANGLE, 'prefixes': PREFIXES.NONE, 'value': 6.2831853071795864769252867665793, 'offset': 0}, // cycle = rad / (2*pi) = rad / 6.2831853071795864769252867665793 // Electric current {'name': 'A', 'base': BASE_UNITS.CURRENT, 'prefixes': PREFIXES.SHORT, 'value': 1, 'offset': 0}, {'name': 'ampere', 'base': BASE_UNITS.CURRENT, 'prefixes': PREFIXES.LONG, 'value': 1, 'offset': 0}, // Temperature // K(C) = °C + 273.15 // K(F) = (°F + 459.67) / 1.8 // K(R) = °R / 1.8 {'name': 'K', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, {'name': 'degC', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 273.15}, {'name': 'degF', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1/1.8, 'offset': 459.67}, {'name': 'degR', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1/1.8, 'offset': 0}, {'name': 'kelvin', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, {'name': 'celsius', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 273.15}, {'name': 'fahrenheit', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1/1.8, 'offset': 459.67}, {'name': 'rankine', 'base': BASE_UNITS.TEMPERATURE, 'prefixes': PREFIXES.NONE, 'value': 1/1.8, 'offset': 0}, // amount of substance {'name': 'mol', 'base': BASE_UNITS.AMOUNT_OF_SUBSTANCE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, {'name': 'mole', 'base': BASE_UNITS.AMOUNT_OF_SUBSTANCE, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, // luminous intensity {'name': 'cd', 'base': BASE_UNITS.LUMINOUS_INTENSITY, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, {'name': 'candela', 'base': BASE_UNITS.LUMINOUS_INTENSITY, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, // TODO: units STERADIAN //{'name': 'sr', 'base': BASE_UNITS.STERADIAN, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, //{'name': 'steradian', 'base': BASE_UNITS.STERADIAN, 'prefixes': PREFIXES.NONE, 'value': 1, 'offset': 0}, // Force {'name': 'N', 'base': BASE_UNITS.FORCE, 'prefixes': PREFIXES.SHORT, 'value': 1, 'offset': 0}, {'name': 'newton', 'base': BASE_UNITS.FORCE, 'prefixes': PREFIXES.LONG, 'value': 1, 'offset': 0}, {'name': 'lbf', 'base': BASE_UNITS.FORCE, 'prefixes': PREFIXES.NONE, 'value': 4.4482216152605, 'offset': 0}, {'name': 'poundforce', 'base': BASE_UNITS.FORCE, 'prefixes': PREFIXES.NONE, 'value': 4.4482216152605, 'offset': 0}, // Binary {'name': 'b', 'base': BASE_UNITS.BIT, 'prefixes': PREFIXES.BINARY_SHORT, 'value': 1, 'offset': 0}, {'name': 'bits', 'base': BASE_UNITS.BIT, 'prefixes': PREFIXES.BINARY_LONG, 'value': 1, 'offset': 0}, {'name': 'B', 'base': BASE_UNITS.BIT, 'prefixes': PREFIXES.BINARY_SHORT, 'value': 8, 'offset': 0}, {'name': 'bytes', 'base': BASE_UNITS.BIT, 'prefixes': PREFIXES.BINARY_LONG, 'value': 8, 'offset': 0} ]; /** * mathjs constants */ math.E = Math.E; math.LN2 = Math.LN2; math.LN10 = Math.LN10; math.LOG2E = Math.LOG2E; math.LOG10E = Math.LOG10E; math.PI = Math.PI; math.SQRT1_2 = Math.SQRT1_2; math.SQRT2 = Math.SQRT2; math.I = new Complex(0, 1); // lower case constants math.pi = math.PI; math.e = math.E; math.i = math.I; /** * Helper methods for functions */ /** * Create a TypeError with message: * 'Function does not support a parameter of type '; * @param {String} name Function name * @param {*} value1 * @param {*} [value2] * @return {TypeError | Error} error */ function newUnsupportedTypeError(name, value1, value2) { var msg = undefined; if (arguments.length == 2) { var t = math['typeof'](value1); msg = 'Function ' + name + '(' + t + ') not supported'; } else if (arguments.length > 2) { var types = []; for (var i = 1; i < arguments.length; i++) { types.push(math['typeof'](arguments[i])); } msg = 'Function ' + name + '(' + types.join(', ') + ') not supported'; } else { msg = 'Unsupported parameter in function ' + name; } return new TypeError(msg); } /** * Create a syntax error with the message: * 'Wrong number of arguments in function ( provided, - expected)' * @param {String} name Function name * @param {Number} count Actual argument count * @param {Number} min Minimum required argument count * @param {Number} [max] Maximum required argument count */ function newArgumentsError(name, count, min, max) { var msg = 'Wrong number of arguments in function ' + name + ' (' + count + ' provided, ' + min + ((max != undefined) ? ('-' + max) : '') + ' expected)'; return new SyntaxError(msg); } /** * Node */ function Node() {} math.expr.node.Node = Node; /** * Evaluate the node * @return {*} result */ Node.prototype.eval = function () { throw new Error('Cannot evaluate a Node interface'); }; /** * Get string representation * @return {String} */ Node.prototype.toString = function() { return ''; }; /** * @constructor Constant * @param {*} value * @extends {Node} */ function Constant(value) { this.value = value; } Constant.prototype = new Node(); math.expr.node.Constant = Constant; /** * Evaluate the constant (just return it) * @return {*} value */ Constant.prototype.eval = function () { return this.value; }; /** * Get string representation * @return {String} str */ Constant.prototype.toString = function() { return math.format(this.value || null); }; /** * @constructor Operator * An operator with two arguments, like 2+3 * @param {String} name Function name, for example '+' * @param {function} fn Function, for example math.add * @param {Node[]} params Parameters */ function Operator (name, fn, params) { this.name = name; this.fn = fn; this.params = params; } Operator.prototype = new Node(); math.expr.node.Operator = Operator; /** * Evaluate the parameters * @return {*} result */ Operator.prototype.eval = function() { return this.fn.apply(this, this.params.map(function (param) { return param.eval(); })); }; /** * Get string representation * @return {String} str */ Operator.prototype.toString = function() { var params = this.params; // special case: unary minus if (this.fn === math.unaryminus) { return '-' + params[0].toString(); } switch (params.length) { case 1: // for example '5!' return params[0].toString() + this.name; case 2: // for example '2+3' var lhs = params[0].toString(); if (params[0] instanceof Operator) { lhs = '(' + lhs + ')'; } var rhs = params[1].toString(); if (params[1] instanceof Operator) { rhs = '(' + rhs + ')'; } return lhs + ' ' + this.name + ' ' + rhs; default: // this should occur. format as a function call return this.name + '(' + this.params.join(', ') + ')'; } }; /** * @constructor Symbol * A symbol can hold and evaluate a link to a value or function defined in a * scope. * @param {String} [name] * @param {Link} link * @extends {Node} */ function Symbol(name, link) { this.name = name; this.link = link; } Symbol.prototype = new Node(); math.expr.node.Symbol = Symbol; /** * Evaluate the symbol * @return {*} result * @override */ Symbol.prototype.eval = function() { // return the value of the link return this.link.get(); }; /** * Get string representation * @return {String} str * @override */ Symbol.prototype.toString = function() { return this.name; }; /** * @constructor Params * invoke a list with parameters on the results of a node * @param {Node} object * @param {Node[]} params */ function Params (object, params) { this.object = object; this.params = params; } Params.prototype = new Node(); math.expr.node.Params = Params; /** * Evaluate the parameters * @return {*} result */ Params.prototype.eval = function() { var object = this.object; if (object == undefined) { throw new Error ('Node undefined'); } var obj = object.eval(); // evaluate the parameters var res = this.params.map(function (arg) { return arg.eval(); }); if (typeof obj === 'function') { // invoke a function with the parameters return obj.apply(this, res); } else if (obj instanceof Object && obj.get) { // apply method get with the parameters return obj.get(res); } // TODO: apply parameters on a string else { throw new TypeError('Cannot apply parameters to object of type ' + math['typeof'](obj)); } }; /** * Get string representation * @return {String} str */ Params.prototype.toString = function() { // format the parameters like "(2, 4.2)" var str = this.object ? this.object.toString() : ''; if (this.params) { str += '(' + this.params.join(', ') + ')'; } return str; }; /** * @constructor MatrixNode * Holds an n-dimensional array with nodes * @param {Array} nodes * @extends {Node} */ function MatrixNode(nodes) { this.nodes = nodes || []; } MatrixNode.prototype = new Node(); math.expr.node.MatrixNode = MatrixNode; (function () { /** * Evaluate the array * @return {Matrix} results * @override */ MatrixNode.prototype.eval = function() { // recursively evaluate the nodes in the array, and merge the result var array = evalArray(this.nodes); if (containsMatrix(array)) { array = merge(array); } return new Matrix(array); }; /** * Recursively evaluate an array with nodes * @param {Array} array * @returns {Array} results */ function evalArray(array) { return array.map(function (child) { if (child instanceof Array) { // evaluate a nested array return evalArray(child); } else { // evaluate a node (end point) return child.eval(); } }) } /** * Merge nested Matrices in an Array. * @param {Array} array Two-dimensional array containing Matrices * @return {Array} merged The merged array (two-dimensional) */ function merge (array) { var merged = []; var rows = array.length; for (var r = 0; r < rows; r++) { var row = array[r]; var cols = row.length; var submatrix = null; var submatrixRows = null; for (var c = 0; c < cols; c++) { var entry = math.clone(row[c]); var size; if (entry instanceof Matrix) { // get the data from the matrix size = entry.size(); entry = entry.valueOf(); if (size.length == 1) { entry = [entry]; size = [1, size[0]]; } else if (size.length > 2) { throw new Error('Cannot merge a multi dimensional matrix'); } } else if (entry instanceof Range) { // change range into an 1xn matrix entry = [entry.valueOf()]; size = [1, entry[0].length]; } else { // change scalar into a 1x1 matrix size = [1, 1]; entry = [[entry]]; } // check the height of this row if (submatrix == null) { // first entry submatrix = entry; submatrixRows = size[0]; } else if (size[0] == submatrixRows) { // merge for (var s = 0; s < submatrixRows; s++) { submatrix[s] = submatrix[s].concat(entry[s]); } } else { // no good... throw new Error('Dimension mismatch ' + '(' + size[0] + ' != ' + submatrixRows + ')'); } } // merge the submatrix merged = merged.concat(submatrix); } return merged; } /** * Recursively test whether a multidimensional array contains at least one * Matrix or Range. * @param {Array} array * @return {Boolean} containsMatrix */ function containsMatrix(array) { return array.some(function (child) { if (child instanceof Matrix || child instanceof Range) { return true; } else if (child instanceof Array) { return containsMatrix(child); } else { return false; } }); } /** * Get string representation * @return {String} str * @override */ MatrixNode.prototype.toString = function() { return util.formatArray(this.nodes); }; })(); /** * @constructor Block * Holds a set with nodes * @extends {Node} */ function Block() { this.params = []; this.visible = []; } Block.prototype = new Node(); math.expr.node.Block = Block; /** * Add a parameter * @param {Node} param * @param {Boolean} [visible] true by default */ Block.prototype.add = function (param, visible) { var index = this.params.length; this.params[index] = param; this.visible[index] = (visible != undefined) ? visible : true; }; /** * Evaluate the set * @return {*[]} results * @override */ Block.prototype.eval = function() { // evaluate the parameters var results = []; for (var i = 0, iMax = this.params.length; i < iMax; i++) { var result = this.params[i].eval(); if (this.visible[i]) { results.push(result); } } return results; }; /** * Get string representation * @return {String} str * @override */ Block.prototype.toString = function() { var strings = []; for (var i = 0, iMax = this.params.length; i < iMax; i++) { if (this.visible[i]) { strings.push('\n ' + this.params[i].toString()); } } return '[' + strings.join(',') + '\n]'; }; /** * @constructor Assignment * @param {String} name Symbol name * @param {Node[] | undefined} params Zero or more parameters * @param {Node} expr The expression defining the symbol * @param {Link} result placeholder for the result */ function Assignment(name, params, expr, result) { this.name = name; this.params = params; this.expr = expr; this.result = result; } Assignment.prototype = new Node(); math.expr.node.Assignment = Assignment; /** * Evaluate the assignment * @return {*} result */ Assignment.prototype.eval = function() { if (this.expr === undefined) { throw new Error('Undefined symbol ' + this.name); } var result; var params = this.params; if (params && params.length) { // change part of a matrix, for example "a=[]", "a(2,3)=4.5" var paramResults = []; this.params.forEach(function (param) { paramResults.push(param.eval()); }); var exprResult = this.expr.eval(); // test if definition is currently undefined var prevResult = this.result.get(); if (prevResult == undefined) { throw new Error('Undefined symbol ' + this.name); } // TODO: check type of prevResult: Matrix, Array, String, other... if (!prevResult.set) { throw new TypeError('Cannot apply a subset to object of type ' + math['typeof'](prevResult)); } result = prevResult.set(paramResults, exprResult); this.result.value = result; } else { // variable definition, for example "a = 3/4" result = this.expr.eval(); this.result.value = result; } return result; }; /** * Get string representation * @return {String} */ Assignment.prototype.toString = function() { var str = ''; str += this.name; if (this.params && this.params.length) { str += '(' + this.params.join(', ') + ')'; } str += ' = '; str += this.expr.toString(); return str; }; /** * @constructor FunctionAssignment * assigns a custom defined function * * @param {String} name Function name * @param {String[]} variableNames Variable names * @param {function[]} variables Links to the variables in a scope * @param {Node} expr The function expression * @param {Link} result Link to store the result */ function FunctionAssignment(name, variableNames, variables, expr, result) { this.name = name; this.variables = variables; this.values = []; for (var i = 0, iMax = this.variables.length; i < iMax; i++) { this.values[i] = (function () { var value = function () { return value.value; }; value.value = undefined; return value; })(); } this.def = this.createFunction(name, variableNames, variables, expr); this.result = result; } FunctionAssignment.prototype = new Node(); math.expr.node.FunctionAssignment = FunctionAssignment; /** * Create a function from the function assignment * @param {String} name Function name * @param {String[]} variableNames Variable names * @param {function[]} values Zero or more functions * @param {Node} expr The function expression * */ FunctionAssignment.prototype.createFunction = function (name, variableNames, values, expr) { var fn = function () { // validate correct number of arguments var valuesNum = values ? values.length : 0; var argumentsNum = arguments ? arguments.length : 0; if (valuesNum != argumentsNum) { throw newArgumentsError(name, argumentsNum, valuesNum); } // fill in all parameter values if (valuesNum > 0) { for (var i = 0; i < valuesNum; i++){ values[i].value = arguments[i]; } } // evaluate the expression return expr.eval(); }; fn.toString = function() { return name + '(' + variableNames.join(', ') + ')'; }; return fn; }; /** * Evaluate the function assignment * @return {function} result */ FunctionAssignment.prototype.eval = function() { // link the variables to the values of this function assignment var variables = this.variables, values = this.values; for (var i = 0, iMax = variables.length; i < iMax; i++) { variables[i].value = values[i]; } // put the definition in the result this.result.set(this.def); // TODO: what to return? a neat "function y(x) defined"? return this.def; }; /** * get string representation * @return {String} str */ FunctionAssignment.prototype.toString = function() { return this.def.toString(); }; /** * Scope * A scope stores functions. * * @constructor mathnotepad.Scope * @param {Scope} [parentScope] * @param {Object} [options] Available options: * {boolean} readonly (false by default). */ math.expr.Scope = function Scope(parentScope, options) { this.readonly = false; if (options && options.readonly != undefined) { this.readonly = options.readonly; } this.parentScope = parentScope; this.nestedScopes = undefined; this.symbols = {}; // the actual symbols // the following objects are just used to test existence. this.defs = {}; // definitions by name (for example "a = [1, 2; 3, 4]") this.updates = {}; // updates by name (for example "a(2, 1) = 5.2") this.links = {}; // links by name (for example "2 * a") }; // TODO: rethink the whole scoping solution again. Try to simplify math.expr.Scope.prototype = { /** * Create a nested scope * The variables in a nested scope are not accessible from the parent scope * @return {math.expr.Scope} nestedScope */ createNestedScope: function () { if (this.readonly) { throw new Error('Cannot create nested scope: Scope is read-only'); } var nestedScope = new math.expr.Scope(this); if (!this.nestedScopes) { this.nestedScopes = []; } this.nestedScopes.push(nestedScope); return nestedScope; }, /** * Clear all symbols in this scope and its nested scopes * (parent scope will not be cleared) */ clear: function () { if (this.readonly) { throw new Error('Cannot clear scope: Scope is read-only'); } this.symbols = {}; this.defs = {}; this.links = {}; this.updates = {}; if (this.nestedScopes) { var nestedScopes = this.nestedScopes; for (var i = 0, iMax = nestedScopes.length; i < iMax; i++) { nestedScopes[i].clear(); } } }, /** * create a symbol * @param {String} name * @return {Link} symbol * @private */ createSymbol: function (name) { var symbol = this.symbols[name]; if (!symbol) { // get a link to the last definition var lastDef = this.findDef(name); // create a new symbol symbol = new Link(this, name, lastDef); this.symbols[name] = symbol; } return symbol; }, /** * create a link to a value. * @param {String} name * @return {Link} symbol */ createLink: function (name) { var symbol = this.links[name]; if (!symbol) { symbol = this.createSymbol(name); this.links[name] = symbol; } return symbol; }, /** * Create a variable definition * Returns the created symbol * @param {String} name * @param {*} [value] * @return {Link} symbol */ createDef: function (name, value) { if (this.readonly) { throw new Error('Cannot create variable: Scope is read-only'); } var symbol = this.defs[name]; if (!symbol) { symbol = this.createSymbol(name); this.defs[name] = symbol; } if (symbol && value != undefined) { symbol.set(value); } return symbol; }, /** * Create a variable update definition * Returns the created symbol * @param {String} name * @return {Link} symbol */ createUpdate: function (name) { if (this.readonly) { throw new Error('Cannot update variable: Scope is read-only'); } var symbol = this.updates[name]; if (!symbol) { symbol = this.createLink(name); this.updates[name] = symbol; } return symbol; }, /** * Create a constant * @param {String} name * @param {*} value * @return {Link} symbol * @private */ createConstant: function (name, value) { var symbol = new Link(this, name, value); this.symbols[name] = symbol; this.defs[name] = symbol; return symbol; }, /** * get the link to a symbol definition or update. * If the symbol is not found in this scope, it will be looked up in its parent * scope. * @param {String} name * @return {Link | undefined} symbol, or undefined when not found */ findDef: function (name) { var symbol; // check scope symbol = this.defs[name]; if (symbol) { return symbol; } symbol = this.updates[name]; if (symbol) { return symbol; } // check parent scope if (this.parentScope) { return this.parentScope.findDef(name); } else { // this is the root scope (has no parent), // try to load constants, functions, or unit from the library // check function (and load the function), for example "sin" or "sqrt" // search in the mathnotepad.math namespace for this symbol var fn = math[name]; if (fn) { return this.createConstant(name, fn); } // Check if token is a unit if (Unit.isPlainUnit(name)) { var unit = new Unit(null, name); return this.createConstant(name, unit); } } return undefined; }, /** * Remove a link to a symbol * @param {String} name */ removeLink: function (name) { delete this.links[name]; }, /** * Remove a definition of a symbol * @param {String} name */ removeDef: function (name) { delete this.defs[name]; }, /** * Remove an update definition of a symbol * @param {String} name */ removeUpdate: function (name) { delete this.updates[name]; }, /** * initialize the scope and its nested scopes * * All functions are linked to their previous definition * If there is no parentScope, or no definition of the func in the parent scope, * the link will be set undefined */ init: function () { var symbols = this.symbols; var parentScope = this.parentScope; for (var name in symbols) { if (symbols.hasOwnProperty(name)) { var symbol = symbols[name]; symbol.set(parentScope ? parentScope.findDef(name) : undefined); } } if (this.nestedScopes) { this.nestedScopes.forEach(function (nestedScope) { nestedScope.init(); }); } }, /** * Check whether this scope or any of its nested scopes contain a link to a * symbol with given name * @param {String} name * @return {boolean} hasLink True if a link with given name is found */ hasLink: function (name) { if (this.links[name]) { return true; } if (this.nestedScopes) { var nestedScopes = this.nestedScopes; for (var i = 0, iMax = nestedScopes.length; i < iMax; i++) { if (nestedScopes[i].hasLink(name)) { return true; } } } return false; }, /** * Check whether this scope contains a definition of a symbol with given name * @param {String} name * @return {boolean} hasDef True if a definition with given name is found */ hasDef: function (name) { return (this.defs[name] != undefined); }, /** * Check whether this scope contains an update definition of a symbol with * given name * @param {String} name * @return {boolean} hasUpdate True if an update definition with given name is found */ hasUpdate: function (name) { return (this.updates[name] != undefined); }, /** * Retrieve all undefined symbols * @return {function[]} undefinedSymbols All symbols which are undefined */ getUndefinedSymbols: function () { var symbols = this.symbols; var undefinedSymbols = []; for (var i in symbols) { if (symbols.hasOwnProperty(i)) { var symbol = symbols[i]; if (symbol.value == undefined) { undefinedSymbols.push(symbol); } } } if (this.nestedScopes) { this.nestedScopes.forEach(function (nestedScope) { undefinedSymbols = undefinedSymbols.concat(nestedScope.getUndefinedSymbols()); }); } return undefinedSymbols; } }; // TODO: comment link function Link (scope, name, value) { this.scope = scope; this.name = name; this.value = value; } /** * Get the links value * @returns {*} value */ Link.prototype.get = function () { var value = this.value; if (!value) { // try to resolve again value = this.value = this.scope.findDef(this.name); if (!value) { throw new Error('Undefined symbol ' + this.name); } } // resolve a chain of links while (value instanceof Link) { value = value.get(); } return value; }; /** * Set the value for the link * @param {*} value */ Link.prototype.set = function (value) { this.value = value; }; (function () { /** * @constructor math.expr.Parser * Parser parses math expressions and evaluates them or returns a node tree. * * @param {Object} [options] Available options: * {boolean} readonly (false by default). * * Methods: * var result = parser.eval(expr); // evaluate an expression * var value = parser.get(name); // retrieve a variable from the parser * parser.set(name, value); // set a variable in the parser * * // it is possible to parse an expression into a node tree: * var node = parser.parse(expr); // parse an expression into a node tree * var result = node.eval(); // evaluate a parsed node * * Example usage: * var parser = new math.expr.Parser(); * // Note: there is a convenience method which can be used instead: * // var parser = new math.parser(); * * // evaluate expressions * var a = parser.eval('sqrt(3^2 + 4^2)'); // 5 * var b = parser.eval('sqrt(-4)'); // 2i * var c = parser.eval('2 inch in cm'); // 5.08 cm * var d = parser.eval('cos(45 deg)'); // 0.7071067811865476 * * // define variables and functions * parser.eval('x = 7 / 2'); // 3.5 * parser.eval('x + 3'); // 6.5 * parser.eval('function f(x, y) = x^y'); // f(x, y) * parser.eval('f(2, 3)'); // 8 * * // get and set variables and functions * var x = parser.get('x'); // 7 * var f = parser.get('f'); // function * var g = f(3, 2); // 9 * parser.set('h', 500); * var i = parser.eval('h / 2'); // 250 * parser.set('hello', function (name) { * return 'hello, ' + name + '!'; * }); * parser.eval('hello("user")'); // "hello, user!" * * // clear defined functions and variables * parser.clear(); */ math.expr.Parser = function Parser(options) { if (!(this instanceof math.expr.Parser)) { throw new SyntaxError( 'Parser constructor must be called with the new operator'); } this.scope = new math.expr.Scope(null, options); }; /** * Parse an expression end return the parsed function node. * The node can be evaluated via node.eval() * @param {String} expression * @param {Scope} [scope] * @return {Node} node * @throws {Error} */ math.expr.Parser.prototype.parse = function (expression, scope) { expr = expression || ''; if (!scope) { scope = this.scope; } return parse_start(scope); }; /** * Parse and evaluate the given expression * @param {String} expression A string containing an expression, for example "2+3" * @return {*} result The result, or undefined when the expression was * empty * @throws {Error} */ math.expr.Parser.prototype.eval = function (expression) { var node = this.parse(expression); return node.eval(); }; /** * Get a variable (a function or variable) by name from the parsers scope. * Returns undefined when not found * @param {String} name * @return {* | undefined} value */ math.expr.Parser.prototype.get = function (name) { var symbol = this.scope.findDef(name); if (symbol) { return symbol.get(); } return undefined; }; /** * Set a symbol (a function or variable) by name from the parsers scope. * @param {String} name * @param {* | undefined} value */ math.expr.Parser.prototype.set = function (name, value) { this.scope.createDef(name, value); }; /** * Clear the scope with variables and functions */ math.expr.Parser.prototype.clear = function () { this.scope.clear(); }; // token types enumeration var TOKENTYPE = { NULL : 0, DELIMITER : 1, NUMBER : 2, SYMBOL : 3, UNKNOWN : 4 }; var expr = ''; // current expression var index = 0; // current index in expr var c = ''; // current token character in expr var token = ''; // current token var token_type = TOKENTYPE.NULL; // type of the token // TODO: do not use this.token, but a local variable var token for better speed? -> getToken() must return token. /** * Get the next character from the expression. * The character is stored into the char t. * If the end of the expression is reached, the function puts an empty * string in t. * @private */ function getChar() { index++; c = expr.charAt(index); } /** * Get the first character from the expression. * The character is stored into the char t. * If the end of the expression is reached, the function puts an empty * string in t. * @private */ function getFirstChar() { index = 0; c = expr.charAt(0); } /** * Get next token in the current string expr. * Uses the Parser data expr, e, token, t, token_type and err * The token and token type are available at token_type and token * @private */ function getToken() { token_type = TOKENTYPE.NULL; token = ''; // skip over whitespaces while (c == ' ' || c == '\t') { // space or tab getChar(); } // skip comment if (c == '#') { while (c != '\n' && c != '') { getChar(); } } // check for end of expression if (c == '') { // token is still empty token_type = TOKENTYPE.DELIMITER; return; } // check for minus, comma, parentheses, quotes, newline, semicolon if (c == '-' || c == ',' || c == '(' || c == ')' || c == '[' || c == ']' || c == '\"' || c == '\n' || c == ';' || c == ':' || c == '!' || c == '\'') { token_type = TOKENTYPE.DELIMITER; token += c; getChar(); return; } // check for operators (delimiters) if (isDelimiter(c)) { token_type = TOKENTYPE.DELIMITER; while (isDelimiter(c)) { token += c; getChar(); } return; } // check for a number if (isDigitDot(c)) { token_type = TOKENTYPE.NUMBER; while (isDigitDot(c)) { token += c; getChar(); } // check for scientific notation like "2.3e-4" or "1.23e50" if (c == 'E' || c == 'e') { token += c; getChar(); if (c == '+' || c == '-') { token += c; getChar(); } // Scientific notation MUST be followed by an exponent if (!isDigit(c)) { // this is no legal number, exponent is missing. token_type = TOKENTYPE.UNKNOWN; } while (isDigit(c)) { token += c; getChar(); } } return; } // check for variables or functions if (isAlpha(c)) { token_type = TOKENTYPE.SYMBOL; while (isAlpha(c) || isDigit(c)) { token += c; getChar(); } return; } // something unknown is found, wrong characters -> a syntax error token_type = TOKENTYPE.UNKNOWN; while (c != '') { token += c; getChar(); } throw createSyntaxError('Syntax error in part "' + token + '"'); } /** * checks if the given char c is a delimiter * minus is not checked in this method (can be unary minus) * @param {String} c a string with one character * @return {Boolean} * @private */ function isDelimiter (c) { return c == '&' || c == '|' || c == '<' || c == '>' || c == '=' || c == '+' || c == '/' || c == '*' || c == '%' || c == '^' || c == ',' || c == ';' || c == '\n' || c == '!' || c == '\''; } /** * Check if a given name is valid * if not, an error is thrown * @param {String} name * @return {boolean} valid * @private */ function isValidSymbolName (name) { for (var i = 0, iMax = name.length; i < iMax; i++) { var c = name.charAt(i); //var valid = (isAlpha(c) || (i > 0 && isDigit(c))); // TODO var valid = (isAlpha(c)); if (!valid) { return false; } } return true; } /** * checks if the given char c is a letter (upper or lower case) * or underscore * @param {String} c a string with one character * @return {Boolean} * @private */ function isAlpha (c) { return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'); } /** * checks if the given char c is a digit or dot * @param {String} c a string with one character * @return {Boolean} * @private */ function isDigitDot (c) { return ((c >= '0' && c <= '9') || c == '.'); } /** * checks if the given char c is a digit * @param {String} c a string with one character * @return {Boolean} * @private */ function isDigit (c) { return ((c >= '0' && c <= '9')); } /** * Start of the parse levels below, in order of precedence * @param {Scope} scope * @return {Node} node * @private */ function parse_start (scope) { // get the first character in expression getFirstChar(); getToken(); var node; if (token == '') { // empty expression node = new Constant(undefined); } else { node = parse_block(scope); } // check for garbage at the end of the expression // an expression ends with a empty character '' and token_type DELIMITER if (token != '') { if (token_type == TOKENTYPE.DELIMITER) { // user entered a not existing operator like "//" // TODO: give hints for aliases, for example with "<>" give as hint " did you mean != ?" throw createError('Unknown operator ' + token); } else { throw createSyntaxError('Unexpected part "' + token + '"'); } } return node; } /** * Parse a block with expressions. Expressions can be separated by a newline * character '\n', or by a semicolon ';'. In case of a semicolon, no output * of the preceding line is returned. * @param {Scope} scope * @return {Node} node * @private */ function parse_block (scope) { var node, block, visible; if (token != '\n' && token != ';' && token != '') { node = parse_ans(scope); } while (token == '\n' || token == ';') { if (!block) { // initialize the block block = new Block(); if (node) { visible = (token != ';'); block.add(node, visible); } } getToken(); if (token != '\n' && token != ';' && token != '') { node = parse_ans(scope); visible = (token != ';'); block.add(node, visible); } } if (block) { return block; } if (!node) { node = parse_ans(scope); } return node; } /** * Parse assignment of ans. * Ans is assigned when the expression itself is no variable or function * assignment * @param {Scope} scope * @return {Node} node * @private */ function parse_ans (scope) { var expression = parse_function_assignment(scope); if (!scope.readonly) { // TODO: not so nice having to specify some special types here... if (!(expression instanceof Assignment) // !(expression instanceof FunctionAssignment) && // TODO // !(expression instanceof plot) // TODO ) { // create a variable definition for ans var name = 'ans'; var params = undefined; var link = scope.createDef(name); return new Assignment(name, params, expression, link); } } return expression; } /** * Parse a function assignment like "function f(a,b) = a*b" * @param {Scope} scope * @return {Node} node * @private */ function parse_function_assignment (scope) { // TODO: keyword 'function' must become a reserved keyword if (token_type == TOKENTYPE.SYMBOL && token == 'function') { // get function name getToken(); if (token_type != TOKENTYPE.SYMBOL) { throw createSyntaxError('Function name expected'); } var name = token; // get parenthesis open getToken(); if (token != '(') { throw createSyntaxError('Opening parenthesis ( expected'); } // get function variables var functionScope = scope.createNestedScope(); var variableNames = []; var variables = []; while (true) { getToken(); if (token_type == TOKENTYPE.SYMBOL) { // store parameter var variableName = token; var variable = functionScope.createDef(variableName); variableNames.push(variableName); variables.push(variable); } else { throw createSyntaxError('Variable name expected'); } getToken(); if (token == ',') { // ok, nothing to do, read next variable } else if (token == ')') { // end of variable list encountered. break loop break; } else { throw createSyntaxError('Comma , or closing parenthesis ) expected"'); } } getToken(); if (token != '=') { throw createSyntaxError('Equal sign = expected'); } // parse the expression, with the correct function scope getToken(); var expression = parse_range(functionScope); var result = scope.createDef(name); return new FunctionAssignment(name, variableNames, variables, expression, result); } return parse_assignment(scope); } /** * Assignment of a variable, can be a variable like "a=2.3" or a updating an * existing variable like "matrix(2,3:5)=[6,7,8]" * @param {Scope} scope * @return {Node} node * @private */ function parse_assignment (scope) { var linkExisted = false; if (token_type == TOKENTYPE.SYMBOL) { linkExisted = scope.hasLink(token); } var node = parse_range(scope); // TODO: support chained assignments like "a = b = 2.3" if (token == '=') { if (node instanceof Symbol) { // assignment if (!linkExisted) { // we parsed the assignment as if it where an expression instead, // therefore, a link was created to the symbol. This link must // be cleaned up again, and only if it wasn't existing before scope.removeLink(name); } // parse the expression, with the correct function scope getToken(); var name = node.name; var params = null; var expression = parse_range(scope); var link = scope.createDef(name); return new Assignment(name, params, expression, link); } else if (node instanceof Params && node.object instanceof Symbol) { // update of a variable if (!linkExisted) { // we parsed the assignment as if it where an expression instead, // therefore, a link was created to the symbol. This link must // be cleaned up again, and only if it wasn't existing before scope.removeLink(name); } // parse the expression, with the correct function scope getToken(); var name = node.object.name; var params = node.params; var expression = parse_range(scope); var link = scope.createUpdate(name); return new Assignment(name, params, expression, link); } else { throw createSyntaxError('Symbol expected at the left hand side ' + 'of assignment operator ='); } } return node; } /** * parse range, "start:end" or "start:step:end" * @param {Scope} scope * @return {Node} node * @private */ function parse_range (scope) { var node = parse_conditions(scope); if (token == ':') { var params = [node]; while (token == ':') { getToken(); params.push(parse_conditions(scope)); } if (params.length > 3) { throw new TypeError('Invalid range'); } var name = 'range'; var fn = math.range; node = new Operator(name, fn, params); } return node; } /** * conditions like and, or, in * @param {Scope} scope * @return {Node} node * @private */ function parse_conditions (scope) { var node = parse_bitwise_conditions(scope); // TODO: precedence of And above Or? // TODO: implement a method for unit to number conversion var operators = { 'in' : 'in' /* TODO: implement conditions 'and' : 'and', '&&' : 'and', 'or': 'or', '||': 'or', 'xor': 'xor' */ }; while (operators[token] !== undefined) { // TODO: with all operators: only load one instance of the operator, use the scope var name = token; var fn = math[operators[name]]; getToken(); var params = [node, parse_bitwise_conditions(scope)]; node = new Operator(name, fn, params); } return node; } /** * conditional operators and bitshift * @param {Scope} scope * @return {Node} node * @private */ function parse_bitwise_conditions (scope) { var node = parse_comparison(scope); /* TODO: implement bitwise conditions var operators = { '&' : 'bitwiseand', '|' : 'bitwiseor', // todo: bitwise xor? '<<': 'bitshiftleft', '>>': 'bitshiftright' }; while (operators[token] !== undefined) { var name = token; var fn = math[operators[name]]; getToken(); var params = [node, parse_comparison()]; node = new Operator(name, fn, params); } */ return node; } /** * comparison operators * @param {Scope} scope * @return {Node} node * @private */ function parse_comparison (scope) { var node = parse_addsubtract(scope); var operators = { '==': 'equal', '!=': 'unequal', '<': 'smaller', '>': 'larger', '<=': 'smallereq', '>=': 'largereq' }; while (operators[token] !== undefined) { var name = token; var fn = math[operators[name]]; getToken(); var params = [node, parse_addsubtract(scope)]; node = new Operator(name, fn, params); } return node; } /** * add or subtract * @param {Scope} scope * @return {Node} node * @private */ function parse_addsubtract (scope) { var node = parse_multiplydivide(scope); var operators = { '+': 'add', '-': 'subtract' }; while (operators[token] !== undefined) { var name = token; var fn = math[operators[name]]; getToken(); var params = [node, parse_multiplydivide(scope)]; node = new Operator(name, fn, params); } return node; } /** * multiply, divide, modulus * @param {Scope} scope * @return {Node} node * @private */ function parse_multiplydivide (scope) { var node = parse_unaryminus(scope); var operators = { '*': 'multiply', '/': 'divide', '%': 'mod', 'mod': 'mod' }; while (operators[token] !== undefined) { var name = token; var fn = math[operators[name]]; getToken(); var params = [node, parse_unaryminus(scope)]; node = new Operator(name, fn, params); } return node; } /** * Unary minus * @param {Scope} scope * @return {Node} node * @private */ function parse_unaryminus (scope) { if (token == '-') { var name = token; var fn = math.unaryminus; getToken(); var params = [parse_pow(scope)]; return new Operator(name, fn, params); } return parse_pow(scope); } /** * power * Node: power operator is right associative * @param {Scope} scope * @return {Node} node * @private */ function parse_pow (scope) { var nodes = [ parse_factorial(scope) ]; // stack all operands of a chained power operator (like '2^3^3') while (token == '^') { getToken(); nodes.push(parse_factorial(scope)); } // evaluate the operands from right to left (right associative) var node = nodes.pop(); while (nodes.length) { var leftNode = nodes.pop(); var name = '^'; var fn = math.pow; var params = [leftNode, node]; node = new Operator(name, fn, params); } return node; } /** * Factorial * @param {Scope} scope * @return {Node} node * @private */ function parse_factorial (scope) { var node = parse_transpose(scope); while (token == '!') { var name = token; var fn = math.factorial; getToken(); var params = [node]; node = new Operator(name, fn, params); } return node; } /** * Transpose * @param {Scope} scope * @return {Node} node * @private */ function parse_transpose (scope) { var node = parse_plot(scope); while (token == '\'') { var name = token; var fn = math.transpose; getToken(); var params = [node]; node = new Operator(name, fn, params); } return node; } /** * parse plot * @param {Scope} scope * @return {Node} node * @private */ function parse_plot (scope) { /* TODO: implement plot if (token_type == TOKENTYPE.SYMBOL && token == 'plot') { getToken(); // parse the parentheses and parameters of the plot // the parameters are something like: plot(sin(x), cos(x), x) var functions = []; if (token == '(') { var plotScope = scope.createNestedScope(); getToken(); functions.push(parse_range(plotScope)); // parse a list with parameters while (token == ',') { getToken(); functions.push(parse_range(plotScope)); } if (token != ')') { throw createSyntaxError('Parenthesis ) missing'); } getToken(); } // check what the variable of the functions is. var variable = undefined; var lastFunction = functions[functions.length - 1]; if (lastFunction) { // if the last function is a variable, remove it from the functions list // and use its variable func var lastIsSymbol = (lastFunction instanceof Arguments); if (lastIsSymbol) { functions.pop(); variable = lastFunction.fn; } } return new plot(functions, variable, plotScope); } */ return parse_symbol(scope); } /** * parse symbols: functions, variables, constants, units * @param {Scope} scope * @return {Node} node * @private */ function parse_symbol (scope) { if (token_type == TOKENTYPE.SYMBOL) { var name = token; getToken(); // create a symbol var link = scope.createLink(name); var symbol = new Symbol(name, link); // parse parameters return parse_params(scope, symbol); } return parse_string(scope); } /** * parse parameters, enclosed in parenthesis * @param {Scope} scope * @param {Node} node Node on which to apply the parameters. If there * are no parameters in the expression, the node * itself is returned * @return {Node} node * @private */ function parse_params (scope, node) { while (token == '(') { var params = []; getToken(); if (token != ')') { params.push(parse_range(scope)); // parse a list with parameters while (token == ',') { getToken(); params.push(parse_range(scope)); } } if (token != ')') { throw createSyntaxError('Parenthesis ) missing'); } getToken(); node = new Params(node, params); } return node; } /** * parse a string. * A string is enclosed by double quotes * @param {Scope} scope * @return {Node} node * @private */ function parse_string (scope) { if (token == '"') { // string "..." var str = ''; var tPrev = ''; while (c != '' && (c != '\"' || tPrev == '\\')) { // also handle escape character str += c; tPrev = c; getChar(); } getToken(); if (token != '"') { throw createSyntaxError('End of string " missing'); } getToken(); var node = new Constant(str); /* TODO: parse arguments // parse arguments while (token == '(') { node = parse_arguments(scope, node); } */ return node; } return parse_matrix(scope); } /** * parse the matrix * @param {Scope} scope * @return {Node} A MatrixNode * @private */ function parse_matrix (scope) { if (token == '[') { // matrix [...] var array; // skip newlines getToken(); while (token == '\n') { getToken(); } // check if this is an empty matrix "[ ]" if (token != ']') { // this is a non-empty matrix var params = []; var r = 0, c = 0; params[0] = [parse_range(scope)]; // the columns in the matrix are separated by commas, and the rows by dot-comma's while (token == ',' || token == ';') { if (token == ',') { c++; } else { r++; c = 0; params[r] = []; } // skip newlines getToken(); while (token == '\n') { getToken(); } params[r][c] = parse_range(scope); // skip newlines while (token == '\n') { getToken(); } } var rows = params.length; var cols = (params.length > 0) ? params[0].length : 0; // check if the number of columns matches in all rows for (r = 1; r < rows; r++) { if (params[r].length != cols) { throw createError('Number of columns must match ' + '(' + params[r].length + ' != ' + cols + ')'); } } if (token != ']') { throw createSyntaxError('End of matrix ] missing'); } getToken(); array = new MatrixNode(params); } else { // this is an empty matrix "[ ]" getToken(); array = new MatrixNode([[]]); } /* TODO: parse arguments // parse arguments while (token == '(') { array = parse_arguments(scope, array); } */ return array; } return parse_number(scope); } /** * parse a number * @param {Scope} scope * @return {Node} node * @private */ function parse_number (scope) { if (token_type == TOKENTYPE.NUMBER) { // this is a number var number; if (token == '.') { number = 0.0; } else { number = Number(token); } getToken(); /* TODO: implicit multiplication? // TODO: how to calculate a=3; 2/2a ? is this (2/2)*a or 2/(2*a) ? // check for implicit multiplication if (token_type == TOKENTYPE.VARIABLE) { node = multiply(node, parse_pow()); } //*/ var value; if (token_type == TOKENTYPE.SYMBOL) { if (token == 'i' || token == 'I') { value = new Complex(0, number); getToken(); return new Constant(value); } if (Unit.isPlainUnit(token)) { value = new Unit(number, token); getToken(); return new Constant(value); } throw createTypeError('Unknown unit "' + token + '"'); } // just a regular number var node = new Constant(number); /* TODO: parse arguments // parse arguments while (token == '(') { node = parse_arguments(scope, node); } */ return node; } return parse_parentheses(scope); } /** * parentheses * @param {Scope} scope * @return {Node} node * @private */ function parse_parentheses (scope) { // check if it is a parenthesized expression if (token == '(') { // parentheses (...) getToken(); var node = parse_range(scope); // start again if (token != ')') { throw createSyntaxError('Parenthesis ) expected'); } getToken(); /* TODO: implicit multiplication? // TODO: how to calculate a=3; 2/2a ? is this (2/2)*a or 2/(2*a) ? // check for implicit multiplication if (token_type == TOKENTYPE.VARIABLE) { node = multiply(node, parse_pow()); } //*/ /* TODO: parse arguments // parse arguments while (token == '(') { node = parse_arguments(scope, node); } */ return node; } return parse_end(scope); } /** * Evaluated when the expression is not yet ended but expected to end * @param {Scope} scope * @return {Node} res * @private */ function parse_end (scope) { if (token == '') { // syntax error or unexpected end of expression throw createSyntaxError('Unexpected end of expression'); } else { throw createSyntaxError('Value expected'); } } /** * Shortcut for getting the current row value (one based) * Returns the line of the currently handled expression * @private */ function row () { // TODO: also register row number during parsing return undefined; } /** * Shortcut for getting the current col value (one based) * Returns the column (position) where the last token starts * @private */ function col () { return index - token.length + 1; } /** * Build up an error message * @param {String} message * @return {String} message with row and column information * @private */ function createErrorMessage (message) { var r = row(); var c = col(); if (r === undefined) { if (c === undefined) { return message; } else { return message + ' (char ' + c + ')'; } } else { return message + ' (line ' + r + ', char ' + c + ')'; } } /** * Create an error * @param {String} message * @return {SyntaxError} instantiated error * @private */ function createSyntaxError (message) { return new SyntaxError(createErrorMessage(message)); } /** * Create an error * @param {String} message * @return {TypeError} instantiated error * @private */ function createTypeError(message) { return new TypeError(createErrorMessage(message)); } /** * Create an error * @param {String} message * @return {Error} instantiated error * @private */ function createError (message) { return new Error(createErrorMessage(message)); } })(); (function () { /** * @constructor math.expr.Workspace * * Workspace manages a set of expressions. Expressions can be added, replace, * deleted, and inserted in the workspace. The workspace keeps track on the * dependencies between the expressions, and automatically updates results of * depending expressions when variables or function definitions are changed in * the workspace. * * Methods: * var id = workspace.append(expr); * var id = workspace.insertBefore(expr, beforeId); * var id = workspace.insertAfter(expr, afterId); * workspace.replace(expr, id); * workspace.remove(id); * workspace.clear(); * var expr = workspace.getExpr(id); * var result = workspace.getResult(id); * var deps = workspace.getDependencies(id); * var changes = workspace.getChanges(updateSeq); * * Usage: * var workspace = new math.expr.Workspace(); * var id0 = workspace.append('a = 3/4'); * var id1 = workspace.append('a + 2'); * console.log('a + 2 = ' + workspace.getResult(id1)); * workspace.replace('a=5/2', id0); * console.log('a + 2 = ' + workspace.getResult(id1)); */ function Workspace () { this.idMax = -1; this.updateSeq = 0; this.parser = new math.expr.Parser(); this.scope = new math.expr.Scope(); this.nodes = {}; this.firstNode = undefined; this.lastNode = undefined; } math.expr.Workspace = Workspace; /** * clear the workspace */ Workspace.prototype.clear = function () { this.nodes = {}; this.firstNode = undefined; this.lastNode = undefined; }; /** * append an expression to the workspace * @param {String} expression * @return {Number} id of the created node */ Workspace.prototype.append = function (expression) { // create the node var id = this._getNewId(); var parentScope = this.lastNode ? this.lastNode.scope : this.scope; var scope = new math.expr.Scope(parentScope); var node = new Workspace.Node({ 'id': id, 'expression': expression, 'parser': this.parser, 'scope': scope, 'nextNode': undefined, 'previousNode': this.lastNode }); this.nodes[id] = node; // link next and previous nodes if (!this.firstNode) { this.firstNode = node; } if (this.lastNode) { this.lastNode.nextNode = node; } this.lastNode = node; // update this node this._update([id]); return id; }; /** * insert an expression before an existing expression * @param {String} expression the new expression * @param {Number} beforeId id of an existing expression * @return {Number} id id of the created node */ Workspace.prototype.insertBefore = function (expression, beforeId) { var nextNode = this.nodes[beforeId]; if (!nextNode) { throw 'Node with id "' + beforeId + '" not found'; } var previousNode = nextNode.previousNode; // create the node var id = this._getNewId(); var previousScope = previousNode ? previousNode.scope : this.scope; var scope = new math.expr.Scope(previousScope); var node = new Workspace.Node({ 'id': id, 'expression': expression, 'parser': this.parser, 'scope': scope, 'nextNode': nextNode, 'previousNode': previousNode }); this.nodes[id] = node; // link next and previous nodes if (previousNode) { previousNode.nextNode = node; } else { this.firstNode = node; } nextNode.previousNode = node; // link to the new the scope nextNode.scope.parentScope = node.scope; // update this node and all dependent nodes var ids = this.getDependencies(id); if (ids.indexOf(id) == -1) { ids.unshift(id); } this._update(ids); return id; }; /** * insert an expression after an existing expression * @param {String} expression the new expression * @param {Number} afterId id of an existing expression * @return {Number} id id of the created expression */ Workspace.prototype.insertAfter = function (expression, afterId) { var previousNode = this.nodes[afterId]; if (!previousNode) { throw 'Node with id "' + afterId + '" not found'; } if (previousNode == this.lastNode) { return this.append(expression); } else { return this.insertBefore(afterId + 1, expression); } }; /** * remove an expression. If the expression is not found, no action will * be taken. * @param {Number} id id of an existing expression */ Workspace.prototype.remove = function (id) { var node = this.nodes[id]; if (!node) { throw 'Node with id "' + id + '" not found'; } // get the dependencies (needed to update them after deletion of this node) var dependentIds = this.getDependencies(id); // adjust links to previous and next nodes var previousNode = node.previousNode; var nextNode = node.nextNode; if (previousNode) { previousNode.nextNode = nextNode; } else { this.firstNode = nextNode; } if (nextNode) { nextNode.previousNode = previousNode; } else { this.lastNode = previousNode; } // re-link the scope var previousScope = previousNode ? previousNode.scope : this.scope; if (nextNode) { nextNode.scope.parentScope = previousScope; } // remove the node delete this.nodes[id]; // update all dependent nodes this._update(dependentIds); }; /** * replace an existing expression * @param {String} expression the new expression * @param {Number} id id of an existing expression */ Workspace.prototype.replace = function (expression, id) { var node = this.nodes[id]; if (!node) { throw 'Node with id "' + id + '" not found'; } // get the dependencies var dependentIds = [id]; Workspace._merge(dependentIds, this.getDependencies(id)); var previousNode = node.previousNode; var nextNode = node.nextNode; var previousScope = previousNode ? previousNode.scope : this.scope; // replace the expression node.setExpr(expression); // add the new dependencies Workspace._merge(dependentIds, this.getDependencies(id)); // update all dependencies this._update(dependentIds); }; /** * @constructor Workspace.Node * @param {Object} params Object containing parameters: * {Number} id * {String} expression An expression, for example "2+3" * {Parser} parser * {Scope} scope * {Workspace.Node} nextNode * {Workspace.Node} previousNode */ Workspace.Node = function (params) { this.id = params.id; this.parser = params.parser; this.scope = params.scope; this.nextNode = params.nextNode; this.previousNode = params.previousNode; // TODO: throw error when id, parser, or scope is not given this.updateSeq = 0; this.result = undefined; this.setExpr(params.expression); }; /** * set the node's expression * @param {String} expression */ Workspace.Node.prototype.setExpr = function (expression) { this.expression = expression || ''; this.scope.clear(); this._parse(); }; /** * get the node's expression * @return {String} expression */ Workspace.Node.prototype.getExpr = function () { return this.expression; }; /** * get the result of the nodes expression * @return {*} result */ Workspace.Node.prototype.getResult = function () { // TODO: automatically evaluate when not up to date? return this.result; }; /** * parse the node's expression * @private */ Workspace.Node.prototype._parse = function () { try { this.fn = this.parser.parse(this.expression, this.scope); } catch (err) { var value = 'Error: ' + String(err.message || err); this.fn = new Constant(value); } }; /** * Evaluate the node expression * @return {*} result */ Workspace.Node.prototype.eval = function () { try { this.scope.init(); this.result = this.fn.eval(); } catch (err) { this.scope.init(); this.result = 'Error: ' + String(err.message || err); } return this.result; }; /** * Merge array2 into array1, only adding distinct elements. * The elements are not sorted. * @param {Array} array1 * @param {Array} array2 * @private */ Workspace._merge = function (array1, array2) { for (var i = 0, iMax = array2.length; i < iMax; i++) { var elem = array2[i]; if (array1.indexOf(elem) == -1) { array1.push(elem); } } }; /** * Retrieve the id's of the nodes which are dependent on this node * @param {Number} id * @return {Number[]} id's of dependent nodes. The ids are not ordered */ Workspace.prototype.getDependencies = function (id) { var ids = [], name; var node = this.nodes[id]; if (node) { // create a list with all symbol names defined/updated in this scope var defs = node.scope.defs; var updates = node.scope.updates; var symbolNames = []; for (name in defs) { if (defs.hasOwnProperty(name)) { symbolNames.push(name); } } for (name in updates) { if (updates.hasOwnProperty(name) && symbolNames.indexOf(name) == -1) { symbolNames.push(name); } } // loop through the nodes and retrieve the ids of nodes dependent on // these values. We start at current node var n = node.nextNode; while (n && symbolNames.length) { var scope = n.scope; // loop through each of the parameters and check if the scope // contains bindings to this parameter func var i = 0; while (i < symbolNames.length) { name = symbolNames[i]; // check if this scope contains a link to the current symbol name if (scope.hasLink(name) || scope.hasUpdate(name)) { if (ids.indexOf(n.id) == -1) { ids.push(n.id); // recursively check the dependencies of this id var childIds = this.getDependencies(n.id); Workspace._merge(ids, childIds); } } // stop propagation of the current symbol name as soon as it is // redefined in one of the next scopes (not if it is updated) if (scope.hasDef(name)) { symbolNames.splice(i, 1); i--; } i++; } n = n.nextNode; } } return ids; }; /** * Retrieve an expression, the original string * @param {Number} id Id of the expression to be retrieved * @return {String} The original expression as a string */ Workspace.prototype.getExpr = function (id) { var node = this.nodes[id]; if (!node) { throw 'Node with id "' + id + '" not found'; } return node.getExpr(); }; /** * get the result of and expression * @param {Number} id * @return {*} result */ Workspace.prototype.getResult = function (id) { var node = this.nodes[id]; if (!node) { throw 'Node with id "' + id + '" not found'; } return node.getResult(); }; /** * Update the results of an expression and all dependent expressions * @param {Number[]} ids Ids of the expressions to be updated * @private */ Workspace.prototype._update = function (ids) { this.updateSeq++; var updateSeq = this.updateSeq; var nodes = this.nodes; for (var i = 0, iMax = ids.length; i < iMax; i++) { var id = ids[i]; var node = nodes[id]; if (node) { node.eval(); //console.log('eval node=' + id + ' result=' + node.result.toString()); // TODO: cleanup node.updateSeq = updateSeq; } else { // TODO: throw error? } } }; /** * Get all changes since an update sequence * @param {Number} updateSeq. Optional. if not provided, all changes are * since the creation of the workspace are returned * @return {Object} ids Object containing two parameters: * param {Number[]} ids Array containing * the ids of the changed * expressions * param {Number} updateSeq the current update * sequence */ Workspace.prototype.getChanges = function (updateSeq) { var changedIds = []; var node = this.firstNode; updateSeq = updateSeq || 0; while (node) { if (node.updateSeq > updateSeq) { changedIds.push(node.id); } node = node.nextNode; } return { 'ids': changedIds, 'updateSeq': this.updateSeq }; }; /** * Return a new, unique id for an expression * @return {Number} new id * @private */ Workspace.prototype._getNewId = function () { this.idMax++; return this.idMax; }; /** * String representation of the Workspace * @return {String} description */ Workspace.prototype.toString = function () { return JSON.stringify(this.toJSON()); }; /** * JSON representation of the Workspace * @return {Object} description */ Workspace.prototype.toJSON = function () { var json = []; var node = this.firstNode; while (node) { var desc = { 'id': node.id, 'expression': node.expression, 'dependencies': this.getDependencies(node.id) }; try { desc.result = node.getResult(); } catch (err) { desc.result = 'Error: ' + String(err.message || err); } json.push(desc); node = node.nextNode; } return json; }; })(); /** * Calculate the absolute value of a value. * * abs(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.abs = function abs(x) { if (arguments.length != 1) { throw newArgumentsError('abs', arguments.length, 1); } if (isNumber(x)) { return Math.abs(x); } if (x instanceof Complex) { return Math.sqrt(x.re * x.re + x.im * x.im); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.abs); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.abs(x.valueOf()); } throw newUnsupportedTypeError('abs', x); }; /** * Add two values * * x + y * add(x, y) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Number | Complex | Unit | String | Array | Matrix} res */ math.add = function add(x, y) { if (arguments.length != 2) { throw newArgumentsError('add', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { // number + number return x + y; } else if (y instanceof Complex) { // number + complex return new Complex( x + y.re, y.im ) } } else if (x instanceof Complex) { if (isNumber(y)) { // complex + number return new Complex( x.re + y, x.im ) } else if (y instanceof Complex) { // complex + complex return new Complex( x.re + y.re, x.im + y.im ); } } else if (x instanceof Unit) { if (y instanceof Unit) { if (!x.equalBase(y)) { throw new Error('Units do not match'); } if (x.value == null) { throw new Error('Unit on left hand side of operator + has an undefined value'); } if (y.value == null) { throw new Error('Unit on right hand side of operator + has an undefined value'); } var res = x.clone(); res.value += y.value; res.fixPrefix = false; return res; } } if (isString(x) || isString(y)) { return x + y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.add); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive value return math.add(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('add', x, y); }; /** * Round a value towards plus infinity * * ceil(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.ceil = function ceil(x) { if (arguments.length != 1) { throw newArgumentsError('ceil', arguments.length, 1); } if (isNumber(x)) { return Math.ceil(x); } if (x instanceof Complex) { return new Complex ( Math.ceil(x.re), Math.ceil(x.im) ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.ceil); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.ceil(x.valueOf()); } throw newUnsupportedTypeError('ceil', x); }; /** * Compute the cube of a value * * x .* x .* x * cube(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.cube = function cube(x) { if (arguments.length != 1) { throw newArgumentsError('cube', arguments.length, 1); } if (isNumber(x)) { return x * x * x; } if (x instanceof Complex) { return math.multiply(math.multiply(x, x), x); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.cube); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.cube(x.valueOf()); } throw newUnsupportedTypeError('cube', x); }; /** * Divide two values. * * x / y * divide(x, y) * * @param {Number | Complex | Unit | Array | Matrix} x * @param {Number | Complex} y * @return {Number | Complex | Unit | Array | Matrix} res */ math.divide = function divide(x, y) { if (arguments.length != 2) { throw newArgumentsError('divide', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { // number / number return x / y; } else if (y instanceof Complex) { // number / complex return _divideComplex(new Complex(x, 0), y); } } if (x instanceof Complex) { if (isNumber(y)) { // complex / number return _divideComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { // complex / complex return _divideComplex(x, y); } } if (x instanceof Unit) { if (isNumber(y)) { var res = x.clone(); res.value /= y; return res; } } if (x instanceof Array || x instanceof Matrix) { if (y instanceof Array || y instanceof Matrix) { // TODO: implement matrix right division using pseudo inverse // http://www.mathworks.nl/help/matlab/ref/mrdivide.html // http://www.gnu.org/software/octave/doc/interpreter/Arithmetic-Ops.html // http://stackoverflow.com/questions/12263932/how-does-gnu-octave-matrix-division-work-getting-unexpected-behaviour return math.multiply(x, math.inv(y)); } else { // matrix / scalar return util.map2(x, y, math.divide); } } if (y instanceof Array || y instanceof Matrix) { // TODO: implement matrix right division using pseudo inverse return math.multiply(x, math.inv(y)); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive value return math.divide(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('divide', x, y); }; /** * Divide two complex numbers. x / y or divide(x, y) * @param {Complex} x * @param {Complex} y * @return {Complex} res * @private */ function _divideComplex (x, y) { var den = y.re * y.re + y.im * y.im; return new Complex( (x.re * y.re + x.im * y.im) / den, (x.im * y.re - x.re * y.im) / den ); } /** * Check if value x equals y, * * x == y * equal(x, y) * * For matrices, the function is evaluated element wise. * In case of complex numbers, x.re must equal y.re, and x.im must equal y.im. * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Boolean | Array | Matrix} res */ math.equal = function equal(x, y) { if (arguments.length != 2) { throw newArgumentsError('equal', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { return x == y; } else if (y instanceof Complex) { return (x == y.re) && (y.im == 0); } } if (x instanceof Complex) { if (isNumber(y)) { return (x.re == y) && (x.im == 0); } else if (y instanceof Complex) { return (x.re == y.re) && (x.im == y.im); } } if ((x instanceof Unit) && (y instanceof Unit)) { if (!x.equalBase(y)) { throw new Error('Cannot compare units with different base'); } return x.value == y.value; } if (isString(x) || isString(y)) { return x == y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.equal); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return equal(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('equal', x, y); }; /** * Calculate the exponent of a value * * exp(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.exp = function exp (x) { if (arguments.length != 1) { throw newArgumentsError('exp', arguments.length, 1); } if (isNumber(x)) { return Math.exp(x); } if (x instanceof Complex) { var r = Math.exp(x.re); return new Complex( r * Math.cos(x.im), r * Math.sin(x.im) ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.exp); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.exp(x.valueOf()); } throw newUnsupportedTypeError('exp', x); }; /** * Round a value towards zero * * fix(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.fix = function fix(x) { if (arguments.length != 1) { throw newArgumentsError('fix', arguments.length, 1); } if (isNumber(x)) { return (x > 0) ? Math.floor(x) : Math.ceil(x); } if (x instanceof Complex) { return new Complex( (x.re > 0) ? Math.floor(x.re) : Math.ceil(x.re), (x.im > 0) ? Math.floor(x.im) : Math.ceil(x.im) ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.fix); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.fix(x.valueOf()); } throw newUnsupportedTypeError('fix', x); }; /** * Round a value towards minus infinity * * floor(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.floor = function floor(x) { if (arguments.length != 1) { throw newArgumentsError('floor', arguments.length, 1); } if (isNumber(x)) { return Math.floor(x); } if (x instanceof Complex) { return new Complex ( Math.floor(x.re), Math.floor(x.im) ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.floor); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.floor(x.valueOf()); } throw newUnsupportedTypeError('floor', x); }; /** * Calculate the greatest common divisor for two or more values or arrays. * * gcd(a, b) * gcd(a, b, c, ...) * * For matrices, the function is evaluated element wise. * * @param {... Number | Array | Matrix} args two or more integer numbers * @return {Number | Array | Matrix} greatest common divisor */ math.gcd = function gcd(args) { var a = arguments[0], b = arguments[1], t; if (arguments.length == 2) { // two arguments if (isNumber(a) && isNumber(b)) { if (!isInteger(a) || !isInteger(b)) { throw new Error('Parameters in function gcd must be integer numbers'); } // http://en.wikipedia.org/wiki/Euclidean_algorithm while (b != 0) { t = b; b = a % t; a = t; } return Math.abs(a); } // evaluate gcd element wise if (a instanceof Array || a instanceof Matrix || b instanceof Array || b instanceof Matrix) { return util.map2(a, b, math.gcd); } if (a.valueOf() !== a || b.valueOf() !== b) { // fallback on the objects primitive value return math.gcd(a.valueOf(), b.valueOf()); } throw newUnsupportedTypeError('gcd', a, b); } if (arguments.length > 2) { // multiple arguments. Evaluate them iteratively for (var i = 1; i < arguments.length; i++) { a = math.gcd(a, arguments[i]); } return a; } // zero or one argument throw new SyntaxError('Function gcd expects two or more arguments'); }; /** * Check if value x is larger y * * x > y * larger(x, y) * * For matrices, the function is evaluated element wise. * In case of complex numbers, the absolute values of a and b are compared. * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Boolean | Array | Matrix} res */ math.larger = function larger(x, y) { if (arguments.length != 2) { throw newArgumentsError('larger', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { return x > y; } else if (y instanceof Complex) { return x > math.abs(y); } } if (x instanceof Complex) { if (isNumber(y)) { return math.abs(x) > y; } else if (y instanceof Complex) { return math.abs(x) > math.abs(y); } } if ((x instanceof Unit) && (y instanceof Unit)) { if (!x.equalBase(y)) { throw new Error('Cannot compare units with different base'); } return x.value > y.value; } if (isString(x) || isString(y)) { return x > y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.larger); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.larger(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('larger', x, y); }; /** * Check if value x is larger or equal to y * * x >= y * largereq(x, y) * * For matrices, the function is evaluated element wise. * In case of complex numbers, the absolute values of a and b are compared. * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Boolean | Array | Matrix} res */ math.largereq = function largereq(x, y) { if (arguments.length != 2) { throw newArgumentsError('largereq', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { return x >= y; } else if (y instanceof Complex) { return x >= math.abs(y); } } if (x instanceof Complex) { if (isNumber(y)) { return math.abs(x) >= y; } else if (y instanceof Complex) { return math.abs(x) >= math.abs(y); } } if ((x instanceof Unit) && (y instanceof Unit)) { if (!x.equalBase(y)) { throw new Error('Cannot compare units with different base'); } return x.value >= y.value; } if (isString(x) || isString(y)) { return x >= y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.largereq); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.largereq(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('largereq', x, y); }; /** * Calculate the least common multiple for two or more values or arrays. * * lcm(a, b) * lcm(a, b, c, ...) * * lcm is defined as: * lcm(a, b) = abs(a * b) / gcd(a, b) * * For matrices, the function is evaluated element wise. * * @param {... Number | Array | Matrix} args two or more integer numbers * @return {Number | Array | Matrix} least common multiple */ math.lcm = function lcm(args) { var a = arguments[0], b = arguments[1], t; if (arguments.length == 2) { // two arguments if (isNumber(a) && isNumber(b)) { if (!isInteger(a) || !isInteger(b)) { throw new Error('Parameters in function lcm must be integer numbers'); } // http://en.wikipedia.org/wiki/Euclidean_algorithm // evaluate gcd here inline to reduce overhead var prod = a * b; while (b != 0) { t = b; b = a % t; a = t; } return Math.abs(prod / a); } // evaluate lcm element wise if (a instanceof Array || a instanceof Matrix || b instanceof Array || b instanceof Matrix) { return util.map2(a, b, math.lcm); } if (a.valueOf() !== a || b.valueOf() !== b) { // fallback on the objects primitive value return math.lcm(a.valueOf(), b.valueOf()); } throw newUnsupportedTypeError('lcm', a, b); } if (arguments.length > 2) { // multiple arguments. Evaluate them iteratively for (var i = 1; i < arguments.length; i++) { a = math.lcm(a, arguments[i]); } return a; } // zero or one argument throw new SyntaxError('Function lcm expects two or more arguments'); }; /** * Calculate the logarithm of a value * * log(x) * log(x, base) * * base is optional. If not provided, the natural logarithm of x is calculated. * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @param {Number | Complex} [base] * @return {Number | Complex | Array | Matrix} res */ math.log = function log(x, base) { if (arguments.length != 1 && arguments.length != 2) { throw newArgumentsError('log', arguments.length, 1, 2); } if (base === undefined) { // calculate natural logarithm, log(x) if (isNumber(x)) { if (x >= 0) { return Math.log(x); } else { // negative value -> complex value computation return math.log(new Complex(x, 0)); } } if (x instanceof Complex) { return new Complex ( Math.log(Math.sqrt(x.re * x.re + x.im * x.im)), Math.atan2(x.im, x.re) ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.log); } } else { // calculate logarithm for a specified base, log(x, base) return math.divide(math.log(x), math.log(base)); } if (x.valueOf() !== x || base.valueOf() !== base) { // fallback on the objects primitive values return math.log(x.valueOf(), base.valueOf()); } throw newUnsupportedTypeError('log', x, base); }; /** * Calculate the 10-base logarithm of a value * * log10(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.log10 = function log10(x) { if (arguments.length != 1) { throw newArgumentsError('log10', arguments.length, 1); } if (isNumber(x)) { if (x >= 0) { return Math.log(x) / Math.LN10; } else { // negative value -> complex value computation return math.log10(new Complex(x, 0)); } } if (x instanceof Complex) { return new Complex ( Math.log(Math.sqrt(x.re * x.re + x.im * x.im)) / Math.LN10, Math.atan2(x.im, x.re) / Math.LN10 ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.log10); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.log10(x.valueOf()); } throw newUnsupportedTypeError('log10', x); }; /** * Calculates the modulus, the remainder of an integer division. * * x % y * mod(x, y) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @param {Number | Complex | Array | Matrix} y * @return {Number | Array | Matrix} res */ math.mod = function mod(x, y) { if (arguments.length != 2) { throw newArgumentsError('mod', arguments.length, 2); } // TODO: only handle integer values in mod? if (isNumber(x)) { if (isNumber(y)) { // number % number return x % y; } else if (y instanceof Complex && y.im == 0) { // number % complex return x % y.re; } } else if (x instanceof Complex && x.im == 0) { if (isNumber(y)) { // complex * number return x.re % y; } else if (y instanceof Complex && y.im == 0) { // complex * complex return x.re % y.re; } } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.mod); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.mod(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('mod', x, y); }; /** * Multiply two values. * * x * y * multiply(x, y) * * @param {Number | Complex | Unit | Array | Matrix} x * @param {Number | Complex | Unit | Array | Matrix} y * @return {Number | Complex | Unit | Array | Matrix} res */ math.multiply = function multiply(x, y) { if (arguments.length != 2) { throw newArgumentsError('multiply', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { // number * number return x * y; } else if (y instanceof Complex) { // number * complex return _multiplyComplex (new Complex(x, 0), y); } else if (y instanceof Unit) { res = y.clone(); res.value *= x; return res; } } else if (x instanceof Complex) { if (isNumber(y)) { // complex * number return _multiplyComplex (x, new Complex(y, 0)); } else if (y instanceof Complex) { // complex * complex return _multiplyComplex (x, y); } } else if (x instanceof Unit) { if (isNumber(y)) { res = x.clone(); res.value *= y; return res; } } else if (x instanceof Array) { if (y instanceof Array) { // matrix * matrix var sizeX = util.size(x); var sizeY = util.size(y); if (sizeX.length != 2) { throw new Error('Can only multiply a 2 dimensional matrix ' + '(A has ' + sizeX.length + ' dimensions)'); } if (sizeY.length != 2) { throw new Error('Can only multiply a 2 dimensional matrix ' + '(B has ' + sizeY.length + ' dimensions)'); } if (sizeX[1] != sizeY[0]) { throw new RangeError('Dimensions mismatch in multiplication. ' + 'Columns of A must match rows of B ' + '(A is ' + sizeX[0] + 'x' + sizeX[1] + ', B is ' + sizeY[0] + 'x' + sizeY[1] + ', ' + sizeY[1] + ' != ' + sizeY[0] + ')'); } // TODO: performance of matrix multiplication can be improved var res = [], rows = sizeX[0], cols = sizeY[1], num = sizeX[1], multiply = math.multiply, add = math.add; for (var r = 0; r < rows; r++) { res[r] = []; for (var c = 0; c < cols; c++) { var result = null; for (var n = 0; n < num; n++) { var p = multiply(x[r][n], y[n][c]); result = (result == null) ? p : add(result, p); } res[r][c] = result; } } return res; } else if (y instanceof Matrix) { return new Matrix(math.multiply(x.valueOf(), y.valueOf())); } else { // matrix * scalar return util.map2(x, y, math.multiply); } } else if (x instanceof Matrix) { return new Matrix(math.multiply(x.valueOf(), y.valueOf())); } if (y instanceof Array) { // scalar * matrix return util.map2(x, y, math.multiply); } else if (y instanceof Matrix) { return new Matrix(math.multiply(x.valueOf(), y.valueOf())); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.multiply(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('multiply', x, y); }; /** * Multiply two complex numbers. x * y or multiply(x, y) * @param {Complex} x * @param {Complex} y * @return {Complex} res * @private */ function _multiplyComplex (x, y) { return new Complex( x.re * y.re - x.im * y.im, x.re * y.im + x.im * y.re ); } /** * Calculates the power of x to y * * x ^ y * pow(x, y) * * @param {Number | Complex | Array | Matrix} x * @param {Number | Complex} y * @return {Number | Complex | Array | Matrix} res */ math.pow = function pow(x, y) { if (arguments.length != 2) { throw newArgumentsError('pow', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { if (isInteger(y) || x >= 0) { // real value computation return Math.pow(x, y); } else { return powComplex(new Complex(x, 0), new Complex(y, 0)); } } else if (y instanceof Complex) { return powComplex(new Complex(x, 0), y); } } else if (x instanceof Complex) { if (isNumber(y)) { return powComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { return powComplex(x, y); } } else if (x instanceof Array) { if (!isNumber(y) || !isInteger(y) || y < 0) { throw new TypeError('For A^b, b must be a positive integer ' + '(value is ' + y + ')'); } // verify that A is a 2 dimensional square matrix var s = util.size(x); if (s.length != 2) { throw new Error('For A^b, A must be 2 dimensional ' + '(A has ' + s.length + ' dimensions)'); } if (s[0] != s[1]) { throw new Error('For A^b, A must be square ' + '(size is ' + s[0] + 'x' + s[1] + ')'); } if (y == 0) { // return the identity matrix return math.eye(s[0]); } else { // value > 0 var res = x; for (var i = 1; i < y; i++) { res = math.multiply(x, res); } return res; } } else if (x instanceof Matrix) { return new Matrix(math.pow(x.valueOf(), y)); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.pow(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('pow', x, y); }; /** * Calculates the power of x to y, x^y, for two complex numbers. * @param {Complex} x * @param {Complex} y * @return {Complex} res * @private */ function powComplex (x, y) { // complex computation // x^y = exp(log(x)*y) = exp((abs(x)+i*arg(x))*y) var temp1 = math.log(x); var temp2 = math.multiply(temp1, y); return math.exp(temp2); } /** * Round a value towards the nearest integer * * round(x) * round(x, n) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @param {Number | Array} [n] number of decimals (by default n=0) * @return {Number | Complex | Array | Matrix} res */ math.round = function round(x, n) { if (arguments.length != 1 && arguments.length != 2) { throw newArgumentsError('round', arguments.length, 1, 2); } if (n == undefined) { // round (x) if (isNumber(x)) { return Math.round(x); } if (x instanceof Complex) { return new Complex ( Math.round(x.re), Math.round(x.im) ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.round); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.round(x.valueOf()); } throw newUnsupportedTypeError('round', x); } else { // round (x, n) if (!isNumber(n)) { throw new TypeError('Number of decimals in function round must be an integer'); } if (n !== Math.round(n)) { throw new TypeError('Number of decimals in function round must be integer'); } if (n < 0 || n > 9) { throw new Error ('Number of decimals in function round must be in te range of 0-9'); } if (isNumber(x)) { return roundNumber(x, n); } if (x instanceof Complex) { return new Complex ( roundNumber(x.re, n), roundNumber(x.im, n) ); } if (x instanceof Array || x instanceof Matrix || n instanceof Array || n instanceof Matrix) { return util.map2(x, n, math.round); } if (x.valueOf() !== x || n.valueOf() !== n) { // fallback on the objects primitive values return math.round(x.valueOf(), n.valueOf()); } throw newUnsupportedTypeError('round', x, n); } }; /** * round a number to the given number of decimals, or to zero if decimals is * not provided * @param {Number} value * @param {Number} [decimals] number of decimals, between 0 and 15 (0 by default) * @return {Number} roundedValue */ function roundNumber (value, decimals) { if (decimals) { var p = Math.pow(10, decimals); return Math.round(value * p) / p; } else { return Math.round(value); } } /** * Compute the sign of a value. * * sign(x) * * The sign of a value x is 1 when x > 1, -1 when x < 0, and 0 when x == 0 * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.sign = function sign(x) { if (arguments.length != 1) { throw newArgumentsError('sign', arguments.length, 1); } if (isNumber(x)) { var sign; if (x > 0) { sign = 1; } else if (x < 0) { sign = -1; } else { sign = 0; } return sign; } if (x instanceof Complex) { var abs = Math.sqrt(x.re * x.re + x.im * x.im); return new Complex(x.re / abs, x.im / abs); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.sign); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.sign(x.valueOf()); } throw newUnsupportedTypeError('sign', x); }; /** * Check if value x is smaller y * * x < y * smaller(x, y) * * For matrices, the function is evaluated element wise. * In case of complex numbers, the absolute values of a and b are compared. * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Boolean | Array | Matrix} res */ math.smaller = function smaller(x, y) { if (arguments.length != 2) { throw newArgumentsError('smaller', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { return x < y; } else if (y instanceof Complex) { return x < math.abs(y); } } if (x instanceof Complex) { if (isNumber(y)) { return math.abs(x) < y; } else if (y instanceof Complex) { return math.abs(x) < math.abs(y); } } if ((x instanceof Unit) && (y instanceof Unit)) { if (!x.equalBase(y)) { throw new Error('Cannot compare units with different base'); } return x.value < y.value; } if (isString(x) || isString(y)) { return x < y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.smaller); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.smaller(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('smaller', x, y); }; /** * Check if value a is smaller or equal to b * * a <= b * smallereq(a, b) * * For matrices, the function is evaluated element wise. * In case of complex numbers, the absolute values of a and b are compared. * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Boolean | Array | Matrix} res */ math.smallereq = function smallereq(x, y) { if (arguments.length != 2) { throw newArgumentsError('smallereq', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { return x <= y; } else if (y instanceof Complex) { return x <= math.abs(y); } } if (x instanceof Complex) { if (isNumber(y)) { return math.abs(x) <= y; } else if (y instanceof Complex) { return math.abs(x) <= math.abs(y); } } if ((x instanceof Unit) && (y instanceof Unit)) { if (!x.equalBase(y)) { throw new Error('Cannot compare units with different base'); } return x.value <= y.value; } if (isString(x) || isString(y)) { return x <= y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.smallereq); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.smallereq(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('smallereq', x, y); }; /** * Calculate the square root of a value * * sqrt(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.sqrt = function sqrt (x) { if (arguments.length != 1) { throw newArgumentsError('sqrt', arguments.length, 1); } if (isNumber(x)) { if (x >= 0) { return Math.sqrt(x); } else { return math.sqrt(new Complex(x, 0)); } } if (x instanceof Complex) { var r = Math.sqrt(x.re * x.re + x.im * x.im); if (x.im >= 0.0) { return new Complex( 0.5 * Math.sqrt(2.0 * (r + x.re)), 0.5 * Math.sqrt(2.0 * (r - x.re)) ); } else { return new Complex( 0.5 * Math.sqrt(2.0 * (r + x.re)), -0.5 * Math.sqrt(2.0 * (r - x.re)) ); } } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.sqrt); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.sqrt(x.valueOf()); } throw newUnsupportedTypeError('sqrt', x); }; /** * Compute the square of a value * * x .* x * square(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.square = function square(x) { if (arguments.length != 1) { throw newArgumentsError('square', arguments.length, 1); } if (isNumber(x)) { return x * x; } if (x instanceof Complex) { return math.multiply(x, x); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.square); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.square(x.valueOf()); } throw newUnsupportedTypeError('square', x); }; /** * Subtract two values * * x - y * subtract(x, y) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @param {Number | Complex | Unit | Array | Matrix} y * @return {Number | Complex | Unit | Array | Matrix} res */ math.subtract = function subtract(x, y) { if (arguments.length != 2) { throw newArgumentsError('subtract', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { // number - number return x - y; } else if (y instanceof Complex) { // number - complex return new Complex ( x - y.re, y.im ); } } else if (x instanceof Complex) { if (isNumber(y)) { // complex - number return new Complex ( x.re - y, x.im ) } else if (y instanceof Complex) { // complex - complex return new Complex ( x.re - y.re, x.im - y.im ) } } else if (x instanceof Unit) { if (y instanceof Unit) { if (!x.equalBase(y)) { throw new Error('Units do not match'); } if (x.value == null) { throw new Error('Unit on left hand side of operator - has an undefined value'); } if (y.value == null) { throw new Error('Unit on right hand side of operator - has an undefined value'); } var res = x.clone(); res.value -= y.value; res.fixPrefix = false; return res; } } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.subtract); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.subtract(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('subtract', x, y); }; /** * Inverse the sign of a value. * * -x * unaryminus(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Unit | Array | Matrix} res */ math.unaryminus = function unaryminus(x) { if (arguments.length != 1) { throw newArgumentsError('unaryminus', arguments.length, 1); } if (isNumber(x)) { return -x; } else if (x instanceof Complex) { return new Complex( -x.re, -x.im ); } else if (x instanceof Unit) { var res = x.clone(); res.value = -x.value; return res; } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.unaryminus); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.unaryminus(x.valueOf()); } throw newUnsupportedTypeError('unaryminus', x); }; /** * Check if value x unequals y * * x != y * unequal(x, y) * * For matrices, the function is evaluated element wise. * In case of complex numbers, x.re must unequal y.re, and x.im must unequal y.im * * @param {Number | Complex | Unit | String | Array | Matrix} x * @param {Number | Complex | Unit | String | Array | Matrix} y * @return {Boolean | Array | Matrix} res */ math.unequal = function unequal(x, y) { if (arguments.length != 2) { throw newArgumentsError('unequal', arguments.length, 2); } if (isNumber(x)) { if (isNumber(y)) { return x == y; } else if (y instanceof Complex) { return (x == y.re) && (y.im == 0); } } if (x instanceof Complex) { if (isNumber(y)) { return (x.re == y) && (x.im == 0); } else if (y instanceof Complex) { return (x.re == y.re) && (x.im == y.im); } } if ((x instanceof Unit) && (y instanceof Unit)) { if (!x.equalBase(y)) { throw new Error('Cannot compare units with different base'); } return x.value == y.value; } if (isString(x) || isString(y)) { return x == y; } if (x instanceof Array || x instanceof Matrix || y instanceof Array || y instanceof Matrix) { return util.map2(x, y, math.unequal); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.unequal(x.valueOf(), y.valueOf()); } throw newUnsupportedTypeError('unequal', x, y); }; /** * Calculate the extended greatest common divisor for two values. * * xgcd(a, b) * * @param {Number} a An integer number * @param {Number} b An integer number * @return {Array} An array containing 3 integers [div, m, n] * where div = gcd(a, b) and a*m + b*n = div * * @see http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm */ math.xgcd = function xgcd(a, b) { if (arguments.length == 2) { // two arguments if (isNumber(a) && isNumber(b)) { if (!isInteger(a) || !isInteger(b)) { throw new Error('Parameters in function xgcd must be integer numbers'); } if(b == 0) { return [a, 1, 0]; } var tmp = xgcd(b, a % b), div = tmp[0], x = tmp[1], y = tmp[2]; return [div, y, x - y * Math.floor(a / b)]; } throw newUnsupportedTypeError('xgcd', a, b); } // zero or one argument throw new SyntaxError('Function xgcd expects two arguments'); }; /** * Compute the argument of a complex value. * If x = a + bi, the argument is computed as atan2(b, a). * * arg(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Array | Matrix} res */ math.arg = function arg(x) { if (arguments.length != 1) { throw newArgumentsError('arg', arguments.length, 1); } if (isNumber(x)) { return Math.atan2(0, x); } if (x instanceof Complex) { return Math.atan2(x.im, x.re); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.arg); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.arg(x.valueOf()); } // handle other types just as non-complex values return math.atan2(0, x); }; /** * Compute the complex conjugate of a complex value. * If x = a+bi, the complex conjugate is a-bi. * * conj(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.conj = function conj(x) { if (arguments.length != 1) { throw newArgumentsError('conj', arguments.length, 1); } if (isNumber(x)) { return x; } if (x instanceof Complex) { return new Complex(x.re, -x.im); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.conj); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.conj(x.valueOf()); } // return a clone of the value for non-complex values return clone(x); }; /** * Get the imaginary part of a complex number. * * im(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Array | Matrix} im */ math.im = function im(x) { if (arguments.length != 1) { throw newArgumentsError('im', arguments.length, 1); } if (isNumber(x)) { return 0; } if (x instanceof Complex) { return x.im; } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.im); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.im(x.valueOf()); } // return 0 for all non-complex values return 0; }; /** * Get the real part of a complex number. * * re(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Array | Matrix} re */ math.re = function re(x) { if (arguments.length != 1) { throw newArgumentsError('re', arguments.length, 1); } if (isNumber(x)) { return x; } if (x instanceof Complex) { return x.re; } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.re); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.re(x.valueOf()); } // return a clone of the value itself for all non-complex values return math.clone(x); }; /** * Create a complex value. Depending on the passed arguments, the function * will create and return a new math.type.Complex object. * * The method accepts the following arguments: * complex() creates a complex value with zero * as real and imaginary part. * complex(re : number, im : string) creates a complex value with provided * values for real and imaginary part. * complex(str : string) parses a string into a complex value. * * Example usage: * var a = math.complex(3, -4); // 3 - 4i * a.re = 5; // a = 5 - 4i * var i = a.im; // -4; * var b = math.complex('2 + 6i'); // 2 + 6i * var c = math.complex(); // 0 + 0i * var d = math.add(a, b); // 5 + 2i * * @param {*} [args] * @return {Complex} value */ math.complex = function complex(args) { switch (arguments.length) { case 0: // no parameters. Set re and im zero return new Complex(0, 0); break; case 1: // parse string into a complex number var str = arguments[0]; if (!isString(str)) { throw new TypeError( 'Two numbers or a single string expected in function complex'); } var c = Complex.parse(str); if (c) { return c; } else { throw new SyntaxError('String "' + str + '" is no valid complex number'); } break; case 2: // re and im provided return new Complex(arguments[0], arguments[1]); break; default: throw newArgumentsError('complex', arguments.length, 0, 2); } }; /** * Create a matrix. The function creates a new math.type.Matrix object. * * The method accepts the following arguments: * matrix() creates an empty matrix * matrix(data) creates a matrix with initial data. * * Example usage: * var m = matrix([[1, 2], [3, 4]); * m.size(); // [2, 2] * m.resize([3, 2], 5); * m.valueOf(); // [[1, 2], [3, 4], [5, 5]] * m.get([1, 0]) // 3 * * @param {Array | Matrix} [data] A multi dimensional array * @return {Matrix} matrix */ math.matrix = function matrix(data) { if (arguments.length > 1) { throw newArgumentsError('matrix', arguments.length, 0, 1); } return new Matrix(data); }; /** * Create a parser. The function creates a new math.expr.Parser object. * * parser() * * Example usage: * var parser = new math.parser(); * * // evaluate expressions * var a = parser.eval('sqrt(3^2 + 4^2)'); // 5 * var b = parser.eval('sqrt(-4)'); // 2i * var c = parser.eval('2 inch in cm'); // 5.08 cm * var d = parser.eval('cos(45 deg)'); // 0.7071067811865476 * * // define variables and functions * parser.eval('x = 7 / 2'); // 3.5 * parser.eval('x + 3'); // 6.5 * parser.eval('function f(x, y) = x^y'); // f(x, y) * parser.eval('f(2, 3)'); // 8 * * // get and set variables and functions * var x = parser.get('x'); // 7 * var f = parser.get('f'); // function * var g = f(3, 2); // 9 * parser.set('h', 500); * var i = parser.eval('h / 2'); // 250 * parser.set('hello', function (name) { * return 'hello, ' + name + '!'; * }); * parser.eval('hello("user")'); // "hello, user!" * * // clear defined functions and variables * parser.clear(); * * @return {math.expr.Parser} Parser */ math.parser = function parser() { return new math.expr.Parser(); }; /** * Create a range. The function creates a new math.type.Range object. * * A range works similar to an Array, with functions like * forEach and map. However, a Range object is very cheap to create compared to * a large Array with indexes, as it stores only a start, step and end value of * the range. * * The method accepts the following arguments * range(str) Create a range from a string, where the * string contains the start, optional step, * and end, separated by a colon. * range(start, end) Create a range with start and end and a * default step size of 1 * range(start, step, end) Create a range with start, step, and end. * * Example usage: * var c = math.range(2, 1, 5); // 2:1:5 * c.toArray(); // [2, 3, 4, 5] * var d = math.range(2, -1, -2); // 2:-1:-2 * d.forEach(function (value, index) { * console.log(index, value); * }); * var e = math.range('2:1:5'); // 2:1:5 * * @param {...*} args * @return {Range} range */ math.range = function range(args) { switch (arguments.length) { case 1: // parse string into a range if (!isString(args)) { throw new TypeError( 'Two or three numbers or a single string expected in function range'); } var r = Range.parse(args); if (r) { return r; } else { throw new SyntaxError('String "' + r + '" is no valid range'); } break; case 2: // range(start, end) return new Range(arguments[0], null, arguments[1]); break; case 3: // range(start, step, end) return new Range(arguments[0], arguments[1], arguments[2]); break; default: throw newArgumentsError('range', arguments.length, 2, 3); } }; /** * Create a unit. Depending on the passed arguments, the function * will create and return a new math.type.Unit object. * * The method accepts the following arguments: * unit(unit : string) * unit(value : number, unit : string * * Example usage: * var a = math.unit(5, 'cm'); // 50 mm * var b = math.unit('23 kg'); // 23 kg * var c = math.in(a, math.unit('m'); // 0.05 m * * @param {*} args * @return {Unit} value */ math.unit = function unit(args) { switch(arguments.length) { case 1: // parse a string var str = arguments[0]; if (!isString(str)) { throw new TypeError('A string or a number and string expected in function unit'); } if (Unit.isPlainUnit(str)) { return new Unit(null, str); // a pure unit } var u = Unit.parse(str); // a unit with value, like '5cm' if (u) { return u; } throw new SyntaxError('String "' + str + '" is no valid unit'); break; case 2: // a number and a unit return new Unit(arguments[0], arguments[1]); break; default: throw newArgumentsError('unit', arguments.length, 1, 2); } }; /** * Create a workspace. The function creates a new math.expr.Workspace object. * * workspace() * * Workspace manages a set of expressions. Expressions can be added, replace, * deleted, and inserted in the workspace. The workspace keeps track on the * dependencies between the expressions, and automatically updates results of * depending expressions when variables or function definitions are changed in * the workspace. * * Methods: * var id = workspace.append(expr); * var id = workspace.insertBefore(expr, beforeId); * var id = workspace.insertAfter(expr, afterId); * workspace.replace(expr, id); * workspace.remove(id); * workspace.clear(); * var expr = workspace.getExpr(id); * var result = workspace.getResult(id); * var deps = workspace.getDependencies(id); * var changes = workspace.getChanges(updateSeq); * * Usage: * var workspace = new math.workspace(); * var id0 = workspace.append('a = 3/4'); * var id1 = workspace.append('a + 2'); * console.log('a + 2 = ' + workspace.getResult(id1)); * workspace.replace('a=5/2', id0); * console.log('a + 2 = ' + workspace.getResult(id1)); * * @return {math.expr.Workspace} Workspace */ math.workspace = function workspace() { return new math.expr.Workspace(); }; /** * Concatenate two or more matrices * Usage: * math.concat(A, B, C, ...) * math.concat(A, B, C, ..., dim) * * Where the optional dim is the one-based number of the dimension to be * concatenated. * * @param {... Array | Matrix} args * @return {Array | Matrix} res */ math.concat = function concat (args) { var i, len = arguments.length, dim = -1, // one-based dimension prevDim, asMatrix = false, matrices = []; // contains multi dimensional arrays for (i = 0; i < len; i++) { var arg = arguments[i]; // test whether we need to return a Matrix (if not we return an Array) if (arg instanceof Matrix) { asMatrix = true; } if ((i == len - 1) && isNumber(arg)) { // last argument contains the dimension on which to concatenate prevDim = dim; dim = arg; if (!isInteger(dim) || dim < 1) { throw new TypeError('Dimension number must be a positive integer ' + '(dim = ' + dim + ')'); } if (i > 0 && dim > prevDim) { throw new RangeError('Dimension out of range ' + '(' + dim + ' > ' + prevDim + ')'); } } else if (arg instanceof Array || arg instanceof Matrix) { // this is a matrix or array var matrix = math.clone(arg.valueOf()); var size = math.size(arg); matrices[i] = matrix; prevDim = dim; dim = size.length; // verify whether each of the matrices has the same number of dimensions if (i > 0 && dim != prevDim) { throw new RangeError('Dimension mismatch ' + '(' + prevDim + ' != ' + dim + ')'); } } else { throw newUnsupportedTypeError('concat', arg); } } if (matrices.length == 0) { throw new SyntaxError('At least one matrix expected'); } var res = matrices.shift(); while (matrices.length) { res = _concat(res, matrices.shift(), dim - 1, 0); } return asMatrix ? new Matrix(res) : res; }; /** * Recursively concatenate two matrices. * The contents of the matrices is not cloned. * @param {Array} a Multi dimensional array * @param {Array} b Multi dimensional array * @param {Number} concatDim The dimension on which to concatenate (zero-based) * @param {Number} dim The current dim (zero-based) * @return {Array} c The concatenated matrix * @private */ function _concat(a, b, concatDim, dim) { if (dim < concatDim) { // recurse into next dimension if (a.length != b.length) { throw new Error('Dimensions mismatch (' + a.length + ' != ' + b.length + ')'); } var c = []; for (var i = 0; i < a.length; i++) { c[i] = _concat(a[i], b[i], concatDim, dim + 1); } return c; } else { // concatenate this dimension return a.concat(b); } } /** * @constructor det * Calculate the determinant of a matrix * * det(x) * * @param {Array | Matrix} x * @return {Number} determinant */ math.det = function det (x) { if (arguments.length != 1) { throw newArgumentsError('det', arguments.length, 1); } var size = math.size(x); switch (size.length) { case 0: // scalar return math.clone(x); break; case 1: // vector if (size[0] == 1) { return math.clone(x.valueOf()[0]); } else { throw new RangeError('Matrix must be square ' + '(size: ' + math.format(size) + ')'); } break; case 2: // two dimensional array var rows = size[0]; var cols = size[1]; if (rows == cols) { return _det(x.valueOf(), rows, cols); } else { throw new RangeError('Matrix must be square ' + '(size: ' + math.format(size) + ')'); } break; default: // multi dimensional array throw new RangeError('Matrix must be two dimensional ' + '(size: ' + math.format(size) + ')'); } }; /** * Calculate the determinant of a matrix * @param {Array[]} matrix A square, two dimensional matrix * @param {Number} rows Number of rows of the matrix (zero-based) * @param {Number} cols Number of columns of the matrix (zero-based) * @returns {Number} det * @private */ function _det (matrix, rows, cols) { var multiply = math.multiply, subtract = math.subtract; // this is a square matrix if (rows == 1) { // this is a 1 x 1 matrix return matrix[0][0]; } else if (rows == 2) { // this is a 2 x 2 matrix // the determinant of [a11,a12;a21,a22] is det = a11*a22-a21*a12 return subtract( multiply(matrix[0][0], matrix[1][1]), multiply(matrix[1][0], matrix[0][1]) ); } else { // this is a matrix of 3 x 3 or larger var d = 0; for (var c = 0; c < cols; c++) { var minor = _minor(matrix, rows, cols, 0, c); //d += Math.pow(-1, 1 + c) * a(1, c) * _det(minor); d += multiply( multiply((c + 1) % 2 + (c + 1) % 2 - 1, matrix[0][c]), _det(minor, rows - 1, cols - 1) ); // faster than with pow() } return d; } } /** * Extract a minor from a matrix * @param {Array[]} matrix A square, two dimensional matrix * @param {Number} rows Number of rows of the matrix (zero-based) * @param {Number} cols Number of columns of the matrix (zero-based) * @param {Number} row Row number to be removed (zero-based) * @param {Number} col Column number to be removed (zero-based) * @private */ function _minor(matrix, rows, cols, row, col) { var minor = [], minorRow; for (var r = 0; r < rows; r++) { if (r != row) { minorRow = minor[r - (r > row)] = []; for (var c = 0; c < cols; c++) { if (c != col) { minorRow[c - (c > col)] = matrix[r][c]; } } } } return minor; } /** * Create a diagonal matrix or retrieve the diagonal of a matrix * * diag(v) * diag(v, k) * diag(X) * diag(X, k) * * TODO: more documentation on diag * * @param {Number | Matrix | Array} x * @param {Number} [k] * @return {Matrix} matrix */ math.diag = function diag (x, k) { var data, vector, i, iMax; if (arguments.length != 1 && arguments.length != 2) { throw newArgumentsError('diag', arguments.length, 1, 2); } if (k) { if (!isNumber(k) || !isInteger(k)) { throw new TypeError ('Second parameter in function diag must be an integer'); } } else { k = 0; } var kSuper = k > 0 ? k : 0; var kSub = k < 0 ? -k : 0; // convert to matrix if (!(x instanceof Matrix) && !(x instanceof Range)) { x = new Matrix(x); } // get as array when the matrix is a vector var s; if (x.isVector()) { x = x.toVector(); s = [x.length]; } else { s = x.size(); } switch (s.length) { case 1: // x is a vector. create diagonal matrix vector = x.valueOf(); var matrix = new Matrix(); matrix.resize([vector.length + kSub, vector.length + kSuper]); data = matrix.valueOf(); iMax = vector.length; for (i = 0; i < iMax; i++) { data[i + kSub][i + kSuper] = math.clone(vector[i]); } return matrix; break; case 2: // x is a matrix get diagonal from matrix vector = []; data = x.valueOf(); iMax = Math.min(s[0] - kSub, s[1] - kSuper); for (i = 0; i < iMax; i++) { vector[i] = math.clone(data[i + kSub][i + kSuper]); } return new Matrix(vector); break; default: throw new RangeError('Matrix for function diag must be 2 dimensional'); } }; /** * Create an identity matrix with size m x n * * eye(m) * eye(m, n) * * TODO: more documentation on eye * * @param {...Number | Matrix | Array} size * @return {Matrix} matrix */ math.eye = function eye (size) { var args = util.argsToArray(arguments); if (args.length == 0) { args = [1, 1]; } else if (args.length == 1) { args[1] = args[0]; } else if (args.length > 2) { throw newArgumentsError('eye', args.length, 0, 2); } var rows = args[0], cols = args[1]; if (!isNumber(rows) || !isInteger(rows) || rows < 1) { throw new Error('Parameters in function eye must be positive integers'); } if (cols) { if (!isNumber(cols) || !isInteger(cols) || cols < 1) { throw new Error('Parameters in function eye must be positive integers'); } } // create and args the matrix var matrix = new Matrix(); matrix.resize(args); // fill in ones on the diagonal var min = math.min(args); var data = matrix.valueOf(); for (var d = 0; d < min; d++) { data[d][d] = 1; } return matrix; }; /** * Calculate the inverse of a matrix * * inv(x) * * TODO: more documentation on inv * * @param {Array | Matrix} x * @return {Array | Matrix} inv */ math.inv = function inv (x) { if (arguments.length != 1) { throw newArgumentsError('inv', arguments.length, 1); } var size = math.size(x); switch (size.length) { case 0: // scalar return math.divide(1, x); break; case 1: // vector if (size[0] == 1) { if (x instanceof Matrix) { return new Matrix([ math.divide(1, x.valueOf()[0]) ]); } else { return [ math.divide(1, x[0]) ]; } } else { throw new RangeError('Matrix must be square ' + '(size: ' + math.format(size) + ')'); } break; case 2: // two dimensional array var rows = size[0]; var cols = size[1]; if (rows == cols) { if (x instanceof Matrix) { return new Matrix( _inv(x.valueOf(), rows, cols) ); } else { // return an Array return _inv(x, rows, cols); } } else { throw new RangeError('Matrix must be square ' + '(size: ' + math.format(size) + ')'); } break; default: // multi dimensional array throw new RangeError('Matrix must be two dimensional ' + '(size: ' + math.format(size) + ')'); } }; /** * Calculate the inverse of a square matrix * @param {Array[]} matrix A square matrix * @param {Number} rows Number of rows * @param {Number} cols Number of columns, must equal rows * @return {Array[]} inv Inverse matrix * @private */ function _inv (matrix, rows, cols){ var r, s, f, value, temp, add = math.add, unaryminus = math.unaryminus, multiply = math.multiply, divide = math.divide; if (rows == 1) { // this is a 1 x 1 matrix value = matrix[0][0]; if (value == 0) { throw Error('Cannot calculate inverse, determinant is zero'); } return [[ divide(1, value) ]]; } else if (rows == 2) { // this is a 2 x 2 matrix var det = math.det(matrix); if (det == 0) { throw Error('Cannot calculate inverse, determinant is zero'); } return [ [ divide(matrix[1][1], det), divide(unaryminus(matrix[0][1]), det) ], [ divide(unaryminus(matrix[1][0]), det), divide(matrix[0][0], det) ] ]; } else { // this is a matrix of 3 x 3 or larger // calculate inverse using gauss-jordan elimination // http://en.wikipedia.org/wiki/Gaussian_elimination // http://mathworld.wolfram.com/MatrixInverse.html // http://math.uww.edu/~mcfarlat/inverse.htm // make a copy of the matrix (only the arrays, not of the elements) var A = matrix.concat(); for (r = 0; r < rows; r++) { A[r] = A[r].concat(); } // create an identity matrix which in the end will contain the // matrix inverse var B = math.eye(rows).valueOf(); // loop over all columns, and perform row reductions for (var c = 0; c < cols; c++) { // element Acc should be non zero. if not, swap content // with one of the lower rows r = c; while (r < rows && A[r][c] == 0) { r++; } if (r == rows || A[r][c] == 0) { throw Error('Cannot calculate inverse, determinant is zero'); } if (r != c) { temp = A[c]; A[c] = A[r]; A[r] = temp; temp = B[c]; B[c] = B[r]; B[r] = temp; } // eliminate non-zero values on the other rows at column c var Ac = A[c], Bc = B[c]; for (r = 0; r < rows; r++) { var Ar = A[r], Br = B[r]; if(r != c) { // eliminate value at column c and row r if (Ar[c] != 0) { f = divide(unaryminus(Ar[c]), Ac[c]); // add (f * row c) to row r to eliminate the value // at column c for (s = c; s < cols; s++) { Ar[s] = add(Ar[s], multiply(f, Ac[s])); } for (s = 0; s < cols; s++) { Br[s] = add(Br[s], multiply(f, Bc[s])); } } } else { // normalize value at Acc to 1, // divide each value on row r with the value at Acc f = Ac[c]; for (s = c; s < cols; s++) { Ar[s] = divide(Ar[s], f); } for (s = 0; s < cols; s++) { Br[s] = divide(Br[s], f); } } } } return B; } } /** * Create a matrix filled with ones * * ones(n) * ones(m, n) * ones([m, n]) * ones([m, n, p, ...]) * * @param {...Number | Array} size * @return {Matrix} matrix */ math.ones = function ones (size) { var args = util.argsToArray(arguments); if (args.length == 0) { args = [1, 1]; } else if (args.length == 1) { args[1] = args[0]; } // create and size the matrix var matrix = new Matrix(); var defaultValue = 1; matrix.resize(args, defaultValue); return matrix; }; /** * Calculate the size of a matrix or scalar * * size(x) * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.size = function size (x) { if (arguments.length != 1) { throw newArgumentsError('size', arguments.length, 1); } if (isNumber(x) || x instanceof Complex || x instanceof Unit || x == null) { return []; } if (isString(x)) { return [x.length]; } if (x instanceof Array) { return util.size(x); } if (x instanceof Matrix) { return x.size(); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.size(x.valueOf()); } throw newUnsupportedTypeError('size', x); }; /** * Remove singleton dimensions from a matrix * * squeeze(x) * * @param {Matrix | Array} x * @return {Matrix | Array} res */ math.squeeze = function squeeze (x) { if (arguments.length != 1) { throw newArgumentsError('squeeze', arguments.length, 1); } if (x instanceof Array) { return _squeezeArray(math.clone(x)); } else if (x instanceof Matrix) { return _squeezeArray(x.toArray()); } else if (x.valueOf() instanceof Array) { return _squeezeArray(math.clone(x.valueOf())); } else { // scalar return math.clone(x); } }; /** * Recursively squeeze a multi dimensional array * @param {Array} array * @return {Array} array * @private */ function _squeezeArray(array) { if (array.length == 1) { // squeeze this array return _squeezeArray(array[0]); } else { // process all childs for (var i = 0, len = array.length; i < len; i++) { var child = array[i]; if (child instanceof Array) { array[i] = _squeezeArray(child); } } return array; } } /** * Create the transpose of a matrix * * transpose(x) * * @param {Array | Matrix} x * @return {Array | Matrix} transpose */ math.transpose = function transpose (x) { if (arguments.length != 1) { throw newArgumentsError('transpose', arguments.length, 1); } var size = math.size(x); switch (size.length) { case 0: // scalar return math.clone(x); break; case 1: // vector // TODO: is it logic to return a 1 dimensional vector itself as transpose? return math.clone(x); break; case 2: // two dimensional array var rows = size[1], // index 1 is no error cols = size[0], // index 0 is no error asMatrix = x instanceof Matrix, array = x.valueOf(), transposed = [], transposedRow, clone = math.clone; for (var r = 0; r < rows; r++) { transposedRow = transposed[r] = []; for (var c = 0; c < cols; c++) { transposedRow[c] = clone(array[c][r]); } } if (cols == 0) { transposed[0] = []; } return asMatrix ? new Matrix(transposed) : transposed; break; default: // multi dimensional array throw new RangeError('Matrix must be two dimensional ' + '(size: ' + math.format(size) + ')'); } }; /** * create a matrix filled with zeros * * zeros(n) * zeros(m, n) * zeros([m, n]) * zeros([m, n, p, ...]) * * @param {...Number | Array} size * @return {Matrix} matrix */ math.zeros = function zeros (size) { var args = util.argsToArray(arguments); if (args.length == 0) { args = [1, 1]; } else if (args.length == 1) { args[1] = args[0]; } // create and size the matrix var matrix = new Matrix(); matrix.resize(args); return matrix; }; /** * Compute the factorial of a value * * x! * factorial(x) * * Factorial only supports an integer value as argument. * For matrices, the function is evaluated element wise. * * @Param {Number | Array | Matrix} x * @return {Number | Array | Matrix} res */ math.factorial = function factorial (x) { if (arguments.length != 1) { throw newArgumentsError('factorial', arguments.length, 1); } if (isNumber(x)) { if (!isInteger(x) || x < 0) { throw new TypeError('Positive integer value expected in function factorial'); } var value = x, res = value; value--; while (value > 1) { res *= value; value--; } if (res == 0) { res = 1; // 0! is per definition 1 } return res; } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.factorial); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.factorial(x.valueOf()); } throw newUnsupportedTypeError('factorial', x); }; /** * Return a random number between 0 and 1 * * random() * * @return {Number} res */ math.random = function random () { if (arguments.length != 0) { throw newArgumentsError('random', arguments.length, 0); } // TODO: implement parameter min and max return Math.random(); }; /** * Compute the maximum value of a list of values * * max(a, b, c, ...) * max([a, b, c, ...]) * * @param {... *} args A single matrix or or multiple scalar values * @return {*} res */ math.max = function max(args) { if (arguments.length == 0) { throw new Error('Function max requires one or more parameters (0 provided)'); } if (args instanceof Array || args instanceof Matrix || args instanceof Range) { // max([a, b, c, d, ...]]) if (arguments.length > 1) { throw Error('Wrong number of parameters (1 matrix or multiple scalars expected)'); } var size = math.size(args); if (size.length == 1) { // vector if (args.length == 0) { throw new Error('Cannot calculate max of an empty vector'); } return _max(args.valueOf()); } else if (size.length == 2) { // 2 dimensional matrix if (size[0] == 0 || size[1] == 0) { throw new Error('Cannot calculate max of an empty matrix'); } if (args instanceof Array) { return _max2(args, size[0], size[1]); } else if (args instanceof Matrix || args instanceof Range) { return new Matrix(_max2(args.valueOf(), size[0], size[1])); } else { throw newUnsupportedTypeError('max', args); } } else { // TODO: implement max for n-dimensional matrices throw new RangeError('Cannot calculate max for multi dimensional matrix'); } } else { // max(a, b, c, d, ...) return _max(arguments); } }; /** * Calculate the max of a one dimensional array * @param {Array} array * @return {Number} max * @private */ function _max(array) { var larger = math.larger; var res = array[0]; for (var i = 1, iMax = array.length; i < iMax; i++) { var value = array[i]; if (larger(value, res)) { res = value; } } return res; } /** * Calculate the max of a two dimensional array * @param {Array} array * @param {Number} rows * @param {Number} cols * @return {Number[]} max * @private */ function _max2(array, rows, cols) { var larger = math.larger; var res = []; for (var c = 0; c < cols; c++) { var max = array[0][c]; for (var r = 1; r < rows; r++) { var value = array[r][c]; if (larger(value, max)) { max = value; } } res[c] = max; } return res; } /** * Compute the minimum value of a list of values * * min(a, b, c, ...) * min([a, b, c, ...]) * * @param {... *} args A single matrix or multiple scalars * @return {*} res */ math.min = function min(args) { if (arguments.length == 0) { throw new Error('Function min requires one or more parameters (0 provided)'); } if (args instanceof Array || args instanceof Matrix || args instanceof Range) { // min([a, b, c, d, ...]]) if (arguments.length > 1) { throw Error('Wrong number of parameters (1 matrix or multiple scalars expected)'); } var size = math.size(args); if (size.length == 1) { // vector if (args.length == 0) { throw new Error('Cannot calculate min of an empty vector'); } return _min(args.valueOf()); } else if (size.length == 2) { // 2 dimensional matrix if (size[0] == 0 || size[1] == 0) { throw new Error('Cannot calculate min of an empty matrix'); } if (args instanceof Array) { return _min2(args, size[0], size[1]); } else if (args instanceof Matrix || args instanceof Range) { return new Matrix(_min2(args.valueOf(), size[0], size[1])); } else { throw newUnsupportedTypeError('min', args); } } else { // TODO: implement min for n-dimensional matrices throw new RangeError('Cannot calculate min for multi dimensional matrix'); } } else { // min(a, b, c, d, ...) return _min(arguments); } }; /** * Calculate the min of a one dimensional array * @param {Array} array * @return {Number} min * @private */ function _min(array) { var smaller = math.smaller; var res = array[0]; for (var i = 1, iMax = array.length; i < iMax; i++) { var value = array[i]; if (smaller(value, res)) { res = value; } } return res; } /** * Calculate the min of a two dimensional array * @param {Array} array * @param {Number} rows * @param {Number} cols * @return {Number[]} min * @private */ function _min2(array, rows, cols) { var smaller = math.smaller; var res = []; for (var c = 0; c < cols; c++) { var min = array[0][c]; for (var r = 1; r < rows; r++) { var value = array[r][c]; if (smaller(value, min)) { min = value; } } res[c] = min; } return res; } /** * Calculate the inverse cosine of a value * * acos(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/InverseCosine.html */ math.acos = function acos(x) { if (arguments.length != 1) { throw newArgumentsError('acos', arguments.length, 1); } if (isNumber(x)) { if (x >= -1 && x <= 1) { return Math.acos(x); } else { return math.acos(new Complex(x, 0)); } } if (x instanceof Complex) { // acos(z) = 0.5*pi + i*log(iz + sqrt(1-z^2)) var temp1 = new Complex( x.im * x.im - x.re * x.re + 1.0, -2.0 * x.re * x.im ); var temp2 = math.sqrt(temp1); var temp3 = new Complex( temp2.re - x.im, temp2.im + x.re ); var temp4 = math.log(temp3); // 0.5*pi = 1.5707963267948966192313216916398 return new Complex( 1.57079632679489661923 - temp4.im, temp4.re ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.acos); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.acos(x.valueOf()); } throw newUnsupportedTypeError('acos', x); }; /** * Calculate the inverse sine of a value * * asin(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/InverseSine.html */ math.asin = function asin(x) { if (arguments.length != 1) { throw newArgumentsError('asin', arguments.length, 1); } if (isNumber(x)) { if (x >= -1 && x <= 1) { return Math.asin(x); } else { return math.asin(new Complex(x, 0)); } } if (x instanceof Complex) { // asin(z) = -i*log(iz + sqrt(1-z^2)) var re = x.re; var im = x.im; var temp1 = new Complex( im * im - re * re + 1.0, -2.0 * re * im ); var temp2 = math.sqrt(temp1); var temp3 = new Complex( temp2.re - im, temp2.im + re ); var temp4 = math.log(temp3); return new Complex(temp4.im, -temp4.re); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.asin); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.asin(x.valueOf()); } throw newUnsupportedTypeError('asin', x); }; /** * Calculate the inverse tangent of a value * * atan(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/InverseTangent.html */ math.atan = function atan(x) { if (arguments.length != 1) { throw newArgumentsError('atan', arguments.length, 1); } if (isNumber(x)) { return Math.atan(x); } if (x instanceof Complex) { // atan(z) = 1/2 * i * (ln(1-iz) - ln(1+iz)) var re = x.re; var im = x.im; var den = re * re + (1.0 - im) * (1.0 - im); var temp1 = new Complex( (1.0 - im * im - re * re) / den, (-2.0 * re) / den ); var temp2 = math.log(temp1); return new Complex( -0.5 * temp2.im, 0.5 * temp2.re ); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.atan); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.atan(x.valueOf()); } throw newUnsupportedTypeError('atan', x); }; /** * Computes the principal value of the arc tangent of y/x in radians * * atan2(y, x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Array | Matrix} y * @param {Number | Complex | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/InverseTangent.html */ math.atan2 = function atan2(y, x) { if (arguments.length != 2) { throw newArgumentsError('atan2', arguments.length, 2); } if (isNumber(y)) { if (isNumber(x)) { return Math.atan2(y, x); } /* TODO: support for complex computation of atan2 else if (x instanceof Complex) { return Math.atan2(y.re, x.re); } */ } else if (y instanceof Complex) { if (isNumber(x)) { return Math.atan2(y.re, x); } /* TODO: support for complex computation of atan2 else if (x instanceof Complex) { return Math.atan2(y.re, x.re); } */ } if (y instanceof Array || y instanceof Matrix || x instanceof Array || x instanceof Matrix) { return util.map2(y, x, math.atan2); } if (x.valueOf() !== x || y.valueOf() !== y) { // fallback on the objects primitive values return math.atan2(y.valueOf(), x.valueOf()); } throw newUnsupportedTypeError('atan2', y, x); }; /** * Calculate the cosine of a value * * cos(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/Cosine.html */ math.cos = function cos(x) { if (arguments.length != 1) { throw newArgumentsError('cos', arguments.length, 1); } if (isNumber(x)) { return Math.cos(x); } if (x instanceof Complex) { // cos(z) = (exp(iz) + exp(-iz)) / 2 return new Complex( 0.5 * Math.cos(x.re) * (Math.exp(-x.im) + Math.exp(x.im)), 0.5 * Math.sin(x.re) * (Math.exp(-x.im) - Math.exp(x.im)) ); } if (x instanceof Unit) { if (!x.hasBase(Unit.BASE_UNITS.ANGLE)) { throw new TypeError ('Unit in function cos is no angle'); } return Math.cos(x.value); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.cos); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.cos(x.valueOf()); } throw newUnsupportedTypeError('cos', x); }; /** * Calculate the cotangent of a value. cot(x) is defined as 1 / tan(x) * * cot(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.cot = function cot(x) { if (arguments.length != 1) { throw newArgumentsError('cot', arguments.length, 1); } if (isNumber(x)) { return 1 / Math.tan(x); } if (x instanceof Complex) { var den = Math.exp(-4.0 * x.im) - 2.0 * Math.exp(-2.0 * x.im) * Math.cos(2.0 * x.re) + 1.0; return new Complex( 2.0 * Math.exp(-2.0 * x.im) * Math.sin(2.0 * x.re) / den, (Math.exp(-4.0 * x.im) - 1.0) / den ); } if (x instanceof Unit) { if (!x.hasBase(Unit.BASE_UNITS.ANGLE)) { throw new TypeError ('Unit in function cot is no angle'); } return 1 / Math.tan(x.value); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.cot); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.cot(x.valueOf()); } throw newUnsupportedTypeError('cot', x); }; /** * Calculate the cosecant of a value, csc(x) = 1/sin(x) * * csc(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.csc = function csc(x) { if (arguments.length != 1) { throw newArgumentsError('csc', arguments.length, 1); } if (isNumber(x)) { return 1 / Math.sin(x); } if (x instanceof Complex) { // csc(z) = 1/sin(z) = (2i) / (exp(iz) - exp(-iz)) var den = 0.25 * (Math.exp(-2.0 * x.im) + Math.exp(2.0 * x.im)) - 0.5 * Math.cos(2.0 * x.re); return new Complex ( 0.5 * Math.sin(x.re) * (Math.exp(-x.im) + Math.exp(x.im)) / den, 0.5 * Math.cos(x.re) * (Math.exp(-x.im) - Math.exp(x.im)) / den ); } if (x instanceof Unit) { if (!x.hasBase(Unit.BASE_UNITS.ANGLE)) { throw new TypeError ('Unit in function csc is no angle'); } return 1 / Math.sin(x.value); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.csc); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.csc(x.valueOf()); } throw newUnsupportedTypeError('csc', x); }; /** * Calculate the secant of a value, sec(x) = 1/cos(x) * * sec(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res */ math.sec = function sec(x) { if (arguments.length != 1) { throw newArgumentsError('sec', arguments.length, 1); } if (isNumber(x)) { return 1 / Math.cos(x); } if (x instanceof Complex) { // sec(z) = 1/cos(z) = 2 / (exp(iz) + exp(-iz)) var den = 0.25 * (Math.exp(-2.0 * x.im) + Math.exp(2.0 * x.im)) + 0.5 * Math.cos(2.0 * x.re); return new Complex( 0.5 * Math.cos(x.re) * (Math.exp(-x.im) + Math.exp( x.im)) / den, 0.5 * Math.sin(x.re) * (Math.exp( x.im) - Math.exp(-x.im)) / den ); } if (x instanceof Unit) { if (!x.hasBase(Unit.BASE_UNITS.ANGLE)) { throw new TypeError ('Unit in function sec is no angle'); } return 1 / Math.cos(x.value); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.sec); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.sec(x.valueOf()); } throw newUnsupportedTypeError('sec', x); }; /** * Calculate the sine of a value * * sin(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/Sine.html */ math.sin = function sin(x) { if (arguments.length != 1) { throw newArgumentsError('sin', arguments.length, 1); } if (isNumber(x)) { return Math.sin(x); } if (x instanceof Complex) { return new Complex( 0.5 * Math.sin(x.re) * (Math.exp(-x.im) + Math.exp( x.im)), 0.5 * Math.cos(x.re) * (Math.exp( x.im) - Math.exp(-x.im)) ); } if (x instanceof Unit) { if (!x.hasBase(Unit.BASE_UNITS.ANGLE)) { throw new TypeError ('Unit in function cos is no angle'); } return Math.sin(x.value); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.sin); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.sin(x.valueOf()); } throw newUnsupportedTypeError('sin', x); }; /** * Calculate the tangent of a value * * tan(x) * * For matrices, the function is evaluated element wise. * * @param {Number | Complex | Unit | Array | Matrix} x * @return {Number | Complex | Array | Matrix} res * * @see http://mathworld.wolfram.com/Tangent.html */ math.tan = function tan(x) { if (arguments.length != 1) { throw newArgumentsError('tan', arguments.length, 1); } if (isNumber(x)) { return Math.tan(x); } if (x instanceof Complex) { var den = Math.exp(-4.0 * x.im) + 2.0 * Math.exp(-2.0 * x.im) * Math.cos(2.0 * x.re) + 1.0; return new Complex( 2.0 * Math.exp(-2.0 * x.im) * Math.sin(2.0 * x.re) / den, (1.0 - Math.exp(-4.0 * x.im)) / den ); } if (x instanceof Unit) { if (!x.hasBase(Unit.BASE_UNITS.ANGLE)) { throw new TypeError ('Unit in function tan is no angle'); } return Math.tan(x.value); } if (x instanceof Array || x instanceof Matrix) { return util.map(x, math.tan); } if (x.valueOf() !== x) { // fallback on the objects primitive value return math.tan(x.valueOf()); } throw newUnsupportedTypeError('tan', x); }; /** * Change the unit of a value. * * x in unit * in(x, unit) * * For matrices, the function is evaluated element wise. * * @param {Unit | Array | Matrix} x * @param {Unit | Array | Matrix} unit * @return {Unit | Array | Matrix} res */ math['in'] = function unit_in(x, unit) { if (arguments.length != 2) { throw newArgumentsError('in', arguments.length, 2); } if (x instanceof Unit) { if (unit instanceof Unit || isString(unit)) { return x['in'](unit); } } if (x instanceof Array || x instanceof Matrix || unit instanceof Array || unit instanceof Matrix) { return util.map2(x, unit, math['in']); } if (x.valueOf() !== x || unit.valueOf() !== unit) { // fallback on the objects primitive value return math['in'](x.valueOf(), unit.valueOf()); } throw newUnsupportedTypeError('in', x, unit); }; /** * Clone an object * * clone(x) * * @param {*} x * @return {*} clone */ math.clone = function clone(x) { if (arguments.length != 1) { throw newArgumentsError('clone', arguments.length, 1); } if (x == null) { // null or undefined return x; } if (typeof(x.clone) === 'function') { return x.clone(); } if (isNumber(x) || isString(x) || isBoolean(x)) { return x; } if (x instanceof Array) { var c = math.clone; return x.map(function (value) { return c(value); }); } if (x instanceof Object) { return util.mapObject(x, math.clone); } throw newUnsupportedTypeError('clone', x); }; /** * Evaluate an expression. The expression will be evaluated using a read-only * instance of a Parser (i.e. variable definitions are not supported). * * eval(expr) * eval([expr1, expr2, expr3, ...]) * * @param {String | Array | Matrix} expr * @return {*} res */ math.eval = function eval(expr) { if (arguments.length != 1) { throw newArgumentsError('eval', arguments.length, 1); } if (isString(expr)) { return _readonlyParser.eval(expr); } if (expr instanceof Array || expr instanceof Matrix) { return util.map(expr, math.eval); } throw new TypeError('String or matrix expected'); }; /** @private */ var _readonlyParser = new math.expr.Parser({ readonly: true }); /** * Format a value of any type into a string. Interpolate values into the string. * Numbers are rounded off to a maximum number of 5 digits by default. * Usage: * math.format(value) * math.format(template, object) * * Example usage: * math.format(2/7); // '0.28571' * math.format(new Complex(2, 3)); // '2 + 3i' * math.format('Hello $name! The date is $date', { * name: 'user', * date: new Date().toISOString().substring(0, 10) * }); // 'hello user! The date is 2013-03-23' * * @param {String} template * @param {Object} values * @return {String} str */ math.format = function format(template, values) { var num = arguments.length; if (num != 1 && num != 2) { throw newArgumentsError('format', num, 1, 2); } if (num == 1) { // just format a value as string var value = arguments[0]; if (isNumber(value)) { return util.formatNumber(value); } if (value instanceof Array) { return util.formatArray(value); } if (isString(value)) { return '"' + value + '"'; } if (value instanceof Object) { return value.toString(); } return String(value); } else { if (!isString(template)) { throw new TypeError('String expected as first parameter in function format'); } if (!(values instanceof Object)) { throw new TypeError('Object expected as first parameter in function format'); } // format values into a string return template.replace(/\$([\w\.]+)/g, function (original, key) { var keys = key.split('.'); var value = values[keys.shift()]; while (keys.length && value != undefined) { var k = keys.shift(); value = k ? value[k] : value + '.'; } return value != undefined ? value : original; } ); } }; /** * Import functions from an object or a file * @param {function | String | Object} object * @param {Object} [options] Available options: * {Boolean} override * If true, existing functions will be * overwritten. False by default. */ // TODO: return status information math['import'] = function math_import(object, options) { var name; var opts = { override: false }; if (options && options instanceof Object) { util.extend(opts, options); } if (isString(object)) { // a string with a filename if (typeof (require) !== 'undefined') { // load the file using require var _module = require(object); math['import'](_module); } else { throw new Error('Cannot load file: require not available.'); } } else if (isSupportedType(object)) { // a single function name = object.name; if (name) { if (opts.override || math[name] === undefined) { _import(name, object); } } else { throw new Error('Cannot import an unnamed function or object'); } } else if (object instanceof Object) { // a map with functions for (name in object) { if (object.hasOwnProperty(name)) { var value = object[name]; if (isSupportedType(value)) { if (opts.override || math[name] === undefined) { _import(name, value); } } else { math['import'](value); } } } } }; /** * Add a property to the math namespace and create a chain proxy for it. * @param {String} name * @param {*} value * @private */ function _import(name, value) { // add to math namespace math[name] = value; // create a proxy for the Selector createSelectorProxy(name, value); } /** * Check whether given object is a supported type * @param object * @return {Boolean} * @private */ function isSupportedType(object) { return (typeof object == 'function') || isNumber(object) || isString(object) || (object instanceof Complex) || (object instanceof Unit); // TODO: add boolean? } /** * Wrap any value in a Selector, allowing to perform chained operations on * the value. * * All methods available in the math.js library can be called upon the selector, * and then will be evaluated with the value itself as first argument. * The selector can be closed by executing selector.done(), which will return * the final value. * * Example usage: * math.select(3) * .add(4) * .subtract(2) * .done(); // 5 * math.select( [[1, 2], [3, 4]] ) * .set([1, 1], 8) * .multiply(3) * .done(); // [[24, 6], [9, 12]] * * The Selector has a number of special functions: * - done() Finalize the chained operation and return the selectors value. * - valueOf() The same as done() * - toString() Executes math.format() onto the selectors value, returning * a string representation of the value. * - get(...) Get a subselection of the selectors value. Only applicable when * the value has a method get, for example when value is a Matrix * or Array. * - set(...) Replace a subselection of the selectors value. Only applicable * when the value has a method get, for example when value is a * Matrix or Array. * * @param {*} value * @return {math.type.Selector} selector */ math.select = function select(value) { return new math.type.Selector(value); }; /** * Determine the type of a variable * * typeof(x) * * @param {*} x * @return {String} type Lower case type, for example "number", "string", * "array". */ math['typeof'] = function math_typeof(x) { if (arguments.length != 1) { throw newArgumentsError('typeof', arguments.length, 1); } var type = typeof x, name; if (type == 'object') { if (x == null) { return 'null'; } if (x instanceof Boolean) { return 'boolean'; } if (x instanceof Number) { return 'number'; } if (x instanceof String) { return 'string'; } if (x instanceof Array) { return 'array'; } if (x instanceof Date) { return 'date'; } if (x.constructor) { // search functions / constants for (name in math) { if (math.hasOwnProperty(name)) { if (x.constructor == math[name]) { return name.toLowerCase(); } } } // search data types for (name in math.type) { if (math.type.hasOwnProperty(name)) { if (x.constructor == math.type[name]) { return name.toLowerCase(); } } } // try the constructors name as last resort if (x.constructor.name) { return x.constructor.name.toLowerCase(); } } } return type; }; /** * Backward compatibility stuff */ // TODO: remove error messages for deprecated methods (deprecated since version 0.5.0) function deprecated(deprecated, replacement) { throw new Error( 'Constructor "' + deprecated +'" has been replaced by ' + 'constructor method "' + replacement + '" in math.js v0.5.0'); } math.Complex = function () { deprecated('new math.Complex()', 'math.complex()'); }; math.Unit = function () { deprecated('new math.Unit()', 'math.unit()'); }; math.parser.Parser = function () { deprecated('new math.parser.Parser()', 'math.parser()'); }; math.parser.Workspace = function () { deprecated('new math.parser.Workspace()', 'math.workspace()'); }; // initialise the Chain prototype with all functions and constants in math for (var prop in math) { if (math.hasOwnProperty(prop) && prop) { createSelectorProxy(prop, math[prop]); } } })();