mathjs/math.js

8956 lines
237 KiB
JavaScript

/**
* 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.5.0-SNAPSHOT
* @date 2013-03-28
*
* @license
* Copyright (C) 2013 Jos de Jong <wjosdejong@gmail.com>
*
* 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() {
/**
* Define namespace
*/
var math = {
type: {},
expr: {
node: {}
},
options: {
precision: 10 // number of decimals 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.0001 && abs < 1000000) || abs == 0.0 ) {
// round the func to a limited number of digits
return String(roundNumber(value, digits));
}
else {
// scientific notation
var exp = Math.round(Math.log(abs) / Math.LN10);
var v = value / (Math.pow(10.0, exp));
return roundNumber(v, digits) + 'E' + exp;
}
};
/**
* 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 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 += 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;
};
/**
* 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} array
* @param {function} fn
* @return {Array} res
*/
util.map = function map(array, fn) {
if (!array instanceof Array) {
throw new TypeError('Array expected');
}
return array.map(function (x) {
return fn(x);
});
};
/**
* Execute function fn element wise for each entry in two given arrays, or for
* an object and array pair. Returns an array with the results
* @param {Array | Object} array1
* @param {Array | Object} array2
* @param {function} fn
* @return {Array} res
*/
util.map2 = function map2(array1, array2, fn) {
var res, len, i;
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 | Array} object The object or array.
* @param {function} callback Mapping function
* @return {Object | Array} mappedObject
*/
util.map = function map (object, callback) {
if (object instanceof Array) {
return object.map(callback);
}
else {
var m = {};
for (var key in object) {
if (object.hasOwnProperty(key)) {
m[key] = callback(object[key]);
}
}
return m;
}
};
/**
* 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 ? 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;
};
}
return util;
})();
/**
* @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.constructor != 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[index];
}
function revert(oldIndex) {
index = oldIndex;
c = text[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[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;
};
/**
* Type documentation
*/
Complex.doc = {
'name': 'Complex',
'category': 'type',
'syntax': [
'a + bi',
'a + b * i'
],
'description':
'A complex value a + bi, ' +
'where a is the real part and b is the complex part, ' +
'and i is the imaginary number defined as sqrt(-1).',
'examples': [
'2 + 3i',
'sqrt(-4)',
'(1.2 -5i) * 2'
],
'seealso': [
'abs',
'arg',
'conj',
'im',
're'
]
};
/**
* @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.
* 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([1, 0]) // 3
*
* @param {Array | Matrix | Range} [data] A multi dimensional array
*/
function Matrix(data) {
if (this.constructor != 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 set of values from the matrix.
* Indexes are zero-based.
* @param {Array | Matrix} index
*/
Matrix.prototype.get = function (index) {
// TODO: support syntax Matrix.get(m,n,p, ...)
// TODO: support getting a range of values
if (index instanceof Matrix) {
if (!index.isVector()) {
throw new RangeError('Index must be a vector ' +
'(size: ' + format(index.size()) + ')');
}
index = index.toVector();
}
if (index instanceof Range) {
index = index.toArray();
}
if (index instanceof Array) {
if (index.length != this._size.length) {
throw new RangeError('Number of dimensions do not match ' +
'(' + index.length + ' != ' + this._size.length + ')');
}
var value = this._data;
index.forEach(function (i) {
if (!isNumber(i) || !isInteger(i) || i < 0) {
throw new TypeError('Positive integer expected as index in method get');
}
if (i > value.length - 1) {
throw new RangeError('Index out of range (' + i + ')');
}
value = value[i];
});
return value;
}
else {
// TODO: support a single number as index in case the matrix is a vector
throw new TypeError('Unsupported type of index ' + math.typeof(index));
}
};
/**
* Get a value or a set of values from the matrix.
* Indexes are zero-based.
* @param {Array | Range | Matrix} index
* @param {*} value
* @return {Matrix} itself
*/
Matrix.prototype.set = function (index, value) {
// TODO: support syntax Matrix.get(m,n,p, ..., value)
// TODO: support setting a range of values
if (index instanceof Matrix) {
if (!index.isVector()) {
throw new RangeError('Index must be a vector ' +
'(size: ' + format(index.size()) + ')');
}
index = index.toVector();
}
if (value instanceof Matrix || value instanceof Range) {
value = value.valueOf();
}
if (index instanceof Array) {
if (value instanceof Array) {
throw new Error('Setting a range of values is not yet implemented...');
}
else {
var size = this._size.concat([]);
var needResize = false;
if (index.length != this._size.length) {
needResize = true;
}
for (var i = 0; i < index.length; i++) {
var index_i = index[i];
if (!isNumber(index_i) || !isInteger(index_i) || index_i < 0) {
throw new TypeError('Positive integer expected as index in method get');
}
if ((size[i] == undefined) || (index_i + 1 > size[i])) {
size[i] = index_i + 1;
needResize = true;
}
}
if (needResize) {
this.resize(size);
}
var len = size.length;
var arr = this._data;
index.forEach(function (v, i) {
if (i < len - 1) {
arr = arr[v];
}
else {
arr[v] = value;
}
});
}
}
else {
// TODO: support a single number as index in case the matrix is a vector
throw new TypeError('Unsupported type of index ' + math.typeof(index));
}
return this;
};
/**
* 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 = clone(size);
};
/**
* Create a clone of the matrix
* @return {Matrix} clone
*/
Matrix.prototype.clone = function () {
var matrix = new Matrix();
matrix._data = clone(this._data);
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;
};
// TODO: implement Matrix.map
// TODO: implement Matrix.forEach
/**
* 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 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 = [];
for (var i = 0, iMax = this._size[dim]; i < iMax; i++) {
index[dim] = i;
vector[i] = clone(this.get(index));
}
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 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 util.formatArray(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.constructor != 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;
/**
* 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 (sign(step) == 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 Called as fn(value, index) for each value in the
* range.
*/
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);
x += step;
i++;
}
}
else if (step < 0) {
while (x >= end) {
callback(x, i);
x += step;
i++;
}
}
};
/**
* Execute a callback function for each value in the Range, and return the
* results as an array
* @returns {Array} array
*/
Range.prototype.map = function (callback) {
var array = [];
this.forEach(function (value, index) {
array[index] = callback(value);
});
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 () {
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 = format(Number(this.start));
if (this.step != 1) {
str += ':' + format(Number(this.step));
}
str += ':' + format(Number(this.end));
return str;
};
/**
* 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.constructor != 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');
}
this.value = 1;
this.unit = Unit.UNIT_NONE;
this.prefix = Unit.PREFIX_NONE; // link to a list with supported prefixes
this.hasUnit = false;
this.hasValue = false;
this.fixPrefix = false; // is set true by the method "x In unit"s
if (unit != null) {
// find the unit and prefix from the string
var UNITS = Unit.UNITS;
var found = false;
for (var i = 0, iMax = UNITS.length; i < iMax; i++) {
var UNIT = UNITS[i];
if (Unit.endsWith(unit, UNIT.name) ) {
var prefixLen = (unit.length - UNIT.name.length);
var prefixName = unit.substring(0, prefixLen);
var prefix = UNIT.prefixes[prefixName];
if (prefix !== undefined) {
// store unit, prefix, and value
this.unit = UNIT;
this.prefix = prefix;
this.hasUnit = true;
found = true;
break;
}
}
}
if (!found) {
throw new Error('String "' + unit + '" is no unit');
}
}
if (value != null) {
this.value = this._normalize(value);
this.hasValue = true;
}
else {
this.value = this._normalize(1);
}
}
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[index];
}
function revert(oldIndex) {
index = oldIndex;
c = text[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 a 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;
};
/**
* check if a text ends with a certain string
* @param {String} text
* @param {String} search
*/
// TODO: put the endsWith method in another
Unit.endsWith = function(text, search) {
var start = text.length - search.length;
var end = text.length;
return (text.substring(start, end) === search);
};
/**
* 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 prefixvalue 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;
}
};
/**
* Test if the given expression is a unit
* @param {String} unit A unit with prefix, like "cm"
* @return {Boolean} true if the given string is a unit
*/
Unit.isUnit = function (unit) {
var UNITS = Unit.UNITS;
var num = UNITS.length;
for (var i = 0; i < num; i++) {
var UNIT = UNITS[i];
if (Unit.endsWith(unit, UNIT.name) ) {
var prefixLen = (unit.length - UNIT.name.length);
if (prefixLen == 0) {
return true;
}
var prefixName = unit.substring(0, prefixLen);
var prefix = UNIT.prefixes[prefixName];
if (prefix !== undefined) {
return true;
}
}
}
return false;
};
/**
* 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);
};
/**
* Get string representation
* @return {String}
*/
Unit.prototype.toString = function() {
var value;
if (!this.fixPrefix) {
// 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;
}
}
}
}
value = this._unnormalize(this.value, bestPrefix.value);
return util.formatNumber(value) + ' ' + bestPrefix.name + this.unit.name;
}
else {
value = this._unnormalize(this.value);
return util.formatNumber(value) + ' ' + this.prefix.name + this.unit.name;
}
};
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 <fn> does not support a parameter of type <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 = _typeof(value1);
msg = 'Function ' + name + ' does not support a parameter of type ' + t;
}
else if (arguments.length > 2) {
var types = [];
for (var i = 1; i < arguments.length; i++) {
types.push(_typeof(arguments[i]));
}
msg = 'Function ' + name + ' does not support a parameters of type ' + types.join(', ');
}
else {
msg = 'Unsupported parameter in function ' + name;
}
return new TypeError(msg);
}
/**
* Create a syntax error with the message:
* 'Wrong number of arguments in function <fn> (<count> provided, <min>-<max> 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);
}
/**
* Calculate the square root of a value
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, abs);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return abs(x.valueOf());
}
throw newUnsupportedTypeError('abs', x);
}
math.abs = abs;
/**
* Function documentation
*/
abs.doc = {
'name': 'abs',
'category': 'Arithmetic',
'syntax': [
'abs(x)'
],
'description': 'Compute the absolute value.',
'examples': [
'abs(3.5)',
'abs(-4.2)'
],
'seealso': ['sign']
};
/**
* Add two values. x + y or add(x, y)
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Number | Complex | Unit | String | Array} res
*/
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.hasValue) {
throw new Error('Unit on left hand side of operator + has no value');
}
if (!y.hasValue) {
throw new Error('Unit on right hand side of operator + has no 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 || y instanceof Array) {
return util.map2(x, y, add);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return add(x.valueOf());
}
throw newUnsupportedTypeError('add', x, y);
}
math.add = add;
/**
* Function documentation
*/
add.doc = {
'name': 'add',
'category': 'Operators',
'syntax': [
'x + y',
'add(x, y)'
],
'description': 'Add two values.',
'examples': [
'2.1 + 3.6',
'ans - 3.6',
'3 + 2i',
'"hello" + " world"',
'3 cm + 2 inch'
],
'seealso': [
'subtract'
]
};
/**
* Round a value towards plus infinity, ceil(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, ceil);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return ceil(x.valueOf());
}
throw newUnsupportedTypeError('ceil', x);
}
math.ceil = ceil;
/**
* Function documentation
*/
ceil.doc = {
'name': 'ceil',
'category': 'Arithmetic',
'syntax': [
'ceil(x)'
],
'description':
'Round a value towards plus infinity.' +
'If x is complex, both real and imaginary part are rounded ' +
'towards plus infinity.',
'examples': [
'ceil(3.2)',
'ceil(3.8)',
'ceil(-4.2)'
],
'seealso': ['floor', 'fix', 'round']
};
/**
* Compute the cube of a value, x * x * x.',
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 multiply(multiply(x, x), x);
}
if (x instanceof Array) {
return multiply(multiply(x, x), x);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return cube(x.valueOf());
}
throw newUnsupportedTypeError('cube', x);
}
math.cube = cube;
/**
* Function documentation
*/
cube.doc = {
'name': 'cube',
'category': 'Arithmetic',
'syntax': [
'cube(x)'
],
'description': 'Compute the cube of a value. ' +
'The cube of x is x * x * x.',
'examples': [
'cube(2)',
'2^3',
'2 * 2 * 2'
],
'seealso': [
'multiply',
'square',
'pow'
]
};
/**
* Divide two values. x / y or divide(x, y)
* @param {Number | Complex | Unit | Array} x
* @param {Number | Complex} y
* @return {Number | Complex | Unit | Array} res
*/
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) {
if (y instanceof Array) {
// TODO: implement matrix/matrix
}
else {
// matrix / scalar
return util.map2(x, y, divide);
}
}
if (y instanceof Array) {
// TODO: implement scalar/matrix
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive value
return 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
);
}
math.divide = divide;
/**
* Function documentation
*/
divide.doc = {
'name': 'divide',
'category': 'Operators',
'syntax': [
'x / y',
'divide(x, y)'
],
'description': 'Divide two values.',
'examples': [
'2 / 3',
'ans * 3',
'4.5 / 2',
'3 + 4 / 2',
'(3 + 4) / 2',
'18 km / 4.5'
],
'seealso': [
'multiply'
]
};
/**
* Check if value x equals y, x == y
* In case of complex numbers, x.re must equal y.re, and x.im must equal y.im.
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Boolean | Array} res
*/
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 || y instanceof Array) {
return util.map2(x, y, 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);
}
math.equal = equal;
/**
* Function documentation
*/
equal.doc = {
'name': 'equal',
'category': 'Operators',
'syntax': [
'x == y',
'equal(x, y)'
],
'description':
'Check equality of two values. ' +
'Returns 1 if the values are equal, and 0 if not.',
'examples': [
'2+2 == 3',
'2+2 == 4',
'a = 3.2',
'b = 6-2.8',
'a == b',
'50cm == 0.5m'
],
'seealso': [
'unequal', 'smaller', 'larger', 'smallereq', 'largereq'
]
};
/**
* Calculate the exponent of a value, exp(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, exp);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return exp(x.valueOf());
}
throw newUnsupportedTypeError('exp', x);
}
math.exp = exp;
/**
* Function documentation
*/
exp.doc = {
'name': 'exp',
'category': 'Arithmetic',
'syntax': [
'exp(x)'
],
'description': 'Calculate the exponent of a value.',
'examples': [
'exp(1.3)',
'e ^ 1.3',
'log(exp(1.3))',
'x = 2.4',
'(exp(i*x) == cos(x) + i*sin(x)) # Euler\'s formula'
],
'seealso': [
'square',
'multiply',
'log'
]
};
/**
* Round a value towards zero, fix(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
function fix(x) {
if (arguments.length != 1) {
throw newArgumentsError('fix', arguments.length, 1);
}
if (isNumber(x)) {
return (value > 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) {
return util.map(x, fix);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return fix(x.valueOf());
}
throw newUnsupportedTypeError('fix', x);
}
math.fix = fix;
/**
* Function documentation
*/
fix.doc = {
'name': 'fix',
'category': 'Arithmetic',
'syntax': [
'fix(x)'
],
'description':
'Round a value towards zero.' +
'If x is complex, both real and imaginary part are rounded ' +
'towards zero.',
'examples': [
'fix(3.2)',
'fix(3.8)',
'fix(-4.2)',
'fix(-4.8)'
],
'seealso': ['ceil', 'floor', 'round']
};
/**
* Round a value towards minus infinity, floor(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, floor);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return floor(x.valueOf());
}
throw newUnsupportedTypeError('floor', x);
}
math.floor = floor;
/**
* Function documentation
*/
floor.doc = {
'name': 'floor',
'category': 'Arithmetic',
'syntax': [
'floor(x)'
],
'description':
'Round a value towards minus infinity.' +
'If x is complex, both real and imaginary part are rounded ' +
'towards minus infinity.',
'examples': [
'floor(3.2)',
'floor(3.8)',
'floor(-4.2)'
],
'seealso': ['ceil', 'fix', 'round']
};
/**
* Check if value x is larger y, x > y
* In case of complex numbers, the absolute values of a and b are compared.
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Boolean | Array} res
*/
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 > abs(y);
}
}
if (x instanceof Complex) {
if (isNumber(y)) {
return abs(x) > y;
}
else if (y instanceof Complex) {
return abs(x) > 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 || y instanceof Array) {
return util.map2(x, y, equal);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return larger(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('larger', x, y);
}
math.larger = larger;
/**
* Function documentation
*/
larger.doc = {
'name': 'larger',
'category': 'Operators',
'syntax': [
'x > y',
'larger(x, y)'
],
'description':
'Check if value x is larger than y. ' +
'Returns 1 if x is larger than y, and 0 if not.',
'examples': [
'2 > 3',
'5 > 2*2',
'a = 3.3',
'b = 6-2.8',
'(a > b)',
'(b < a)',
'5 cm > 2 inch'
],
'seealso': [
'equal', 'unequal', 'smaller', 'smallereq', 'largereq'
]
};
/**
* Check if value x is larger or equal to y, x >= y
* In case of complex numbers, the absolute values of a and b are compared.
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Boolean | Array} res
*/
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 >= abs(y);
}
}
if (x instanceof Complex) {
if (isNumber(y)) {
return abs(x) >= y;
}
else if (y instanceof Complex) {
return abs(x) >= 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 || y instanceof Array) {
return util.map2(x, y, largereq);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return largereq(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('largereq', x, y);
}
math.largereq = largereq;
/**
* Function documentation
*/
largereq.doc = {
'name': 'largereq',
'category': 'Operators',
'syntax': [
'x >= y',
'largereq(x, y)'
],
'description':
'Check if value x is larger or equal to y. ' +
'Returns 1 if x is larger or equal to y, and 0 if not.',
'examples': [
'2 > 1+1',
'2 >= 1+1',
'a = 3.2',
'b = 6-2.8',
'(a > b)'
],
'seealso': [
'equal', 'unequal', 'smallereq', 'smaller', 'largereq'
]
};
/**
* Calculate the logarithm of a value, log(x [, base])
* base is optional. If not provided, the natural logarithm of x is calculated
* logarithm for any base, like log(x, base)
* @param {Number | Complex | Array} x
* @param {Number | Complex} [base]
* @return {Number | Complex | Array} res
*/
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 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) {
return util.map(x, log);
}
}
else {
// calculate logarithm for a specified base, log(x, base)
return divide(log(x), log(base));
}
if (x.valueOf() !== x || base.valueOf() !== base) {
// fallback on the objects primitive values
return log(x.valueOf(), base.valueOf());
}
throw newUnsupportedTypeError('log', x, base);
}
math.log = log;
/**
* Function documentation
*/
log.doc = {
'name': 'log',
'category': 'Arithmetic',
'syntax': [
'log(x)',
'log(x, base)'
],
'description': 'Compute the logarithm of a value. ' +
'If no base is provided, the natural logarithm of x is calculated. ' +
'If base if provided, the logarithm is calculated for the specified base. ' +
'log(x, base) is defined as log(x) / log(base).',
'examples': [
'log(3.5)',
'a = log(2.4)',
'exp(a)',
'10 ^ 3',
'log(1000, 10)',
'log(1000) / log(10)',
'b = logb(1024, 2)',
'2 ^ b'
],
'seealso': [
'exp',
'log10'
]
};
/**
* Calculate the 10-base logarithm of a value, log10(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 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) {
return util.map(x, log10);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return log10(x.valueOf());
}
throw newUnsupportedTypeError('log10', x);
}
math.log10 = log10;
/**
* Function documentation
*/
log10.doc = {
'name': 'log10',
'category': 'Arithmetic',
'syntax': [
'log10(x)'
],
'description': 'Compute the 10-base logarithm of a value.',
'examples': [
'log10(1000)',
'10 ^ 3',
'log10(0.01)',
'log(1000) / log(10)',
'log(1000, 10)'
],
'seealso': [
'exp',
'log'
]
};
/**
* Calculates the modulus, the remainder of an integer division.
* @param {Number | Complex | Array} x
* @param {Number | Complex | Array} y
* @return {Number | Array} res
*/
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 || y instanceof Array) {
return util.map2(x, y, mod);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return mod(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('mod', x, y);
}
math.mod = mod;
/**
* Function documentation
*/
mod.doc = {
'name': 'mod',
'category': 'Operators',
'syntax': [
'x % y',
'x mod y',
'mod(x, y)'
],
'description':
'Calculates the modulus, the remainder of an integer division.',
'examples': [
'7 % 3',
'11 % 2',
'10 mod 4',
'function isOdd(x) = x % 2',
'isOdd(2)',
'isOdd(3)'
],
'seealso': []
};
/**
* Multiply two values. x + y or multiply(x, y)
* @param {Number | Complex | Unit | Array} x
* @param {Number | Complex | Unit | Array} y
* @return {Number | Complex | Unit | Array} res
*/
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 = [];
var rows = sizeX[0];
var cols = sizeY[1];
var num = sizeX[1];
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 {
// matrix * scalar
return util.map2(x, y, multiply);
}
}
if (y instanceof Array) {
// scalar * matrix
return util.map2(x, y, multiply);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return 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
);
}
math.multiply = multiply;
/**
* Function documentation
*/
multiply.doc = {
'name': 'multiply',
'category': 'Operators',
'syntax': [
'x * y',
'multiply(x, y)'
],
'description': 'multiply two values.',
'examples': [
'2.1 * 3.6',
'ans / 3.6',
'2 * 3 + 4',
'2 * (3 + 4)',
'3 * 2.1 km'
],
'seealso': [
'divide'
]
};
/**
* Calculates the power of x to y, x^y
* @param {Number | Complex | Array} x
* @param {Number | Complex} y
* @return {Number | Complex | Array} res
*/
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 identity(s[0]);
}
else {
// value > 0
var res = x;
for (var i = 1; i < y; i++) {
res = multiply(x, res);
}
return res;
}
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return pow(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('pow', x, y);
}
/**
* Caculates 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 = log(x);
var temp2 = multiply(temp1, y);
return exp(temp2);
}
math.pow = pow;
/**
* Function documentation
*/
pow.doc = {
'name': 'pow',
'category': 'Operators',
'syntax': [
'x ^ y',
'pow(x, y)'
],
'description':
'Calculates the power of x to y, x^y.',
'examples': [
'2^3 = 8',
'2*2*2',
'1 + e ^ (pi * i)'
],
'seealso': [
'unequal', 'smaller', 'larger', 'smallereq', 'largereq'
]
};
/**
* Round a value towards the nearest integer, round(x [, n])
* @param {Number | Complex | Array} x
* @param {Number | Array} [n] number of digits
* @return {Number | Complex | Array} res
*/
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) {
util.map(x, round);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return round(x.valueOf());
}
throw newUnsupportedTypeError('round', x);
}
else {
// round (x, n)
if (!isNumber(n)) {
throw new TypeError('Number of digits in function round must be an integer');
}
if (n !== Math.round(n)) {
throw new TypeError('Number of digits in function round must be integer');
}
if (n < 0 || n > 9) {
throw new Error ('Number of digits 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 || n instanceof Array) {
return util.map2(x, n, round);
}
if (x.valueOf() !== x || n.valueOf() !== n) {
// fallback on the objects primitive values
return larger(x.valueOf(), n.valueOf());
}
throw newUnsupportedTypeError('round', x, n);
}
}
math.round = round;
/**
* round a number to the given number of digits, or to the default if
* digits is not provided
* @param {Number} value
* @param {Number} [digits] number of digits, between 0 and 15
* @return {Number} roundedValue
*/
function roundNumber (value, digits) {
var p = Math.pow(10, (digits != undefined) ? digits : math.options.precision);
return Math.round(value * p) / p;
}
/**
* Function documentation
*/
round.doc = {
'name': 'round',
'category': 'Arithmetic',
'syntax': [
'round(x)',
'round(x, n)'
],
'description':
'round a value towards the nearest integer.' +
'If x is complex, both real and imaginary part are rounded ' +
'towards the nearest integer. ' +
'When n is specified, the value is rounded to n decimals.',
'examples': [
'round(3.2)',
'round(3.8)',
'round(-4.2)',
'round(-4.8)',
'round(pi, 3)',
'round(123.45678, 2)'
],
'seealso': ['ceil', 'floor', 'fix']
};
/**
* Compute the sign of a value.
* The sign of a value x is 1 when x>1, -1 when x<0, and 0 when x=0.
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, sign);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return sign(x.valueOf());
}
throw newUnsupportedTypeError('sign', x);
}
math.sign = sign;
/**
* Function documentation
*/
sign.doc = {
'name': 'sign',
'category': 'Arithmetic',
'syntax': [
'sign(x)'
],
'description':
'Compute the sign of a value. ' +
'The sign of a value x is 1 when x>1, -1 when x<0, and 0 when x=0.',
'examples': [
'sign(3.5)',
'sign(-4.2)',
'sign(0)'
],
'seealso': [
'abs'
]
};
/**
* Check if value x is smaller y, x < y
* In case of complex numbers, the absolute values of a and b are compared.
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Boolean | Array} res
*/
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 < abs(y);
}
}
if (x instanceof Complex) {
if (isNumber(y)) {
return abs(x) < y;
}
else if (y instanceof Complex) {
return abs(x) < 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 || y instanceof Array) {
return util.map2(x, y, smaller);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return smaller(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('smaller', x, y);
}
math.smaller = smaller;
/**
* Function documentation
*/
smaller.doc = {
'name': 'smaller',
'category': 'Operators',
'syntax': [
'x < y',
'smaller(x, y)'
],
'description':
'Check if value x is smaller than value y. ' +
'Returns 1 if x is smaller than y, and 0 if not.',
'examples': [
'2 < 3',
'5 < 2*2',
'a = 3.3',
'b = 6-2.8',
'(a < b)',
'5 cm < 2 inch'
],
'seealso': [
'equal', 'unequal', 'larger', 'smallereq', 'largereq'
]
};
/**
* Check if value a is smaller or equal to b, a <= b
* In case of complex numbers, the absolute values of a and b are compared.
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Boolean | Array} res
*/
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 <= abs(y);
}
}
if (x instanceof Complex) {
if (isNumber(y)) {
return abs(x) <= y;
}
else if (y instanceof Complex) {
return abs(x) <= 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 || y instanceof Array) {
return util.map2(x, y, smallereq);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return smallereq(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('smallereq', x, y);
}
math.smallereq = smallereq;
/**
* Function documentation
*/
smallereq.doc = {
'name': 'smallereq',
'category': 'Operators',
'syntax': [
'x <= y',
'smallereq(x, y)'
],
'description':
'Check if value x is smaller or equal to value y. ' +
'Returns 1 if x is smaller than y, and 0 if not.',
'examples': [
'2 < 1+1',
'2 <= 1+1',
'a = 3.2',
'b = 6-2.8',
'(a < b)'
],
'seealso': [
'equal', 'unequal', 'larger', 'smaller', 'largereq'
]
};
/**
* Calculate the square root of a value
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 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) {
return util.map(x, sqrt);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return sqrt(x.valueOf());
}
throw newUnsupportedTypeError('sqrt', x);
}
math.sqrt = sqrt;
/**
* Function documentation
*/
sqrt.doc = {
'name': 'sqrt',
'category': 'Arithmetic',
'syntax': [
'sqrt(x)'
],
'description':
'Compute the square root value. ' +
'If x = y * y, then y is the square root of x.',
'examples': [
'sqrt(25)',
'5 * 5',
'sqrt(-1)'
],
'seealso': [
'square',
'multiply'
]
};
/**
* Compute the square of a value, x * x
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
function square(x) {
if (arguments.length != 1) {
throw newArgumentsError('square', arguments.length, 1);
}
if (isNumber(x)) {
return x * x;
}
if (x instanceof Complex) {
return multiply(x, x);
}
if (x instanceof Array) {
return multiply(x, x);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return square(x.valueOf());
}
throw newUnsupportedTypeError('square', x);
}
math.square = square;
/**
* Function documentation
*/
square.doc = {
'name': 'square',
'category': 'Arithmetic',
'syntax': [
'square(x)'
],
'description':
'Compute the square of a value. ' +
'The square of x is x * x.',
'examples': [
'square(3)',
'sqrt(9)',
'3^2',
'3 * 3'
],
'seealso': [
'multiply',
'pow',
'sqrt',
'cube'
]
};
/**
* Subtract two values. x - y or subtract(x, y)
* @param {Number | Complex | Unit | Array} x
* @param {Number | Complex | Unit | Array} y
* @return {Number | Complex | Unit | Array} res
*/
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.hasValue) {
throw new Error('Unit on left hand side of operator - has no value');
}
if (!y.hasValue) {
throw new Error('Unit on right hand side of operator - has no value');
}
var res = x.clone();
res.value -= y.value;
res.fixPrefix = false;
return res;
}
}
if (x instanceof Array || y instanceof Array) {
return util.map2(x, y, subtract);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return subtract(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('subtract', x, y);
}
math.subtract = subtract;
/**
* Function documentation
*/
subtract.doc = {
'name': 'subtract',
'category': 'Operators',
'syntax': [
'x - y',
'subtract(x, y)'
],
'description': 'subtract two values.',
'examples': [
'5.3 - 2',
'ans + 2',
'2/3 - 1/6',
'2 * 3 - 3',
'2.1 km - 500m'
],
'seealso': [
'add'
]
};
/**
* Inverse the sign of a value. -x or unaryminus(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Unit | Array} res
*/
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) {
return util.map(x, unaryminus);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return unaryminus(x.valueOf());
}
throw newUnsupportedTypeError('unaryminus', x);
}
math.unaryminus = unaryminus;
/**
* Function documentation
*/
unaryminus.doc = {
'name': 'unaryminus',
'category': 'Operators',
'syntax': [
'-x',
'unaryminus(x)'
],
'description':
'Inverse the sign of a value.',
'examples': [
'-4.5',
'-(-5.6)'
],
'seealso': [
'add', 'subtract'
]
};
/**
* Check if value x unequals y, x != y
* In case of complex numbers, x.re must unequal y.re, and x.im must unequal y.im
* @param {Number | Complex | Unit | String | Array} x
* @param {Number | Complex | Unit | String | Array} y
* @return {Boolean | Array} res
*/
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 || y instanceof Array) {
return util.map2(x, y, unequal);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return unequal(x.valueOf(), y.valueOf());
}
throw newUnsupportedTypeError('unequal', x, y);
}
math.unequal = unequal;
/**
* Function documentation
*/
unequal.doc = {
'name': 'unequal',
'category': 'Operators',
'syntax': [
'x != y',
'unequal(x, y)'
],
'description':
'Check unequality of two values. ' +
'Returns 1 if the values are unequal, and 0 if they are equal.',
'examples': [
'2+2 != 3',
'2+2 != 4',
'a = 3.2',
'b = 6-2.8',
'a != b',
'50cm != 0.5m',
'5 cm != 2 inch'
],
'seealso': [
'equal', 'smaller', 'larger', 'smallereq', 'largereq'
]
};
/**
* Compute the argument of a complex value.
* If x = a+bi, the argument is computed as atan2(b, a).
* @param {Number | Complex | Array} x
* @return {Number | Array} res
*/
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) {
return util.map(x, arg);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return arg(x.valueOf());
}
throw newUnsupportedTypeError('arg', x);
}
math.arg = arg;
/**
* Function documentation
*/
arg.doc = {
'name': 'arg',
'category': 'Complex',
'syntax': [
'arg(x)'
],
'description':
'Compute the argument of a complex value. ' +
'If x = a+bi, the argument is computed as atan2(b, a).',
'examples': [
'arg(2 + 2i)',
'atan2(3, 2)',
'arg(2 - 3i)'
],
'seealso': [
're',
'im',
'conj',
'abs'
]
};
/**
* Compute the complex conjugate of a complex value.
* If x = a+bi, the complex conjugate is a-bi.
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, conj);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return conj(x.valueOf());
}
throw newUnsupportedTypeError('conj', x);
}
math.conj = conj;
/**
* Function documentation
*/
conj.doc = {
'name': 'conj',
'category': 'Complex',
'syntax': [
'conj(x)'
],
'description':
'Compute the complex conjugate of a complex value. ' +
'If x = a+bi, the complex conjugate is a-bi.',
'examples': [
'conj(2 + 3i)',
'conj(2 - 3i)',
'conj(-5.2i)'
],
'seealso': [
're',
'im',
'abs',
'arg'
]
};
/**
* Get the imaginary part of a complex number.
* @param {Number | Complex | Array} x
* @return {Number | Array} 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) {
return util.map(x, im);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return im(x.valueOf());
}
throw newUnsupportedTypeError('im', x);
}
math.im = im;
/**
* Function documentation
*/
im.doc = {
'name': 'im',
'category': 'Complex',
'syntax': [
'im(x)'
],
'description': 'Get the imaginary part of a complex number.',
'examples': [
'im(2 + 3i)',
're(2 + 3i)',
'im(-5.2i)',
'im(2.4)'
],
'seealso': [
're',
'conj',
'abs',
'arg'
]
};
/**
* Get the real part of a complex number.
* @param {Number | Complex | Array} x
* @return {Number | Array} 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) {
return util.map(x, re);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return re(x.valueOf());
}
throw newUnsupportedTypeError('re', x);
}
math.re = re;
/**
* Function documentation
*/
re.doc = {
'name': 're',
'category': 'Complex',
'syntax': [
're(x)'
],
'description': 'Get the real part of a complex number.',
'examples': [
're(2 + 3i)',
'im(2 + 3i)',
're(-5.2i)',
're(2.4)'
],
'seealso': [
'im',
'conj',
'abs',
'arg'
]
};
/**
* 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
*/
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);
}
}
math.complex = complex;
/**
* 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:
* 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 | Range} [data] A multi dimensional array
* @return {Matrix} matrix
*/
function matrix(data) {
if (arguments.length > 1) {
throw newArgumentsError('matrix', arguments.length, 0, 1);
}
return new Matrix(data);
}
math.matrix = matrix;
/**
* Create a parser. The function creates a new math.expr.Parser object.
*
* 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
*/
function parser() {
return new math.expr.Parser();
}
math.parser = 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(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);
* });
*
* @param {...*} args
* @return {Range} range
*/
function range(args) {
switch (arguments.length) {
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);
}
}
math.range = range;
/**
* 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
*/
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.isUnit(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);
}
}
math.unit = unit;
/**
* Create a workspace. The function creates a new math.expr.Workspace object.
*
* 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
*/
function workspace() {
return new math.expr.Workspace();
}
math.workspace = workspace;
/**
* Create a diagonal matrix or retrieve the diagonal of a matrix
* diag(v)
* diag(v, k)
* diag(X)
* diag(X, k)
* @param {Number | Matrix | Array} x
* @param {Number} [k]
* @return {Matrix} matrix
*/
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] = 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] = clone(data[i + kSub][i + kSuper]);
}
return new Matrix(vector);
break;
default:
throw new RangeError('Matrix for function diag must be 2 dimensional');
}
}
math.diag = diag;
/**
* Function documentation
*/
diag.doc = {
'name': 'diag',
'category': 'Matrix',
'syntax': [
'diag(x)',
'diag(x, k)'
],
'description': 'Create a diagonal matrix or retrieve the diagonal ' +
'of a matrix. When x is a vector, a matrix with the vector values ' +
'on the diagonal will be returned. When x is a matrix, ' +
'a vector with the diagonal values of the matrix is returned.' +
'When k is provided, the k-th diagonal will be ' +
'filled in or retrieved, if k is positive, the values are placed ' +
'on the super diagonal. When k is negative, the values are placed ' +
'on the sub diagonal.',
'examples': [
'diag(1:4)',
'diag(1:4, 1)',
'a = [1, 2, 3; 4, 5, 6; 7, 8, 9]',
'diag(a)'
],
'seealso': [
'identity', 'ones', 'range', 'size', 'transpose', 'zeros'
]
};
/**
* Create an identity matrix with size m x n, identity(m [, n])
* @param {...Number | Matrix | Array} size
* @return {Matrix} matrix
*/
function identity (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('identity', num, 0, 2);
}
var rows = args[0],
cols = args[1];
if (!isNumber(rows) || !isInteger(rows) || rows < 1) {
throw new Error('Parameters in function identity must be positive integers');
}
if (cols) {
if (!isNumber(cols) || !isInteger(cols) || cols < 1) {
throw new Error('Parameters in function identity 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;
}
math.identity = identity;
/**
* Function documentation
*/
identity.doc = {
'name': 'identity',
'category': 'Numerics',
'syntax': [
'identity(n)',
'identity(m, n)',
'identity([m, n])',
'identity'
],
'description': 'Returns the identity matrix with size m-by-n. ' +
'The matrix has ones on the diagonal and zeros elsewhere.',
'examples': [
'identity(3)',
'identity(3, 5)',
'a = [1, 2, 3; 4, 5, 6]',
'identity(size(a))'
],
'seealso': [
'diag', 'ones', 'range', 'size', 'transpose', 'zeros'
]
};
/**
* @constructor ones
* ones(n)
* ones(m, n)
* ones([m, n])
* ones([m, n, p, ...])
* returns a matrix filled with ones
* @param {...Number | Array} size
* @return {Matrix} matrix
*/
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;
}
math.ones = ones;
/**
* Function documentation
*/
ones.doc = {
'name': 'ones',
'category': 'Numerics',
'syntax': [
'ones(n)',
'ones(m, n)',
'ones(m, n, p, ...)',
'ones([m, n])',
'ones([m, n, p, ...])',
'ones'
],
'description': 'Create a matrix containing ones.',
'examples': [
'ones(3)',
'ones(3, 5)',
'ones([2,3]) * 4.5',
'a = [1, 2, 3; 4, 5, 6]',
'ones(size(a))'
],
'seealso': [
'diag', 'identity', 'range', 'size', 'transpose', 'zeros'
]
};
/**
* Calculate the size of a matrix. size(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 || x instanceof Range) {
return x.size();
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return size(x.valueOf());
}
throw newUnsupportedTypeError('size', x);
}
math.size = size;
/**
* Function documentation
*/
size.doc = {
'name': 'size',
'category': 'Numerics',
'syntax': [
'size(x)'
],
'description': 'Calculate the size of a matrix.',
'examples': [
'size(2.3)',
'size("hello world")',
'a = [1, 2; 3, 4; 5, 6]',
'size(a)',
'size(1:6)'
],
'seealso': [
'diag', 'identity', 'ones', 'range', 'transpose', 'zeros'
]
};
/**
* @constructor zeros
* zeros(n)
* zeros(m, n)
* zeros([m, n])
* zeros([m, n, p, ...])
* returns a matrix filled with zeros
* @param {...Number | Array} size
* @return {Matrix} matrix
*/
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;
}
math.zeros = zeros;
/**
* Function documentation
*/
zeros.doc = {
'name': 'zeros',
'category': 'Numerics',
'syntax': [
'zeros(n)',
'zeros(m, n)',
'zeros(m, n, p, ...)',
'zeros([m, n])',
'zeros([m, n, p, ...])',
'zeros'
],
'description': 'Create a matrix containing zeros.',
'examples': [
'zeros(3)',
'zeros(3, 5)',
'a = [1, 2, 3; 4, 5, 6]',
'zeros(size(a))'
],
'seealso': [
'diag', 'identity', 'ones', 'range', 'size', 'transpose'
]
};
/**
* Compute the factorial of a value, factorial(x) or x!
* @Param {Number | Array} x
* @return {Number | Array} res
*/
function factorial (x) {
if (arguments.length != 1) {
throw newArgumentsError('factorial', arguments.length, 1);
}
if (isNumber(x)) {
if (!isInteger(x)) {
throw new TypeError('Function factorial can only handle integer values');
}
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) {
return util.map(x, factorial);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return factorial(x.valueOf());
}
throw newUnsupportedTypeError('factorial', x);
}
math.factorial = factorial;
/**
* Function documentation
*/
factorial.doc = {
'name': 'factorial',
'category': 'Probability',
'syntax': [
'x!',
'factorial(x)'
],
'description': 'Compute the factorial of a value',
'examples': [
'5!',
'5*4*3*2*1',
'3!'
],
'seealso': []
};
/**
* Return a random number between 0 and 1
* @return {Number} res
*/
function random () {
if (arguments.length != 0) {
throw newArgumentsError('random', arguments.length, 0);
}
// TODO: implement parameter min and max
return Math.random();
}
math.random = random;
/**
* Function documentation
*/
random.doc = {
'name': 'random',
'category': 'Probability',
'syntax': [
'random()'
],
'description':
'Return a random number between 0 and 1.',
'examples': [
'random()',
'100 * random()'
],
'seealso': []
};
/**
* Compute the maximum value of a list of values, max(a, b, c, ...)
* @param {... *} args one or multiple arguments
* @return {*} res
*/
function max(args) {
if (arguments.length == 0) {
throw new Error('Function sum requires one or more parameters (0 provided)');
}
if (arguments.length == 1 && (args.valueOf() instanceof Array)) {
return max.apply(this, args.valueOf());
}
var res = arguments[0];
for (var i = 1, iMax = arguments.length; i < iMax; i++) {
var value = arguments[i];
if (larger(value, res)) {
res = value;
}
}
return res;
}
math.max = max;
/**
* Function documentation
*/
max.doc = {
'name': 'max',
'category': 'Statistics',
'syntax': [
'max(a, b, c, ...)'
],
'description': 'Compute the maximum value of a list of values.',
'examples': [
'max(2, 3, 4, 1)',
'max(2.7, 7.1, -4.5, 2.0, 4.1)',
'min(2.7, 7.1, -4.5, 2.0, 4.1)'
],
'seealso': [
'sum',
'prod',
'avg',
'var',
'std',
'min',
'median'
]
};
/**
* Compute the minimum value of a list of values, min(a, b, c, ...)
* @param {... *} args one or multiple arguments
* @return {*} res
*/
function min(args) {
if (arguments.length == 0) {
throw new Error('Function sum requires one or more parameters (0 provided)');
}
if (arguments.length == 1 && (args.valueOf() instanceof Array)) {
return min.apply(this, args.valueOf());
}
var res = arguments[0];
for (var i = 1, iMax = arguments.length; i < iMax; i++) {
var value = arguments[i];
if (smaller(value, res)) {
res = value;
}
}
return res;
}
math.min = min;
/**
* Function documentation
*/
min.doc = {
'name': 'min',
'category': 'Statistics',
'syntax': [
'min(a, b, c, ...)'
],
'description': 'Compute the minimum value of a list of values.',
'examples': [
'max(2, 3, 4, 1)',
'max(2.7, 7.1, -4.5, 2.0, 4.1)',
'min(2.7, 7.1, -4.5, 2.0, 4.1)'
],
'seealso': [
'sum',
'prod',
'avg',
'var',
'std',
'min',
'median'
]
};
/**
* Calculate the inverse cosine of a value, acos(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 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 = sqrt(temp1);
var temp3 = new Complex(
temp2.re - x.im,
temp2.im + x.re
);
var temp4 = log(temp3);
// 0.5*pi = 1.5707963267948966192313216916398
return new Complex(
1.57079632679489661923 - temp4.im,
temp4.re
);
}
if (x instanceof Array) {
return util.map(x, acos);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return acos(x.valueOf());
}
throw newUnsupportedTypeError('acos', x);
}
math.acos = acos;
/**
* Function documentation
*/
acos.doc = {
'name': 'acos',
'category': 'Trigonometry',
'syntax': [
'acos(x)'
],
'description': 'Compute the inverse cosine of a value in radians.',
'examples': [
'acos(0.5)',
'acos(cos(2.3))'
],
'seealso': [
'cos',
'acos',
'asin'
]
};
/**
* Calculate the inverse sine of a value, asin(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 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 = sqrt(temp1);
var temp3 = new Complex(
temp2.re - im,
temp2.im + re
);
var temp4 = log(temp3);
return new Complex(temp4.im, -temp4.re);
}
if (x instanceof Array) {
return util.map(x, asin);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return asin(x.valueOf());
}
throw newUnsupportedTypeError('asin', x);
}
math.asin = asin;
/**
* Function documentation
*/
asin.doc = {
'name': 'asin',
'category': 'Trigonometry',
'syntax': [
'asin(x)'
],
'description': 'Compute the inverse sine of a value in radians.',
'examples': [
'asin(0.5)',
'asin(sin(2.3))'
],
'seealso': [
'sin',
'acos',
'asin'
]
};
/**
* Calculate the inverse tangent of a value, atan(x)
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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 = log(temp1);
return new Complex(
-0.5 * temp2.im,
0.5 * temp2.re
);
}
if (x instanceof Array) {
return util.map(x, atan);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return atan(x.valueOf());
}
throw newUnsupportedTypeError('atan', x);
}
math.atan = atan;
/**
* Function documentation
*/
atan.doc = {
'name': 'atan',
'category': 'Trigonometry',
'syntax': [
'atan(x)'
],
'description': 'Compute the inverse tangent of a value in radians.',
'examples': [
'atan(0.5)',
'atan(tan(2.3))'
],
'seealso': [
'tan',
'acos',
'asin'
]
};
/**
* Computes the principal value of the arc tangent of y/x in radians, atan2(y,x)
* @param {Number | Complex | Array} y
* @param {Number | Complex | Array} x
* @return {Number | Complex | Array} res
*/
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);
}
else if (x instanceof Complex) {
return Math.atan2(y, x.re);
}
}
else if (y instanceof Complex) {
if (isNumber(x)) {
return Math.atan2(y.re, x);
}
else if (x instanceof Complex) {
return Math.atan2(y.re, x.re);
}
}
if (x instanceof Array || y instanceof Array) {
return util.map2(y, x, atan2);
}
if (x.valueOf() !== x || y.valueOf() !== y) {
// fallback on the objects primitive values
return atan2(y.valueOf(), x.valueOf());
}
throw newUnsupportedTypeError('atan2', y, x);
}
math.atan2 = atan2;
/**
* Function documentation
*/
atan2.doc = {
'name': 'atan2',
'category': 'Trigonometry',
'syntax': [
'atan2(y, x)'
],
'description':
'Computes the principal value of the arc tangent of y/x in radians.',
'examples': [
'atan2(2, 2) / pi',
'angle = 60 deg in rad',
'x = cos(angle)',
'y = sin(angle)',
'atan2(y, x)'
],
'seealso': [
'sin',
'cos',
'tan'
]
};
/**
* Calculate the cosine of a value, cos(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, cos);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return cos(x.valueOf());
}
throw newUnsupportedTypeError('cos', x);
}
math.cos = cos;
/**
* Function documentation
*/
cos.doc = {
'name': 'cos',
'category': 'Trigonometry',
'syntax': [
'cos(x)'
],
'description': 'Compute the cosine of x in radians.',
'examples': [
'cos(2)',
'cos(pi / 4) ^ 2',
'cos(180 deg)',
'cos(60 deg)',
'sin(0.2)^2 + cos(0.2)^2'
],
'seealso': [
'acos',
'sin',
'tan'
]
};
/**
* Calculate the cotangent of a value, cot(x) = 1/tan(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, cot);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return cot(x.valueOf());
}
throw newUnsupportedTypeError('cot', x);
}
math.cot = cot;
/**
* Function documentation
*/
cot.doc = {
'name': 'cot',
'category': 'Trigonometry',
'syntax': [
'cot(x)'
],
'description': 'Compute the cotangent of x in radians. ' +
'Defined as 1/tan(x)',
'examples': [
'cot(2)',
'1 / tan(2)'
],
'seealso': [
'sec',
'csc',
'tan'
]
};
/**
* Calculate the cosecant of a value, csc(x) = 1/sin(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, csc);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return csc(x.valueOf());
}
throw newUnsupportedTypeError('csc', x);
}
math.csc = csc;
/**
* Function documentation
*/
csc.doc = {
'name': 'csc',
'category': 'Trigonometry',
'syntax': [
'csc(x)'
],
'description': 'Compute the cosecant of x in radians. ' +
'Defined as 1/sin(x)',
'examples': [
'csc(2)',
'1 / sin(2)'
],
'seealso': [
'sec',
'cot',
'sin'
]
};
/**
* Calculate the secant of a value, sec(x) = 1/cos(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, sec);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return sec(x.valueOf());
}
throw newUnsupportedTypeError('sec', x);
}
math.sec = sec;
/**
* Function documentation
*/
sec.doc = {
'name': 'sec',
'category': 'Trigonometry',
'syntax': [
'sec(x)'
],
'description': 'Compute the secant of x in radians. ' +
'Defined as 1/cos(x)',
'examples': [
'sec(2)',
'1 / cos(2)'
],
'seealso': [
'cot',
'csc',
'cos'
]
};
/**
* Calculate the sine of a value, sin(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, sin);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return sin(x.valueOf());
}
throw newUnsupportedTypeError('sin', x);
}
math.sin = sin;
/**
* Function documentation
*/
sin.doc = {
'name': 'sin',
'category': 'Trigonometry',
'syntax': [
'sin(x)'
],
'description': 'Compute the sine of x in radians.',
'examples': [
'sin(2)',
'sin(pi / 4) ^ 2',
'sin(90 deg)',
'sin(30 deg)',
'sin(0.2)^2 + cos(0.2)^2'
],
'seealso': [
'asin',
'cos',
'tan'
]
};
/**
* Calculate the tangent of a value, tan(x)
* @param {Number | Complex | Unit | Array} x
* @return {Number | Complex | Array} res
*/
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) {
return util.map(x, tan);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return tan(x.valueOf());
}
throw newUnsupportedTypeError('tan', x);
}
math.tan = tan;
/**
* Function documentation
*/
tan.doc = {
'name': 'tan',
'category': 'Trigonometry',
'syntax': [
'tan(x)'
],
'description': 'Compute the tangent of x in radians.',
'examples': [
'tan(0.5)',
'sin(0.5) / cos(0.5)',
'tan(pi / 4)',
'tan(45 deg)'
],
'seealso': [
'atan',
'sin',
'cos'
]
};
/**
* Change the unit of a value. x in unit or in(x, unit)
* @param {Unit | Array} x
* @param {Unit | Array} unit
* @return {Unit | Array} res
*/
function unit_in(x, unit) {
if (arguments.length != 2) {
throw newArgumentsError('in', arguments.length, 2);
}
if (x instanceof Unit && unit instanceof Unit) {
if (!x.equalBase(unit)) {
throw new Error('Units do not match');
}
if (unit.hasValue) {
throw new Error('Cannot convert to a unit with a value');
}
if (!unit.hasUnit) {
throw new Error('Unit expected on the right hand side of function in');
}
var res = unit.clone();
res.value = x.value;
res.fixPrefix = true;
return res;
}
if (x instanceof Array || unit instanceof Array) {
return util.map2(x, unit, unit_in);
}
if (x.valueOf() !== x) {
// fallback on the objects primitive value
return math.in(x.valueOf());
}
throw newUnsupportedTypeError('in', x);
}
math.in = unit_in;
/**
* Function documentation
*/
unit_in.doc ={
'name': 'in',
'category': 'Units',
'syntax': [
'x in unit',
'in(x, unit)'
],
'description': 'Change the unit of a value.',
'examples': [
'5 inch in cm',
'3.2kg in g',
'16 bytes in bits'
],
'seealso': []
};
/**
* Clone an object
* @param {*} x
* @return {*} 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)) {
return x;
}
if (x instanceof Array) {
return x.map(function (value) {
return clone(value);
});
}
if (x instanceof Object) {
return util.map(x, clone);
}
throw newUnsupportedTypeError('clone', x);
}
math.clone = clone;
/**
* Function documentation
*/
clone.doc = {
'name': 'clone',
'category': 'Utils',
'syntax': [
'clone(x)'
],
'description': 'Clone a variable. Creates a copy of primitive variables,' +
'and a deep copy of matrices',
'examples': [
'clone(3.5)',
'clone(2 - 4i)',
'clone(45 deg)',
'clone([1, 2; 3, 4])',
'clone("hello world")'
],
'seealso': []
};
/**
* Format a value of any type into a string. Interpolate values into the string.
* Usage:
* math.format(value)
* math.format(template, object)
*
* Example usage:
* math.format(2/7); // '0.2857142857'
* 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
*/
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;
}
);
}
}
math.format = format;
/**
* Function documentation
*/
format.doc = {
'name': 'format',
'category': 'Utils',
'syntax': [
'format(value)'
],
'description': 'Format a value of any type as string.',
'examples': [
'format(2.3)',
'format(3 - 4i)',
'format([])'
],
'seealso': []
};
/**
* Display documentation on a function or data type
* @param {function | string | Object} subject
* @return {String} documentation
*/
function help(subject) {
if (arguments.length != 1) {
throw newArgumentsError('help', arguments.length, 1);
}
if (subject != undefined) {
if (subject.doc) {
return generateDoc(subject.doc);
}
else if (subject.constructor.doc) {
return generateDoc(subject.constructor.doc);
}
else if (isString(subject)) {
// search the subject in the methods
var obj = math[subject];
if (obj && obj.doc) {
return generateDoc(obj.doc);
}
}
}
// TODO: generate documentation for constants, number and string
if (subject instanceof Object && subject.name) {
return 'No documentation found on subject "' + subject.name +'"';
}
else if (subject instanceof Object && subject.constructor.name) {
return 'No documentation found on subject "' + subject.constructor.name +'"';
}
else {
return 'No documentation found on subject "' + subject +'"';
}
}
math.help = help;
/**
* Generate readable documentation from a documentation object
* @param {Object} doc
* @return {String} readableDoc
* @private
*/
function generateDoc (doc) {
var desc = '';
if (doc.name) {
desc += 'NAME\n' + doc.name + '\n\n';
}
if (doc.category) {
desc += 'CATEGORY\n' + doc.category + '\n\n';
}
if (doc.syntax) {
desc += 'SYNTAX\n' + doc.syntax.join('\n') + '\n\n';
}
if (doc.examples) {
var parser = math.parser();
desc += 'EXAMPLES\n';
for (var i = 0; i < doc.examples.length; i++) {
var expr = doc.examples[i];
var res;
try {
res = parser.eval(expr);
}
catch (e) {
res = e;
}
desc += expr + '\n';
desc += ' ' + math.format(res) + '\n';
}
desc += '\n';
}
if (doc.seealso) {
desc += 'SEE ALSO\n' + doc.seealso.join(', ') + '\n';
}
return desc;
}
/**
* Function documentation
*/
help.doc = {
'name': 'help',
'category': 'Utils',
'syntax': [
'help(object)'
],
'description': 'Display documentation on a function or data type.',
'examples': [
'help("sqrt")',
'help("Complex")'
],
'seealso': []
};
/**
* Import functions from an object or a file
* @param {function | String | Object} object
* @param {boolean} [override] If true, existing functions will be
* overwritten. False by default.
*/
// TODO: return status information
function _import(object, override) {
var name;
if (isString(object)) {
// a string with a filename
if (typeof (require) !== 'undefined') {
// load the file using require
var module = require(object);
_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 (override || math[name] === undefined) {
math[name] = object;
}
}
else {
throw new Error('Cannot import an unnamed function');
}
}
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 (override || math[name] === undefined) {
math[name] = value;
}
}
else {
_import(value);
}
}
}
}
}
math['import'] = _import;
/**
* 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?
}
/**
* Function documentation
*/
_import.doc = {
'name': 'import',
'category': 'Utils',
'syntax': [
'import(string)'
],
'description': 'Import functions from a file.',
'examples': [
'import("numbers")',
'import("./mylib.js")'
],
'seealso': []
};
/**
* Calculate the square root of a value
* @param {*} x
* @return {String} type Lower case type, for example "number", "string",
* "array".
*/
function _typeof(x) {
if (arguments.length != 1) {
throw newArgumentsError('typeof', arguments.length, 1);
}
var type = typeof x;
if (type == 'object') {
if (x == null) {
return 'null';
}
if (x.constructor) {
for (var name in math) {
if (math.hasOwnProperty(name)) {
if (x.constructor == math[name]) {
return name.toLowerCase();
}
}
}
if (x.constructor.name) {
return x.constructor.name.toLowerCase();
}
}
}
return type;
}
math['typeof'] = _typeof;
/**
* Function documentation
*/
_typeof.doc = {
'name': 'typeof',
'category': 'Utils',
'syntax': [
'typeof(x)'
],
'description': 'Get the type of a variable.',
'examples': [
'typeof(3.5)',
'typeof(2 - 4i)',
'typeof(45 deg)',
'typeof("hello world")'
],
'seealso': []
};
/**
* 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 Symbol
* A symbol can hold and evaluate a variable or function with parameters.
* @param {String} [name]
* @param {function} fn
* @param {Node[]} params
* @extends {Node}
*/
function Symbol(name, fn, params) {
this.name = name;
this.fn = fn;
this.params = params;
}
Symbol.prototype = new Node();
math.expr.node.Symbol = Symbol;
/**
* Check whether the Symbol has one or multiple parameters set.
* @return {Boolean}
*/
Symbol.prototype.hasParams = function () {
return (this.params != undefined && this.params.length > 0);
};
/**
* Evaluate the symbol
* @return {*} result
* @override
*/
Symbol.prototype.eval = function() {
var fn = this.fn;
if (fn === undefined) {
throw new Error('Undefined symbol ' + this.name);
}
// evaluate the parameters
var results = this.params.map(function (param) {
return param.eval();
});
// evaluate the function
return fn.apply(this, results);
};
/**
* Get string representation
* @return {String} str
* @override
*/
Symbol.prototype.toString = function() {
// variable. format the symbol like "myvar"
if (this.name && !this.params) {
return this.name;
}
/* TODO: determine if the function is an operator
// operator. format the operation like "(2 + 3)"
if (this.fn && (this.fn instanceof mathnotepad.fn.Operator)) {
if (this.params && this.params.length == 2) {
return '(' +
this.params[0].toString() + ' ' +
this.name + ' ' +
this.params[1].toString() + ')';
}
}
*/
// function. format the operation like "f(2, 4.2)"
var str = this.name;
if (this.params && this.params.length) {
str += '(' + this.params.join(', ') + ')';
}
return str;
};
/**
* @constructor Constant
* @param {*} value
* @extends {Node}
*/
function Constant(value) {
this.value = value;
}
Constant.prototype = new Node();
math.expr.node.Constant = Constant;
/**
* Evaluate the constant
* @return {*} value
*/
Constant.prototype.eval = function () {
return this.value;
};
/**
* Get string representation
* @return {String} str
*/
Constant.prototype.toString = function() {
return this.value ? math.format(this.value) : '';
};
/**
* @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
return new Matrix(evalArray(this.nodes));
};
/**
* 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();
}
})
}
/**
* Get string representation
* @return {String} str
* @override
*/
MatrixNode.prototype.toString = function() {
return formatArray(this.nodes);
};
/**
* Recursively evaluate an array with nodes
* @param {Array} array
* @returns {String} str
*/
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 += formatArray(array[i]);
}
str += ']';
return str;
}
else {
return array.toString();
}
}
})();
/**
* @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 mathnotepad.tree.Assignment
* @param {String} name Symbol name
* @param {Node[] | undefined} params Zero or more parameters
* @param {Node} expr The expression defining the symbol
* @param {function} 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
if (this.result.value == undefined) {
throw new Error('Undefined symbol ' + this.name);
}
var prevResult = this.result.eval();
result = prevResult.set(paramResults, exprResult); // TODO implement set subset
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 {function} 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.value = 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();
};
(function () {
/**
* Scope
* A scope stores functions.
*
* @constructor mathnotepad.Scope
* @param {Scope} [parentScope]
*/
function Scope(parentScope) {
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")
}
math.expr.Scope = Scope;
// TODO: rethink the whole scoping solution again. Try to simplify
/**
* Create a nested scope
* The variables in a nested scope are not accessible from the parent scope
* @return {Scope} nestedScope
*/
Scope.prototype.createNestedScope = function () {
var nestedScope = new 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)
*/
Scope.prototype.clear = function () {
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 {function} symbol
* @private
*/
Scope.prototype.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 = this.newSymbol(name, lastDef);
this.symbols[name] = symbol;
}
return symbol;
};
/**
* Create a new symbol
* @param {String} name
* @param {*} [value]
* @return {function} symbol
* @private
*/
Scope.prototype.newSymbol = function (name, value) {
// create a new symbol
var scope = this;
var symbol = function () {
if (!symbol.value) {
// try to resolve again
symbol.value = scope.findDef(name);
if (!symbol.value) {
throw new Error('Undefined symbol ' + name);
}
}
if (typeof symbol.value == 'function') {
return symbol.value.apply(null, arguments);
}
else {
// TODO: implement subset for all types
return symbol.value;
}
};
symbol.value = value;
symbol.toString = function () {
return symbol.value ? symbol.value.toString() : '';
};
return symbol;
};
/**
* create a link to a value.
* @param {String} name
* @return {function} symbol
*/
Scope.prototype.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 {function} symbol
*/
Scope.prototype.createDef = function (name, value) {
var symbol = this.defs[name];
if (!symbol) {
symbol = this.createSymbol(name);
this.defs[name] = symbol;
}
if (symbol && value != undefined) {
symbol.value = value;
}
return symbol;
};
/**
* Create a variable update definition
* Returns the created symbol
* @param {String} name
* @return {function} symbol
*/
Scope.prototype.createUpdate = function (name) {
var symbol = this.updates[name];
if (!symbol) {
symbol = this.createLink(name);
this.updates[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 {function | undefined} symbol, or undefined when not found
*/
Scope.prototype.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)
var newSymbol = this.newSymbol,
symbols = this.symbols,
defs = this.defs;
/**
* Store a symbol in the root scope
* @param {String} name
* @param {*} value
* @return {function} symbol
*/
function put(name, value) {
var symbol = newSymbol(name, value);
symbols[name] = symbol;
defs[name] = symbol;
return symbol;
}
// check constant (and load the constant)
if (name == 'pi') {
return put(name, math.PI);
}
if (name == 'e') {
return put(name, math.E);
}
if (name == 'i') {
return put(name, new Complex(0, 1));
}
// 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 put(name, fn);
}
// Check if token is a unit
// Note: we do not check the upper case name, units are case sensitive!
if (Unit.isUnit(name)) {
var unit = new Unit(null, name);
return put(name, unit);
}
}
return undefined;
};
/**
* Remove a link to a symbol
* @param {String} name
*/
Scope.prototype.removeLink = function (name) {
delete this.links[name];
};
/**
* Remove a definition of a symbol
* @param {String} name
*/
Scope.prototype.removeDef = function (name) {
delete this.defs[name];
};
/**
* Remove an update definition of a symbol
* @param {String} name
*/
Scope.prototype.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
*/
Scope.prototype.init = function () {
var symbols = this.symbols;
var parentScope = this.parentScope;
for (var name in symbols) {
if (symbols.hasOwnProperty(name)) {
var symbol = symbols[name];
symbol.value = (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
*/
Scope.prototype.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
*/
Scope.prototype.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
*/
Scope.prototype.hasUpdate = function (name) {
return (this.updates[name] != undefined);
};
/**
* Retrieve all undefined symbols
* @return {function[]} undefinedSymbols All symbols which are undefined
*/
Scope.prototype.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;
};
})();
(function () {
/**
* @constructor math.expr.Parser
* Parser parses math expressions and evaluates them or returns a node tree.
*
* 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() {
if (this.constructor != Parser) {
throw new SyntaxError(
'Parser constructor must be called with the new operator');
}
this.scope = new math.expr.Scope();
};
/**
* 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) {
this._newScope();
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) {
this._newScope();
var symbol = this.scope.findDef(name);
if (symbol) {
return symbol.value;
}
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);
};
/**
* Create a new scope having the current scope as parent scope, to make current
* scope immutable
* @private
*/
math.expr.Parser.prototype._newScope = function () {
this.scope = new math.expr.Scope(this.scope);
// TODO: smartly cleanup scopes which are not relevant anymore
};
/**
* 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 == ':') {
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 == '!';
}
/**
* 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 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);
// 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 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 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)) {
throw createSyntaxError('Symbol expected at the left hand side ' +
'of assignment operator =');
}
var name = node.name;
var params = node.params;
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 expression = parse_range(scope);
var link = node.hasParams() ? scope.createUpdate(name) : scope.createDef(name);
return new Assignment(name, params, expression, link);
}
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 = range;
node = new Symbol(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?
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 Symbol(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 Symbol(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 Symbol(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 Symbol(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 Symbol(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 = unaryminus;
getToken();
var params = [parse_pow(scope)];
return new Symbol(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 = pow;
var params = [leftNode, node];
node = new Symbol(name, fn, params);
}
return node;
}
/**
* Factorial
* @param {Scope} scope
* @return {Node} node
* @private
*/
function parse_factorial (scope) {
var node = parse_plot(scope);
while (token == '!') {
var name = token;
var fn = factorial;
getToken();
var params = [node];
node = new Symbol(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 Symbol &&
!lastFunction.hasParams());
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();
var link = scope.createLink(name);
var arguments = parse_arguments(scope); // TODO: not so nice to "misuse" creating a Function
var symbol = new Symbol(name, link, arguments);
/* TODO: parse arguments
// parse arguments
while (token == '(') {
symbol = parse_arguments(scope, symbol);
}
*/
return symbol;
}
return parse_string(scope);
}
/**
* parse symbol parameters
* @param {Scope} scope
* @return {Node[]} arguments
* @private
*/
function parse_arguments (scope) {
var arguments = [];
if (token == '(') {
// TODO: in case of Plot, create a new scope.
getToken();
if (token != ')') {
arguments.push(parse_range(scope));
// parse a list with parameters
while (token == ',') {
getToken();
arguments.push(parse_range(scope));
}
}
if (token != ')') {
throw createSyntaxError('Parenthesis ) missing');
}
getToken();
}
return arguments;
}
/**
* 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 res = new Constant(str);
/* TODO: implement string with arguments
// parse arguments
while (token == '(') {
res = parse_arguments(scope, res);
}
*/
return res;
}
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([]);
}
// 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.isUnit(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: implement number with arguments
// parse arguments
while (token == '(') {
res = parse_arguments(scope, res);
}
*/
return node;
}
return parse_parentheses(scope);
}
/**
* parentheses
* @param {Scope} scope
* @return {Node} res
* @private
*/
function parse_parentheses (scope) {
// check if it is a parenthesized expression
if (token == '(') {
// parentheses (...)
getToken();
var res = 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 parentheses with arguments
// parse arguments
while (token == '(') {
res = parse_arguments(scope, res);
}
*/
return res;
}
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 row = row();
var col = col();
if (row === undefined) {
if (col === undefined) {
return message;
} else {
return message + ' (col ' + col + ')';
}
} else {
return message + ' (ln ' + row + ', col ' + col + ')';
}
}
/**
* 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 mathnotepad.Workspace.Node
* @param {Object} params Object containing parameters:
* {Number} id
* {String} expression An expression, for example "2+3"
* {mathnotepad.Parser} parser
* {mathnotepad.Scope} scope
* {mathnotepad.Workspace.Node} nextNode
* {mathnotepad.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;
};
})();
})();