mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser and offers an integrated solution to work with numbers, big numbers, complex numbers, units, and matrices.
431 lines (383 loc) • 9.75 kB
JavaScript
var util = require('../util/index'),
Unit = require('./Unit'),
number = util.number,
isNumber = util.number.isNumber,
isUnit = Unit.isUnit,
isString = util.string.isString;
/**
* @constructor Complex
*
* A complex value can be constructed in the following ways:
* var a = new Complex();
* var b = new Complex(re, im);
* var c = Complex.parse(str);
*
* Example usage:
* var a = new Complex(3, -4); // 3 - 4i
* a.re = 5; // a = 5 - 4i
* var i = a.im; // -4;
* var b = Complex.parse('2 + 6i'); // 2 + 6i
* var c = new Complex(); // 0 + 0i
* var d = math.add(a, b); // 5 + 2i
*
* @param {Number} re The real part of the complex value
* @param {Number} [im] The imaginary part of the complex value
*/
function Complex(re, im) {
if (!(this instanceof Complex)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
switch (arguments.length) {
case 0:
this.re = 0;
this.im = 0;
break;
case 1:
var arg = arguments[0];
if (typeof arg === 'object') {
if('re' in arg && 'im' in arg) {
var construct = new Complex(arg.re, arg.im); // pass on input validation
this.re = construct.re;
this.im = construct.im;
break;
} else if ('r' in arg && 'phi' in arg) {
var construct = Complex.fromPolar(arg.r, arg.phi);
this.re = construct.re;
this.im = construct.im;
break;
}
}
throw new SyntaxError('Object with the re and im or r and phi properties expected.');
case 2:
if (!isNumber(re) || !isNumber(im)) {
throw new TypeError('Two numbers expected in Complex constructor');
}
this.re = re;
this.im = im;
break;
default:
throw new SyntaxError('One, two or three arguments expected in Complex constructor');
}
}
/**
* Test whether value is a Complex value
* @param {*} value
* @return {Boolean} isComplex
*/
Complex.isComplex = function isComplex(value) {
return (value instanceof Complex);
};
// private variables and functions for the parser
var text, index, c;
function skipWhitespace() {
while (c == ' ' || c == '\t') {
next();
}
}
function isDigitDot (c) {
return ((c >= '0' && c <= '9') || c == '.');
}
function isDigit (c) {
return ((c >= '0' && c <= '9'));
}
function next() {
index++;
c = text.charAt(index);
}
function revert(oldIndex) {
index = oldIndex;
c = text.charAt(index);
}
function parseNumber () {
var number = '';
var oldIndex;
oldIndex = index;
if (c == '+') {
next();
}
else if (c == '-') {
number += c;
next();
}
if (!isDigitDot(c)) {
// a + or - must be followed by a digit
revert(oldIndex);
return null;
}
// get number, can have a single dot
if (c == '.') {
number += c;
next();
if (!isDigit(c)) {
// this is no legal number, it is just a dot
revert(oldIndex);
return null;
}
}
else {
while (isDigit(c)) {
number += c;
next();
}
if (c == '.') {
number += c;
next();
}
}
while (isDigit(c)) {
number += c;
next();
}
// check for exponential notation like "2.3e-4" or "1.23e50"
if (c == 'E' || c == 'e') {
number += c;
next();
if (c == '+' || c == '-') {
number += c;
next();
}
// Scientific notation MUST be followed by an exponent
if (!isDigit(c)) {
// this is no legal number, exponent is missing.
revert(oldIndex);
return null;
}
while (isDigit(c)) {
number += c;
next();
}
}
return number;
}
function parseComplex () {
// check for 'i', '-i', '+i'
var cnext = text.charAt(index + 1);
if (c == 'I' || c == 'i') {
next();
return '1';
}
else if ((c == '+' || c == '-') && (cnext == 'I' || cnext == 'i')) {
var number = (c == '+') ? '1' : '-1';
next();
next();
return number;
}
return null;
}
/**
* Parse a complex number from a string. For example Complex.parse("2 + 3i")
* will return a Complex value where re = 2, im = 3.
* Returns null if provided string does not contain a valid complex number.
* @param {String} str
* @returns {Complex | null} complex
*/
Complex.parse = function parse (str) {
text = str;
index = -1;
c = '';
if (!isString(text)) {
return null;
}
next();
skipWhitespace();
var first = parseNumber();
if (first) {
if (c == 'I' || c == 'i') {
// pure imaginary number
next();
skipWhitespace();
if (c) {
// garbage at the end. not good.
return null;
}
return new Complex(0, Number(first));
}
else {
// complex and real part
skipWhitespace();
var separator = c;
if (separator != '+' && separator != '-') {
// pure real number
skipWhitespace();
if (c) {
// garbage at the end. not good.
return null;
}
return new Complex(Number(first), 0);
}
else {
// complex and real part
next();
skipWhitespace();
var second = parseNumber();
if (second) {
if (c != 'I' && c != 'i') {
// 'i' missing at the end of the complex number
return null;
}
next();
}
else {
second = parseComplex();
if (!second) {
// imaginary number missing after separator
return null;
}
}
if (separator == '-') {
if (second[0] == '-') {
second = '+' + second.substring(1);
}
else {
second = '-' + second;
}
}
next();
skipWhitespace();
if (c) {
// garbage at the end. not good.
return null;
}
return new Complex(Number(first), Number(second));
}
}
}
else {
// check for 'i', '-i', '+i'
first = parseComplex();
if (first) {
skipWhitespace();
if (c) {
// garbage at the end. not good.
return null;
}
return new Complex(0, Number(first));
}
}
return null;
};
/**
* Create a complex number from polar coordinates
*
* Usage:
*
* Complex.fromPolar(r: Number, phi: Number) : Complex
* Complex.fromPolar({r: Number, phi: Number}) : Complex
*
* @param {*} args...
* @return {Complex}
*/
Complex.fromPolar = function fromPolar(args) {
switch (arguments.length) {
case 1:
var arg = arguments[0];
if(typeof arg === 'object') {
return Complex.fromPolar(arg.r, arg.phi);
}
throw new TypeError('Input has to be an object with r and phi keys.');
case 2:
var r = arguments[0],
phi = arguments[1];
if(isNumber(r)) {
if (isUnit(phi) && phi.hasBase(Unit.BASE_UNITS.ANGLE)) {
// convert unit to a number in radians
phi = phi.toNumber('rad');
}
if(isNumber(phi)) {
return new Complex(r * Math.cos(phi), r * Math.sin(phi));
}
throw new TypeError('Phi is not a number nor an angle unit.');
} else {
throw new TypeError('Radius r is not a number.');
}
default:
throw new SyntaxError('Wrong number of arguments in function fromPolar');
}
};
/*
* Return the value of the complex number in polar notation
* The angle phi will be set in the interval of [-pi, pi].
* @return {{r: number, phi: number}} Returns and object with properties r and phi.
*/
Complex.prototype.toPolar = function() {
return {
r: Math.sqrt(this.re * this.re + this.im * this.im),
phi: Math.atan2(this.im, this.re)
};
};
/**
* Create a copy of the complex value
* @return {Complex} clone
*/
Complex.prototype.clone = function clone () {
return new Complex(this.re, this.im);
};
/**
* Test whether this complex number equals an other complex value.
* Two complex numbers are equal when both their real and imaginary parts
* are equal.
* @param {Complex} other
* @return {boolean} isEqual
*/
Complex.prototype.equals = function equals (other) {
return (this.re === other.re) && (this.im === other.im);
};
/**
* Get a string representation of the complex number,
* with optional formatting options.
* @param {Object | Number | Function} [options] Formatting options. See
* lib/util/number:format for a
* description of the available
* options.
* @return {String} str
*/
Complex.prototype.format = function format (options) {
var str = '',
strRe = number.format(this.re, options),
strIm = number.format(this.im, options);
if (this.im == 0) {
// real value
str = strRe;
}
else if (this.re == 0) {
// purely complex value
if (this.im == 1) {
str = 'i';
}
else if (this.im == -1) {
str = '-i';
}
else {
str = strIm + 'i';
}
}
else {
// complex value
if (this.im > 0) {
if (this.im == 1) {
str = strRe + ' + i';
}
else {
str = strRe + ' + ' + strIm + 'i';
}
}
else {
if (this.im == -1) {
str = strRe + ' - i';
}
else {
str = strRe + ' - ' + strIm.substring(1) + 'i';
}
}
}
return str;
};
/**
* Get a string representation of the complex number.
* @return {String} str
*/
Complex.prototype.toString = function toString () {
return this.format();
};
// exports
module.exports = Complex;