mirror of
https://github.com/josdejong/mathjs.git
synced 2025-12-08 19:46:04 +00:00
431 lines
9.5 KiB
JavaScript
431 lines
9.5 KiB
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;
|