mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-18 14:59:29 +00:00
4823 lines
127 KiB
JavaScript
4823 lines
127 KiB
JavaScript
/**
|
|
* math.js
|
|
* https://github.com/josdejong/mathjs
|
|
*
|
|
* Math.js is an extensive math library for JavaScript and Node.js,
|
|
* compatible with JavaScript's built-in Math library.
|
|
*
|
|
* Features:
|
|
* - A flexible expression parser
|
|
* - Support for numbers, complex values, units, strings, arrays*,
|
|
* and matrices*
|
|
* - A large set of built-in functions and constants
|
|
* - Easily extensible with new functions and constants
|
|
* - Powerful and easy to use
|
|
*
|
|
* * Note: arrays and matrices are to be implemented.
|
|
*
|
|
* @version 2013-02-23
|
|
* @date 0.2.0-SNAPSHOT
|
|
*
|
|
* @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: {},
|
|
parser: {
|
|
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;
|
|
}
|
|
|
|
|
|
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.format = function (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;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a semi UUID
|
|
* source: http://stackoverflow.com/a/105074/1262753
|
|
* @return {String} uuid
|
|
*/
|
|
util.randomUUID = function () {
|
|
var S4 = function () {
|
|
return Math.floor(
|
|
Math.random() * 0x10000 /* 65536 */
|
|
).toString(16);
|
|
};
|
|
|
|
return (
|
|
S4() + S4() + '-' +
|
|
S4() + '-' +
|
|
S4() + '-' +
|
|
S4() + '-' +
|
|
S4() + S4() + S4()
|
|
);
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
try {
|
|
console.log("Warning: Ancient browser detected. Please update your browser");
|
|
}
|
|
catch (err) {
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @constructor Unit
|
|
*
|
|
* @param {Number} [value] A value for the unit, like 5.2
|
|
* @param {String} [prefixUnit] A unit like "cm" or "inch"
|
|
*/
|
|
function Unit(value, prefixUnit) {
|
|
if (this.constructor != Unit) {
|
|
throw new Error('Unit constructor must be called with the new operator');
|
|
}
|
|
|
|
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
|
|
|
|
this._init(value, prefixUnit);
|
|
}
|
|
|
|
math.type.Unit = Unit;
|
|
|
|
/**
|
|
* 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} copy
|
|
*/
|
|
Unit.prototype.copy = 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);
|
|
};
|
|
|
|
/**
|
|
* Initialize a unit and value
|
|
* @param {Number} [value]
|
|
* @param {String} [unit] A string containing unit (and prefix), like "cm"
|
|
* @private
|
|
*/
|
|
Unit.prototype._init = function (value, unit) {
|
|
// find the unit and prefix from the string
|
|
if (unit !== undefined) {
|
|
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 !== undefined) {
|
|
this.value = this._normalize(value);
|
|
this.hasValue = true;
|
|
}
|
|
else {
|
|
this.value = this._normalize(1);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 {math.type.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.5 for nicer values: 999m
|
|
// is still displayed as 999m, and 1000m as 1km)
|
|
var bestPrefix = Unit.PREFIX_NONE;
|
|
var bestDiff = Math.abs(
|
|
Math.log(this.value / bestPrefix.value) / Math.LN10 - 1.5);
|
|
|
|
// TODO: 1000m is still displayed as 1000m, 1001m correctly as 1.001km
|
|
// TODO: working wrong with prefixes below zero, should do + 1.5 offset?
|
|
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(this.value / prefix.value) / Math.LN10 - 1.5);
|
|
|
|
if (diff < bestDiff) {
|
|
bestPrefix = prefix;
|
|
bestDiff = diff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
value = this._unnormalize(this.value, bestPrefix.value);
|
|
return util.format(value) + ' ' + bestPrefix.name + this.unit.name;
|
|
}
|
|
else {
|
|
value = this._unnormalize(this.value);
|
|
return util.format(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}
|
|
];
|
|
|
|
/**
|
|
* 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');
|
|
}
|
|
|
|
/**
|
|
* 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 Complex
|
|
*
|
|
* A complex value can be constructed in three ways:
|
|
* var a = new Complex(re, im);
|
|
* var b = new Complex(str);
|
|
* var c = new Complex();
|
|
*
|
|
* Example usage:
|
|
* var a = new Complex(3, -4); // 3 - 4i
|
|
* var b = new Complex('2 + 6i'); // 2 + 6i
|
|
* var c = new Complex(); // 0 + 0i
|
|
* var d = math.add(a, b); // 5 + 2i
|
|
*
|
|
* @param {Number | String} re A number with the real part of the complex
|
|
* value, or a string containing a complex number
|
|
* @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');
|
|
}
|
|
|
|
switch (arguments.length) {
|
|
case 2:
|
|
// re and im numbers provided
|
|
if (!isNumber(re) || !isNumber(im)) {
|
|
throw new TypeError(
|
|
'Two numbers or a single string expected in Complex constructor');
|
|
}
|
|
this.re = re;
|
|
this.im = im;
|
|
break;
|
|
|
|
case 1:
|
|
// parse string into a complex number
|
|
if (!isString(re)) {
|
|
throw new TypeError(
|
|
'Two numbers or a single string expected in Complex constructor');
|
|
}
|
|
|
|
// TODO: replace by some nice regexp?
|
|
// TODO: also support a pattern like "-2.5e+3 - 7.6e-5i"
|
|
var parts = [],
|
|
part;
|
|
var separator = '+';
|
|
var index = re.lastIndexOf(separator);
|
|
if (index == -1) {
|
|
separator = '-';
|
|
index = re.lastIndexOf(separator);
|
|
}
|
|
|
|
if (index != -1) {
|
|
part = trim(re.substring(0, index));
|
|
if (part) {
|
|
parts.push(part);
|
|
}
|
|
part = trim(re.substring(index + 1));
|
|
if (part) {
|
|
parts.push(separator + part);
|
|
}
|
|
}
|
|
else {
|
|
part = trim(re);
|
|
if (part) {
|
|
parts.push(part);
|
|
}
|
|
}
|
|
|
|
var ok = false;
|
|
switch (parts.length) {
|
|
case 1:
|
|
part = parts[0];
|
|
if (part[part.length - 1].toUpperCase() == 'I') {
|
|
// complex number
|
|
this.re = 0;
|
|
this.im = Number(part.substring(0, part.length - 1));
|
|
ok = !isNaN(this.im);
|
|
}
|
|
else {
|
|
// real number
|
|
this.re = Number(part);
|
|
this.im = 0;
|
|
ok = !isNaN(this.re);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
part = parts[0];
|
|
this.re = Number(parts[0]);
|
|
this.im = Number(parts[1].substring(0, parts[1].length - 1));
|
|
ok = !isNaN(this.re) && !isNaN(this.im) &&
|
|
(parts[1][parts[1].length - 1].toUpperCase() == 'I');
|
|
break;
|
|
}
|
|
|
|
// TODO: allow '+3-2'
|
|
|
|
if (!ok) {
|
|
throw new SyntaxError('Invalid string "' + re + '"');
|
|
}
|
|
|
|
break;
|
|
|
|
case 0:
|
|
// nul values
|
|
this.re = 0;
|
|
this.im = 0;
|
|
break;
|
|
|
|
default:
|
|
throw new SyntaxError(
|
|
'Wrong number of arguments in Complex constructor ' +
|
|
'(' + arguments.length + ' provided, 0, 1, or 2 expected)');
|
|
}
|
|
}
|
|
|
|
math.type.Complex = Complex;
|
|
|
|
/**
|
|
* Trim a string
|
|
* http://stackoverflow.com/a/498995/1262753
|
|
* @param str
|
|
* @return {*|void}
|
|
*/
|
|
function trim(str) {
|
|
return str.replace(/^\s+|\s+$/g, '');
|
|
}
|
|
|
|
/**
|
|
* 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} copy
|
|
*/
|
|
Complex.prototype.copy = function () {
|
|
return new Complex(this.re, this.im);
|
|
};
|
|
|
|
/**
|
|
* Get string representation of the Complex value
|
|
* @return {String} str
|
|
*/
|
|
Complex.prototype.toString = function () {
|
|
var str = '';
|
|
|
|
if (this.im === 0) {
|
|
// real value
|
|
str = util.format(this.re);
|
|
}
|
|
else if (this.re === 0) {
|
|
// purely complex value
|
|
if (this.im === 1) {
|
|
str = 'i';
|
|
}
|
|
else if (this.im === -1) {
|
|
str = '-i';
|
|
}
|
|
else {
|
|
str = util.format(this.im) + 'i';
|
|
}
|
|
}
|
|
else {
|
|
// complex value
|
|
if (this.im > 0) {
|
|
if (this.im == 1) {
|
|
str = util.format(this.re) + ' + i';
|
|
}
|
|
else {
|
|
str = util.format(this.re) + ' + ' + util.format(this.im) + 'i';
|
|
}
|
|
}
|
|
else {
|
|
if (this.im == -1) {
|
|
str = util.format(this.re) + ' - i';
|
|
}
|
|
else {
|
|
str = util.format(this.re) + ' - ' + util.format(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'
|
|
]
|
|
};
|
|
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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.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);
|
|
}
|
|
|
|
// search the subject in the types
|
|
for (var t in math.type) {
|
|
if (math.type.hasOwnProperty(t)) {
|
|
if (subject.toLowerCase() == t.toLowerCase() && math.type[t].doc) {
|
|
return generateDoc(math.type[t].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) {
|
|
desc += 'EXAMPLES\n';
|
|
for (var i = 0; i < doc.examples.length; i++) {
|
|
desc += doc.examples[i] + '\n';
|
|
// TODO: evaluate the examples
|
|
}
|
|
desc += '\n';
|
|
}
|
|
if (doc.seealso) {
|
|
desc += 'SEE ALSO\n' + doc.seealso.join(', ') + '\n';
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
math.abs = abs;
|
|
|
|
/**
|
|
* 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': []
|
|
};
|
|
|
|
/**
|
|
* 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 && x.constructor && 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': []
|
|
};
|
|
|
|
/**
|
|
* 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)');
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
var min = arguments[0];
|
|
for (var i = 1, iMax = arguments.length; i < iMax; i++) {
|
|
var value = arguments[i];
|
|
if (smaller(value, min)) {
|
|
min = value;
|
|
}
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* 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)');
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
var max = arguments[0];
|
|
for (var i = 1, iMax = arguments.length; i < iMax; i++) {
|
|
var value = arguments[i];
|
|
if (larger(value, max)) {
|
|
max = value;
|
|
}
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Change the unit of a value. x in unit or in(x, unit)
|
|
* @param {Unit} x
|
|
* @param {Unit} unit
|
|
* @return {Unit} res
|
|
*/
|
|
function unit_in(x, unit) {
|
|
if (arguments.length != 2) {
|
|
throw newArgumentsError('in', arguments.length, 2);
|
|
}
|
|
|
|
if (x instanceof Unit) {
|
|
// Test if unit has no value
|
|
if (unit.hasValue) {
|
|
throw new Error('Cannot convert to a unit with a value');
|
|
}
|
|
// Test if unit has a unit
|
|
if (!unit.hasUnit) {
|
|
throw new Error('Unit expected on the right hand side of function in');
|
|
}
|
|
|
|
var res = unit.copy();
|
|
res.value = x.value;
|
|
res.fixPrefix = true;
|
|
|
|
return res;
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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': []
|
|
};
|
|
|
|
/**
|
|
* Calculate the sine of a value, sin(x)
|
|
* @param {Number | Complex | Unit} x
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Computes the principal value of the arc tangent of y/x in radians, atan2(y,x)
|
|
* @param {Number | Complex} y
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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 inverse sine of a value, asin(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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} x
|
|
* @return {Number | Complex} 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
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Calculate the cosine of a value, cos(x)
|
|
* @param {Number | Complex | Unit} x
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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 tangent of a value, tan(x)
|
|
* @param {Number | Complex | Unit} x
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Calculate the inverse cosine of a value, acos(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Divide two values. x / y or divide(x, y)
|
|
* @param {Number | Complex | Unit} x
|
|
* @param {Number | Complex} y
|
|
* @return {Number | Complex | Unit} 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);
|
|
}
|
|
}
|
|
else 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);
|
|
}
|
|
}
|
|
else if (x instanceof Unit) {
|
|
if (isNumber(y)) {
|
|
var res = x.copy();
|
|
res.value /= y;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
throw newUnsupportedTypeError('divide', x, y);
|
|
}
|
|
|
|
/**
|
|
* Divide two complex values. 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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Round a value towards the nearest integer, round(x [, n])
|
|
* @param {Number | Complex} x
|
|
* @param {Number} [n] number of digits
|
|
* @return {Number | Complex} 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)
|
|
);
|
|
}
|
|
|
|
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)
|
|
);
|
|
}
|
|
|
|
throw newUnsupportedTypeError('round', x, n);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
}
|
|
|
|
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']
|
|
};
|
|
|
|
/**
|
|
* Round a value towards zero, fix(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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)
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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']
|
|
};
|
|
|
|
/**
|
|
* Add two values. x + y or add(x, y)
|
|
* @param {Number | Complex | Unit | String} x
|
|
* @param {Number | Complex | Unit | String} y
|
|
* @return {Number | Complex | Unit | String} 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.copy();
|
|
res.value += y.value;
|
|
res.fixPrefix = false;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (isString(x) || isString(y)) {
|
|
return x + y;
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Calculate the exponent of a value, exp(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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)
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
/**
|
|
* Calculate the square root of a value
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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))
|
|
);
|
|
}
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Check if value x is larger y, x > y
|
|
* In case of complex values, the absolute values of a and b are compared.
|
|
* @param {Number | Complex | Unit | String} x
|
|
* @param {Number | Complex | Unit | String} y
|
|
* @return {Boolean} 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;
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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 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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Inverse the sign of a value. -x or unaryminus(x)
|
|
* @param {Number | Complex | Unit} x
|
|
* @return {Number | Complex | Unit} 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.copy();
|
|
res.value = -x.value;
|
|
return res;
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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 a is smaller b, a < b
|
|
* In case of complex values, the absolute values of a and b are compared.
|
|
* @param {Number | Complex | Unit | String} x
|
|
* @param {Number | Complex | Unit | String} y
|
|
* @return {Boolean} 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;
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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 a is smaller value b. ' +
|
|
'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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Calculate the square root of a value
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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']
|
|
};
|
|
|
|
/**
|
|
* Calculate the natural logarithm of a value, log(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} res
|
|
*/
|
|
function log(x) {
|
|
if (arguments.length != 1) {
|
|
throw newArgumentsError('log', arguments.length, 1);
|
|
}
|
|
|
|
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)
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
throw newUnsupportedTypeError('log', x);
|
|
}
|
|
|
|
math.log = log;
|
|
|
|
/**
|
|
* Function documentation
|
|
*/
|
|
log.doc = {
|
|
'name': 'log',
|
|
'category': 'Arithmetic',
|
|
'syntax': [
|
|
'log(x)'
|
|
],
|
|
'description': 'Compute the natural logarithm of a value.',
|
|
'examples': [
|
|
'log(3.5)',
|
|
'a = log(2.4)',
|
|
'exp(a)',
|
|
'log(1000) / log(10)'
|
|
],
|
|
'seealso': [
|
|
'exp',
|
|
'logb',
|
|
'log10'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Calculates the power of x to y, x^y
|
|
* @param {Number | Complex} x
|
|
* @param {Number | Complex} y
|
|
* @return {Number | Complex} 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);
|
|
}
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
throw newUnsupportedTypeError('pow', x, y);
|
|
}
|
|
|
|
/**
|
|
* Caculates the power of x to y, x^y, for two complex values.
|
|
* @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 minus infinity, floor(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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)
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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']
|
|
};
|
|
|
|
/**
|
|
* Round a value towards plus infinity, ceil(x)
|
|
* @param {Number | Complex} x
|
|
* @return {Number | Complex} 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)
|
|
);
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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']
|
|
};
|
|
|
|
/**
|
|
* Multiply two values. x + y or multiply(x, y)
|
|
* @param {Number | Complex | Unit} x
|
|
* @param {Number | Complex | Unit} y
|
|
* @return {Number | Complex | Unit} res
|
|
*/
|
|
function multiply(x, y) {
|
|
var res;
|
|
|
|
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.copy();
|
|
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.copy();
|
|
res.value *= y;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
throw newUnsupportedTypeError('multiply', x, y);
|
|
}
|
|
|
|
/**
|
|
* Multiply two complex values. 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'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Subtract two values. x - y or subtract(x, y)
|
|
* @param {Number | Complex | Unit} x
|
|
* @param {Number | Complex | Unit} y
|
|
* @return {Number | Complex | Unit} 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.copy();
|
|
res.value -= y.value;
|
|
res.fixPrefix = false;
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// TODO: implement array support
|
|
// TODO: implement matrix support
|
|
|
|
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'
|
|
]
|
|
};
|
|
/**
|
|
* 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': []
|
|
};
|
|
|
|
/**
|
|
* Node
|
|
*/
|
|
function Node() {}
|
|
|
|
math.parser.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 math.parser.node.Function
|
|
* @param {String} [name]
|
|
* @param {function} fn
|
|
* @param {Node[]} params
|
|
* @extends {Node}
|
|
*/
|
|
function Function(name, fn, params) {
|
|
this.name = name;
|
|
this.fn = fn;
|
|
this.params = params;
|
|
}
|
|
|
|
Function.prototype = new Node();
|
|
|
|
math.parser.node.Function = Function;
|
|
|
|
/**
|
|
* Check whether the Function has one or multiple parameters set.
|
|
* @return {Boolean}
|
|
*/
|
|
Function.prototype.hasParams = function () {
|
|
return (this.params != undefined && this.params.length > 0);
|
|
};
|
|
|
|
/**
|
|
* Evaluate the symbol
|
|
* @return {*} result
|
|
* @override
|
|
*/
|
|
Function.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
|
|
*/
|
|
Function.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 math.parser.node.Constant
|
|
* @param {*} value
|
|
* @extends {Node}
|
|
*/
|
|
function Constant(value) {
|
|
this.value = value;
|
|
}
|
|
|
|
Constant.prototype = new Node();
|
|
|
|
math.parser.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 ? this.value.toString() : '';
|
|
};
|
|
|
|
/**
|
|
* @constructor math.parser.node.Block
|
|
* Holds a set with nodes
|
|
* @extends {Node}
|
|
*/
|
|
function Block() {
|
|
this.params = [];
|
|
this.visible = [];
|
|
}
|
|
|
|
Block.prototype = new Node();
|
|
|
|
math.parser.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.parser.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);
|
|
|
|
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.parser.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 returning a value
|
|
* Each function contains a parameter
|
|
* name of type String and value of
|
|
* type mathnotepad.fn.Link
|
|
* @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();
|
|
};
|
|
|
|
/**
|
|
* @License Apache 2.0 License
|
|
*
|
|
* @Author Jos de Jong
|
|
* @Date 2012-07-10
|
|
*/
|
|
|
|
/**
|
|
* 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.parser.node.Scope = Scope;
|
|
|
|
/**
|
|
* 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 symbol = function () {
|
|
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
|
|
* @return {function} symbol
|
|
*/
|
|
Scope.prototype.createDef = function (name) {
|
|
var symbol = this.defs[name];
|
|
if (!symbol) {
|
|
symbol = this.createSymbol(name);
|
|
this.defs[name] = symbol;
|
|
}
|
|
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(undefined, 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.set(parentScope ? parentScope.findDef(name) : undefined);
|
|
}
|
|
}
|
|
|
|
if (this.nestedScopes) {
|
|
this.nestedScopes.forEach(function (nestedScope) {
|
|
nestedScope.init();
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check whether this scope or any of its nested scopes contain a link to a
|
|
* symbol with given name
|
|
* @param {String} name
|
|
* @return {boolean} hasLink True if a link with given name is found
|
|
*/
|
|
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;
|
|
};
|
|
|
|
// TODO: do not use this.token, but a local variable var token for better speed? -> getToken() must return token.
|
|
// TODO: make all parse methods private
|
|
|
|
/**
|
|
* @constructor math.parser.Parser
|
|
* TODO: add comments to the Parser constructor
|
|
*/
|
|
function Parser() {
|
|
// token types enumeration
|
|
this.TOKENTYPE = {
|
|
NULL : 0,
|
|
DELIMITER : 1,
|
|
NUMBER : 2,
|
|
SYMBOL : 3,
|
|
UNKNOWN : 4
|
|
};
|
|
|
|
this.expr = ''; // current expression
|
|
this.index = 0; // current index in expr
|
|
this.c = ''; // current token character in expr
|
|
this.token = ''; // current token
|
|
this.token_type = this.TOKENTYPE.NULL; // type of the token
|
|
|
|
this.scope = new Scope();
|
|
}
|
|
|
|
math.parser.Parser = Parser;
|
|
|
|
/**
|
|
* Clear the scope with variables and functions
|
|
*/
|
|
Parser.prototype.clear = function () {
|
|
this.scope.clear();
|
|
};
|
|
|
|
/**
|
|
* Parse an expression end return the parsed function node.
|
|
* The node can be evaluated via node.eval()
|
|
* @param {String} expr
|
|
* @param {Scope} [scope]
|
|
* @return {Node} node
|
|
* @throws {Error}
|
|
*/
|
|
Parser.prototype.parse = function (expr, scope) {
|
|
this.expr = expr || '';
|
|
|
|
if (!scope) {
|
|
scope = this.scope;
|
|
}
|
|
|
|
return this.parse_start(scope);
|
|
};
|
|
|
|
/**
|
|
* Parse and evaluate the given expression
|
|
* @param {String} expr A string containing an expression, for example "2+3"
|
|
* @return {*} result The result, or undefined when the expression was
|
|
* empty
|
|
* @throws {Error}
|
|
*/
|
|
Parser.prototype.eval = function (expr) {
|
|
var result = undefined;
|
|
|
|
try {
|
|
var node = this.parse(expr);
|
|
result = node.eval();
|
|
} catch (err) {
|
|
result = err.toString ? err.toString() : err;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
Parser.prototype.getChar = function () {
|
|
this.index++;
|
|
this.c = this.expr.charAt(this.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
|
|
*/
|
|
Parser.prototype.getFirstChar = function () {
|
|
this.index = 0;
|
|
this.c = this.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 this.token_type and this.token
|
|
* @private
|
|
*/
|
|
Parser.prototype.getToken = function () {
|
|
this.token_type = this.TOKENTYPE.NULL;
|
|
this.token = '';
|
|
|
|
// skip over whitespaces
|
|
while (this.c == ' ' || this.c == '\t') { // space or tab
|
|
this.getChar();
|
|
}
|
|
|
|
// skip comment
|
|
if (this.c == '#') {
|
|
while (this.c != '\n' && this.c != '') {
|
|
this.getChar();
|
|
}
|
|
}
|
|
|
|
// check for end of expression
|
|
if (this.c == '') {
|
|
// token is still empty
|
|
this.token_type = this.TOKENTYPE.DELIMITER;
|
|
return;
|
|
}
|
|
|
|
// check for minus, comma, parentheses, quotes, newline, semicolon
|
|
if (this.c == '-' || this.c == ',' ||
|
|
this.c == '(' || this.c == ')' ||
|
|
this.c == '[' || this.c == ']' ||
|
|
this.c == '\"' || this.c == '\n' ||
|
|
this.c == ';' || this.c == ':') {
|
|
this.token_type = this.TOKENTYPE.DELIMITER;
|
|
this.token += this.c;
|
|
this.getChar();
|
|
return;
|
|
}
|
|
|
|
// check for operators (delimiters)
|
|
if (this.isDelimiter(this.c)) {
|
|
this.token_type = this.TOKENTYPE.DELIMITER;
|
|
while (this.isDelimiter(this.c)) {
|
|
this.token += this.c;
|
|
this.getChar();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// check for a number
|
|
if (this.isDigitDot(this.c)) {
|
|
this.token_type = this.TOKENTYPE.NUMBER;
|
|
while (this.isDigitDot(this.c)) {
|
|
this.token += this.c;
|
|
this.getChar();
|
|
}
|
|
|
|
// check for scientific notation like "2.3e-4" or "1.23e50"
|
|
if (this.c == 'E' || this.c == 'e') {
|
|
this.token += this.c;
|
|
this.getChar();
|
|
|
|
if (this.c == '+' || this.c == '-') {
|
|
this.token += this.c;
|
|
this.getChar();
|
|
}
|
|
|
|
// Scientific notation MUST be followed by an exponent
|
|
if (!this.isDigit(this.c)) {
|
|
// this is no legal number, exponent is missing.
|
|
this.token_type = this.TOKENTYPE.UNKNOWN;
|
|
}
|
|
|
|
while (this.isDigit(this.c)) {
|
|
this.token += this.c;
|
|
this.getChar();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// check for variables or functions
|
|
if (this.isAlpha(this.c)) {
|
|
this.token_type = this.TOKENTYPE.SYMBOL;
|
|
|
|
while (this.isAlpha(this.c) || this.isDigit(this.c))
|
|
{
|
|
this.token += this.c;
|
|
this.getChar();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// something unknown is found, wrong characters -> a syntax error
|
|
this.token_type = this.TOKENTYPE.UNKNOWN;
|
|
while (this.c != '') {
|
|
this.token += this.c;
|
|
this.getChar();
|
|
}
|
|
throw this.createSyntaxError('Syntax error in part "' + this.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
|
|
*/
|
|
Parser.prototype.isDelimiter = function (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
|
|
*/
|
|
Parser.prototype.isValidSymbolName = function (name) {
|
|
for (var i = 0, iMax = name.length; i < iMax; i++) {
|
|
var c = name.charAt(i);
|
|
//var valid = (this.isAlpha(c) || (i > 0 && this.isDigit(c))); // TODO
|
|
var valid = (this.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
|
|
*/
|
|
Parser.prototype.isAlpha = function (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
|
|
*/
|
|
Parser.prototype.isDigitDot = function (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
|
|
*/
|
|
Parser.prototype.isDigit = function (c) {
|
|
return ((c >= '0' && c <= '9'));
|
|
};
|
|
|
|
/**
|
|
* Start of the parse levels below, in order of precedence
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_start = function (scope) {
|
|
// get the first character in expression
|
|
this.getFirstChar();
|
|
|
|
this.getToken();
|
|
|
|
var node;
|
|
if (this.token == '') {
|
|
// empty expression
|
|
node = new Constant(undefined);
|
|
}
|
|
else {
|
|
node = this.parse_block(scope);
|
|
}
|
|
|
|
// check for garbage at the end of the expression
|
|
// an expression ends with a empty character '' and token_type DELIMITER
|
|
if (this.token != '') {
|
|
if (this.token_type == this.TOKENTYPE.DELIMITER) {
|
|
// user entered a not existing operator like "//"
|
|
|
|
// TODO: give hints for aliases, for example with "<>" give as hint " did you mean != ?"
|
|
throw this.createError('Unknown operator ' + this.token);
|
|
}
|
|
else {
|
|
throw this.createSyntaxError('Unexpected part "' + this.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
|
|
*/
|
|
Parser.prototype.parse_ans = function (scope) {
|
|
var expression = this.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
|
|
*/
|
|
Parser.prototype.parse_block = function (scope) {
|
|
var node, block, visible;
|
|
|
|
if (this.token != '\n' && this.token != ';' && this.token != '') {
|
|
node = this.parse_ans(scope);
|
|
}
|
|
|
|
while (this.token == '\n' || this.token == ';') {
|
|
if (!block) {
|
|
// initialize the block
|
|
block = new Block();
|
|
if (node) {
|
|
visible = (this.token != ';');
|
|
block.add(node, visible);
|
|
}
|
|
}
|
|
|
|
this.getToken();
|
|
if (this.token != '\n' && this.token != ';' && this.token != '') {
|
|
node = this.parse_ans(scope);
|
|
|
|
visible = (this.token != ';');
|
|
block.add(node, visible);
|
|
}
|
|
}
|
|
|
|
if (block) {
|
|
return block;
|
|
}
|
|
|
|
if (!node) {
|
|
node = this.parse_ans(scope);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* Parse a function assignment like "function f(a,b) = a*b"
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_function_assignment = function (scope) {
|
|
// TODO: keyword 'function' must become a reserved keyword
|
|
if (this.token_type == this.TOKENTYPE.SYMBOL && this.token == 'function') {
|
|
// get function name
|
|
this.getToken();
|
|
if (this.token_type != this.TOKENTYPE.SYMBOL) {
|
|
throw this.createSyntaxError('Function name expected');
|
|
}
|
|
var name = this.token;
|
|
|
|
// get parenthesis open
|
|
this.getToken();
|
|
if (this.token != '(') {
|
|
throw this.createSyntaxError('Opening parenthesis ( expected');
|
|
}
|
|
|
|
// get function variables
|
|
var functionScope = scope.createNestedScope();
|
|
var variableNames = [];
|
|
var variables = [];
|
|
while (true) {
|
|
this.getToken();
|
|
if (this.token_type == this.TOKENTYPE.SYMBOL) {
|
|
// store parameter
|
|
var variableName = this.token;
|
|
var variable = functionScope.createDef(variableName);
|
|
variableNames.push(variableName);
|
|
variables.push(variable);
|
|
}
|
|
else {
|
|
throw this.createSyntaxError('Variable name expected');
|
|
}
|
|
|
|
this.getToken();
|
|
if (this.token == ',') {
|
|
// ok, nothing to do, read next variable
|
|
}
|
|
else if (this.token == ')') {
|
|
// end of variable list encountered. break loop
|
|
break;
|
|
}
|
|
else {
|
|
throw this.createSyntaxError('Comma , or closing parenthesis ) expected"');
|
|
}
|
|
}
|
|
|
|
this.getToken();
|
|
if (this.token != '=') {
|
|
throw this.createSyntaxError('Equal sign = expected');
|
|
}
|
|
|
|
// parse the expression, with the correct function scope
|
|
this.getToken();
|
|
var expression = this.parse_range(functionScope);
|
|
var result = scope.createDef(name);
|
|
|
|
return new FunctionAssignment(name, variableNames, variables,
|
|
expression, result);
|
|
}
|
|
|
|
return this.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
|
|
*/
|
|
Parser.prototype.parse_assignment = function (scope) {
|
|
var linkExisted = false;
|
|
if (this.token_type == this.TOKENTYPE.SYMBOL) {
|
|
linkExisted = scope.hasLink(this.token);
|
|
}
|
|
|
|
var node = this.parse_range(scope);
|
|
|
|
if (this.token == '=') {
|
|
if (!(node instanceof Function)) {
|
|
throw this.createSyntaxError('Variable 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
|
|
this.getToken();
|
|
var expression = this.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
|
|
*/
|
|
Parser.prototype.parse_range = function (scope) {
|
|
var node = this.parse_conditions(scope);
|
|
|
|
/* TODO: implement range
|
|
if (this.token == ':') {
|
|
var params = [node];
|
|
|
|
while (this.token == ':') {
|
|
this.getToken();
|
|
params.push(this.parse_conditions(scope));
|
|
}
|
|
|
|
var fn = range;
|
|
var name = ':';
|
|
node = new Function(name, fn, params);
|
|
}
|
|
*/
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* conditions like and, or, in
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_conditions = function (scope) {
|
|
var node = this.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[this.token] !== undefined) {
|
|
// TODO: with all operators: only load one instance of the operator, use the scope
|
|
var name = this.token;
|
|
var fn = math[operators[name]];
|
|
|
|
this.getToken();
|
|
var params = [node, this.parse_bitwise_conditions(scope)];
|
|
node = new Function(name, fn, params);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* conditional operators and bitshift
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_bitwise_conditions = function (scope) {
|
|
var node = this.parse_comparison(scope);
|
|
|
|
/* TODO: implement bitwise conditions
|
|
var operators = {
|
|
'&' : 'bitwiseand',
|
|
'|' : 'bitwiseor',
|
|
// todo: bitwise xor?
|
|
'<<': 'bitshiftleft',
|
|
'>>': 'bitshiftright'
|
|
};
|
|
while (operators[this.token] !== undefined) {
|
|
var name = this.token;
|
|
var fn = math[operators[name]];
|
|
|
|
this.getToken();
|
|
var params = [node, this.parse_comparison()];
|
|
node = new Function(name, fn, params);
|
|
}
|
|
*/
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* comparison operators
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_comparison = function (scope) {
|
|
var node = this.parse_addsubtract(scope);
|
|
|
|
var operators = {
|
|
'==': 'equal',
|
|
'!=': 'unequal',
|
|
'<': 'smaller',
|
|
'>': 'larger',
|
|
'<=': 'smallereq',
|
|
'>=': 'largereq'
|
|
};
|
|
while (operators[this.token] !== undefined) {
|
|
var name = this.token;
|
|
var fn = math[operators[name]];
|
|
|
|
this.getToken();
|
|
var params = [node, this.parse_addsubtract(scope)];
|
|
node = new Function(name, fn, params);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* add or subtract
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_addsubtract = function (scope) {
|
|
var node = this.parse_multiplydivide(scope);
|
|
|
|
var operators = {
|
|
'+': 'add',
|
|
'-': 'subtract'
|
|
};
|
|
while (operators[this.token] !== undefined) {
|
|
var name = this.token;
|
|
var fn = math[operators[name]];
|
|
|
|
this.getToken();
|
|
var params = [node, this.parse_multiplydivide(scope)];
|
|
node = new Function(name, fn, params);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
|
|
/**
|
|
* multiply, divide, modulus
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_multiplydivide = function (scope) {
|
|
var node = this.parse_pow(scope);
|
|
|
|
var operators = {
|
|
'*': 'multiply',
|
|
'/': 'divide',
|
|
'%': 'mod',
|
|
'mod': 'mod'
|
|
};
|
|
while (operators[this.token] !== undefined) {
|
|
var name = this.token;
|
|
var fn = math[operators[name]];
|
|
|
|
this.getToken();
|
|
var params = [node, this.parse_pow(scope)];
|
|
node = new Function(name, fn, params);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* power
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_pow = function (scope) {
|
|
var node = this.parse_factorial(scope);
|
|
|
|
while (this.token == '^') {
|
|
var name = this.token;
|
|
var fn = pow;
|
|
this.getToken();
|
|
var params = [node, this.parse_factorial(scope)];
|
|
|
|
node = new Function(name, fn, params);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* Factorial
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_factorial = function (scope) {
|
|
var node = this.parse_unaryminus(scope);
|
|
|
|
while (this.token == '!') {
|
|
var name = this.token;
|
|
var fn = factorial;
|
|
this.getToken();
|
|
var params = [node];
|
|
|
|
node = new Function(name, fn, params);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* Unary minus
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_unaryminus = function (scope) {
|
|
if (this.token == '-') {
|
|
var name = this.token;
|
|
var fn = unaryminus;
|
|
this.getToken();
|
|
var params = [this.parse_plot(scope)];
|
|
|
|
return new Function(name, fn, params);
|
|
}
|
|
|
|
return this.parse_plot(scope);
|
|
};
|
|
|
|
/**
|
|
* parse plot
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_plot = function (scope) {
|
|
/* TODO: implement plot
|
|
if (this.token_type == this.TOKENTYPE.SYMBOL &&
|
|
this.token == 'plot') {
|
|
this.getToken();
|
|
|
|
// parse the parentheses and parameters of the plot
|
|
// the parameters are something like: plot(sin(x), cos(x), x)
|
|
var functions = [];
|
|
if (this.token == '(') {
|
|
var plotScope = scope.createNestedScope();
|
|
|
|
this.getToken();
|
|
functions.push(this.parse_range(plotScope));
|
|
|
|
// parse a list with parameters
|
|
while (this.token == ',') {
|
|
this.getToken();
|
|
functions.push(this.parse_range(plotScope));
|
|
}
|
|
|
|
if (this.token != ')') {
|
|
throw this.createSyntaxError('Parenthesis ) missing');
|
|
}
|
|
this.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 Function &&
|
|
!lastFunction.hasParams());
|
|
if (lastIsSymbol) {
|
|
functions.pop();
|
|
variable = lastFunction.fn;
|
|
}
|
|
}
|
|
return new plot(functions, variable, plotScope);
|
|
}
|
|
*/
|
|
|
|
return this.parse_symbol(scope);
|
|
};
|
|
|
|
/**
|
|
* parse symbols: functions, variables, constants, units
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_symbol = function (scope) {
|
|
if (this.token_type == this.TOKENTYPE.SYMBOL) {
|
|
var name = this.token;
|
|
|
|
this.getToken();
|
|
|
|
var link = scope.createLink(name);
|
|
var arguments = this.parse_arguments(scope); // TODO: not so nice to "misuse" creating a Function
|
|
var symbol = new Function(name, link, arguments);
|
|
|
|
/* TODO: parse arguments
|
|
// parse arguments
|
|
while (this.token == '(') {
|
|
symbol = this.parse_arguments(scope, symbol);
|
|
}
|
|
*/
|
|
return symbol;
|
|
}
|
|
|
|
return this.parse_string(scope);
|
|
};
|
|
|
|
/**
|
|
* parse symbol parameters
|
|
* @param {Scope} scope
|
|
* @return {Node[]} arguments
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_arguments = function (scope) {
|
|
var arguments = [];
|
|
if (this.token == '(') {
|
|
// TODO: in case of Plot, create a new scope.
|
|
|
|
this.getToken();
|
|
arguments.push(this.parse_range(scope));
|
|
|
|
// parse a list with parameters
|
|
while (this.token == ',') {
|
|
this.getToken();
|
|
arguments.push(this.parse_range(scope));
|
|
}
|
|
|
|
if (this.token != ')') {
|
|
throw this.createSyntaxError('Parenthesis ) missing');
|
|
}
|
|
this.getToken();
|
|
}
|
|
|
|
return arguments;
|
|
};
|
|
|
|
/**
|
|
* parse a string.
|
|
* A string is enclosed by double quotes
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_string = function (scope) {
|
|
if (this.token == '"') {
|
|
// string "..."
|
|
var str = '';
|
|
var tPrev = '';
|
|
while (this.c != '' && (this.c != '\"' || tPrev == '\\')) { // also handle escape character
|
|
str += this.c;
|
|
tPrev = this.c;
|
|
this.getChar();
|
|
}
|
|
|
|
this.getToken();
|
|
if (this.token != '"') {
|
|
throw this.createSyntaxError('End of string " missing');
|
|
}
|
|
this.getToken();
|
|
|
|
var res = new Constant(str);
|
|
|
|
/* TODO: implement string with arguments
|
|
// parse arguments
|
|
while (this.token == '(') {
|
|
res = this.parse_arguments(scope, res);
|
|
}
|
|
*/
|
|
|
|
return res;
|
|
}
|
|
|
|
return this.parse_matrix(scope);
|
|
};
|
|
|
|
/**
|
|
* parse the matrix
|
|
* @param {Scope} scope
|
|
* @return {Node} A MatrixNode
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_matrix = function (scope) {
|
|
/* TODO: implement matrix
|
|
if (this.token == '[') {
|
|
// matrix [...]
|
|
var matrix;
|
|
|
|
// skip newlines
|
|
this.getToken();
|
|
while (this.token == '\n') {
|
|
this.getToken();
|
|
}
|
|
|
|
// check if this is an empty matrix "[ ]"
|
|
if (this.token != ']') {
|
|
// this is a non-empty matrix
|
|
var params = [];
|
|
var r = 0, c = 0;
|
|
|
|
params[0] = [this.parse_range(scope)];
|
|
|
|
// the columns in the matrix are separated by commas, and the rows by dot-comma's
|
|
while (this.token == ',' || this.token == ';') {
|
|
if (this.token == ',') {
|
|
c++;
|
|
}
|
|
else {
|
|
r++;
|
|
c = 0;
|
|
params[r] = [];
|
|
}
|
|
|
|
// skip newlines
|
|
this.getToken();
|
|
while (this.token == '\n') {
|
|
this.getToken();
|
|
}
|
|
|
|
params[r][c] = this.parse_range(scope);
|
|
|
|
// skip newlines
|
|
while (this.token == '\n') {
|
|
this.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 this.createError('Number of columns must match ' +
|
|
'(' + params[r].length + ' != ' + cols + ')');
|
|
}
|
|
}
|
|
|
|
if (this.token != ']') {
|
|
throw this.createSyntaxError('End of matrix ] missing');
|
|
}
|
|
|
|
this.getToken();
|
|
matrix = new MatrixNode(params);
|
|
}
|
|
else {
|
|
// this is an empty matrix "[ ]"
|
|
this.getToken();
|
|
matrix = new MatrixNode();
|
|
}
|
|
|
|
// parse arguments
|
|
while (this.token == '(') {
|
|
matrix = this.parse_arguments(scope, matrix);
|
|
}
|
|
|
|
return matrix;
|
|
}
|
|
*/
|
|
|
|
return this.parse_number(scope);
|
|
};
|
|
|
|
/**
|
|
* parse a number
|
|
* @param {Scope} scope
|
|
* @return {Node} node
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_number = function (scope) {
|
|
if (this.token_type == this.TOKENTYPE.NUMBER) {
|
|
// this is a number
|
|
var number;
|
|
if (this.token == '.') {
|
|
number = 0.0;
|
|
} else {
|
|
number = Number(this.token);
|
|
}
|
|
this.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 (this.token_type == this.TOKENTYPE.SYMBOL) {
|
|
if (this.token == 'i' || this.token == 'I') {
|
|
value = new Complex(0, number);
|
|
this.getToken();
|
|
return new Constant(value);
|
|
}
|
|
|
|
if (Unit.isUnit(this.token)) {
|
|
value = new Unit(number, this.token);
|
|
this.getToken();
|
|
return new Constant(value);
|
|
}
|
|
|
|
throw this.createTypeError('Unknown unit "' + this.token + '"');
|
|
}
|
|
|
|
// just a regular number
|
|
var res = new Constant(number);
|
|
|
|
/* TODO: implement number with arguments
|
|
// parse arguments
|
|
while (this.token == '(') {
|
|
res = this.parse_arguments(scope, res);
|
|
}
|
|
*/
|
|
|
|
return res;
|
|
}
|
|
|
|
return this.parse_parentheses(scope);
|
|
};
|
|
|
|
/**
|
|
* parentheses
|
|
* @param {Scope} scope
|
|
* @return {Node} res
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_parentheses = function (scope) {
|
|
// check if it is a parenthesized expression
|
|
if (this.token == '(') {
|
|
// parentheses (...)
|
|
this.getToken();
|
|
var res = this.parse_range(scope); // start again
|
|
|
|
if (this.token != ')') {
|
|
throw this.createSyntaxError('Parenthesis ) expected');
|
|
}
|
|
this.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 (this.token == '(') {
|
|
res = this.parse_arguments(scope, res);
|
|
}
|
|
*/
|
|
|
|
return res;
|
|
}
|
|
|
|
return this.parse_end(scope);
|
|
};
|
|
|
|
/**
|
|
* Evaluated when the expression is not yet ended but expected to end
|
|
* @param {Scope} scope
|
|
* @return {Node} res
|
|
* @private
|
|
*/
|
|
Parser.prototype.parse_end = function (scope) {
|
|
if (this.token == '') {
|
|
// syntax error or unexpected end of expression
|
|
throw this.createSyntaxError('Unexpected end of expression');
|
|
} else {
|
|
throw this.createSyntaxError('Value expected');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Shortcut for getting the current row value (one based)
|
|
* Returns the line of the currently handled expression
|
|
* @private
|
|
*/
|
|
Parser.prototype.row = function () {
|
|
// 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
|
|
*/
|
|
Parser.prototype.col = function () {
|
|
return this.index - this.token.length + 1;
|
|
};
|
|
|
|
|
|
/**
|
|
* Build up an error message
|
|
* @param {String} message
|
|
* @return {String} message with row and column information
|
|
* @private
|
|
*/
|
|
Parser.prototype.createErrorMessage = function(message) {
|
|
var row = this.row();
|
|
var col = this.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
|
|
*/
|
|
Parser.prototype.createSyntaxError = function(message) {
|
|
return new SyntaxError(this.createErrorMessage(message));
|
|
};
|
|
|
|
/**
|
|
* Create an error
|
|
* @param {String} message
|
|
* @return {TypeError} instantiated error
|
|
* @private
|
|
*/
|
|
Parser.prototype.createTypeError = function(message) {
|
|
return new TypeError(this.createErrorMessage(message));
|
|
};
|
|
|
|
/**
|
|
* Create an error
|
|
* @param {String} message
|
|
* @return {Error} instantiated error
|
|
* @private
|
|
*/
|
|
Parser.prototype.createError = function(message) {
|
|
return new Error(this.createErrorMessage(message));
|
|
};
|
|
|
|
|
|
})();
|