/** * @license Complex.js v2.0.1 11/02/2016 * * Copyright (c) 2016, Robert Eisele (robert@xarg.org) * Dual licensed under the MIT or GPL Version 2 licenses. **/ /** * * This class allows the manipilation of complex numbers. * You can pass a complex number in different formats. Either as object, double, string or two integer parameters. * * Object form * { re: , im: } * { arg: , abs: } * { phi: , r: } * * Double form * 99.3 - Single double value * * String form * "23.1337" - Simple real number * "15+3i" - a simple complex number * "3-i" - a simple complex number * * Example: * * var c = new Complex("99.3+8i"); * c.mul({r: 3, i: 9}).div(4.9).sub(3, 2); * */ (function(root) { "use strict"; var P = {'re': 0, 'im': 0}; Math.cosh = Math.cosh || function(x) { return (Math.exp(x) + Math.exp(-x)) * 0.5; }; Math.sinh = Math.sinh || function(x) { return (Math.exp(x) - Math.exp(-x)) * 0.5; }; var parser_exit = function() { throw SyntaxError("Invalid Param"); }; /** * Calculates log(sqrt(a^2+b^2)) in a way to avoid overflows * * @param {number} a * @param {number} b * @returns {number} */ function logHypot(a, b) { var _a = Math.abs(a); var _b = Math.abs(b); if (a === 0) { return Math.log(_b); } if (b === 0) { return Math.log(_a); } if (_a < 3000 && _b < 3000) { return Math.log(a * a + b * b) * 0.5; } /* I got 4 ideas to compute this property without overflow: * * Testing 1000000 times with random samples for a,b ∈ [1, 1000000000] against a big decimal library to get an error estimate * * 1. Only eliminate the square root: (OVERALL ERROR: 3.9122483030951116e-11) Math.log(a * a + b * b) / 2 * * * 2. Try to use the non-overflowing pythagoras: (OVERALL ERROR: 8.889760039210159e-10) var fn = function(a, b) { a = Math.abs(a); b = Math.abs(b); var t = Math.min(a, b); a = Math.max(a, b); t = t / a; return Math.log(a) + Math.log(1 + t * t) / 2; }; * 3. Abuse the identity cos(atan(y/x) = x / sqrt(x^2+y^2): (OVERALL ERROR: 3.4780178737037204e-10) Math.log(a / Math.cos(Math.atan2(b, a))) * 4. Use 3. and apply log rules: (OVERALL ERROR: 1.2014087502620896e-9) Math.log(a) - Math.log(Math.cos(Math.atan2(b, a))) */ return Math.log(a / Math.cos(Math.atan2(b, a))); } var parse = function(a, b) { if (a === undefined || a === null) { P["re"] = P["im"] = 0; } else if (b !== undefined) { P["re"] = a; P["im"] = b; } else switch (typeof a) { case "object": if ("im" in a && "re" in a) { P["re"] = a["re"]; P["im"] = a["im"]; } else if ("abs" in a && "arg" in a) { P["re"] = a["abs"] * Math.cos(a["arg"]); P["im"] = a["abs"] * Math.sin(a["arg"]); } else if ("r" in a && "phi" in a) { P["re"] = a["r"] * Math.cos(a["phi"]); P["im"] = a["r"] * Math.sin(a["phi"]); } else { parser_exit(); } break; case "string": P["im"] = /* void */ P["re"] = 0; var tokens = a.match(/\d+\.?\d*e[+-]?\d+|\d+\.?\d*|\.\d+|./g); var plus = 1; var minus = 0; if (tokens === null) { parser_exit(); } for (var i = 0; i < tokens.length; i++) { var c = tokens[i]; if (c === ' ' || c === '\t' || c === '\n') { /* void */ } else if (c === '+') { plus++; } else if (c === '-') { minus++; } else if (c === 'i' || c === 'I') { if (plus + minus === 0) { parser_exit(); } if (tokens[i + 1] !== ' ' && !isNaN(tokens[i + 1])) { P["im"]+= parseFloat((minus % 2 ? "-" : "") + tokens[i + 1]); i++; } else { P["im"]+= parseFloat((minus % 2 ? "-" : "") + "1"); } plus = minus = 0; } else { if (plus + minus === 0 || isNaN(c)) { parser_exit(); } if (tokens[i + 1] === 'i' || tokens[i + 1] === 'I') { P["im"]+= parseFloat((minus % 2 ? "-" : "") + c); i++; } else { P["re"]+= parseFloat((minus % 2 ? "-" : "") + c); } plus = minus = 0; } } // Still something on the stack if (plus + minus > 0) { parser_exit(); } break; case "number": P["im"] = 0; P["re"] = a; break; default: parser_exit(); } if (isNaN(P["re"]) || isNaN(P["im"])) { // If a calculation is NaN, we treat it as NaN and don't throw //parser_exit(); } }; /** * @constructor * @returns {Complex} */ function Complex(a, b) { if (!(this instanceof Complex)) { return new Complex(a, b); } parse(a, b); // mutates P this["re"] = P["re"]; this["im"] = P["im"]; } Complex.prototype = { "re": 0, "im": 0, /** * Calculates the sign of a complex number * * @returns {Complex} */ "sign": function() { var abs = this["abs"](); return new Complex( this["re"] / abs, this["im"] / abs); }, /** * Adds two complex numbers * * @returns {Complex} */ "add": function(a, b) { parse(a, b); // mutates P return new Complex( this["re"] + P["re"], this["im"] + P["im"]); }, /** * Subtracts two complex numbers * * @returns {Complex} */ "sub": function(a, b) { parse(a, b); // mutates P return new Complex( this["re"] - P["re"], this["im"] - P["im"]); }, /** * Multiplies two complex numbers * * @returns {Complex} */ "mul": function(a, b) { parse(a, b); // mutates P // Besides the addition/subtraction, this helps having a solution for rational Infinity if (P['im'] === 0 && this['im'] === 0) { return new Complex(this['re'] * P['re'], 0); } return new Complex( this["re"] * P["re"] - this["im"] * P["im"], this["re"] * P["im"] + this["im"] * P["re"]); }, /** * Divides two complex numbers * * @returns {Complex} */ "div": function(a, b) { parse(a, b); // mutates P a = this["re"]; b = this["im"]; var c = P["re"]; var d = P["im"]; var t, x; // Divisor is zero if (0 === c && 0 === d) { return new Complex( (a !== 0) ? (a / 0) : 0, (b !== 0) ? (b / 0) : 0); } // Divisor is rational if (0 === d) { return new Complex(a / c, b / c); } if (Math.abs(c) < Math.abs(d)) { x = c / d; t = c * x + d; return new Complex( (a * x + b) / t, (b * x - a) / t); } else { x = d / c; t = d * x + c; return new Complex( (a + b * x) / t, (b - a * x) / t); } }, /** * Calculate the power of two complex numbers * * @returns {Complex} */ "pow": function(a, b) { parse(a, b); // mutates P a = this["re"]; b = this["im"]; if (a === 0 && b === 0) { return new Complex(0, 0); } var arg = Math.atan2(b, a); var loh = logHypot(a, b); if (P["im"] === 0) { if (b === 0 && a >= 0) { return new Complex(Math.pow(a, P["re"]), 0); } else if (a === 0) { switch (P["re"] % 4) { case 0: return new Complex(Math.pow(b, P["re"]), 0); case 1: return new Complex(0, Math.pow(b, P["re"])); case 2: return new Complex(-Math.pow(b, P["re"]), 0); case 3: return new Complex(0, -Math.pow(b, P["re"])); } } } /* I couldn"t find a good formula, so here is a derivation and optimization * * z_1^z_2 = (a + bi)^(c + di) * = exp((c + di) * log(a + bi) * = pow(a^2 + b^2, (c + di) / 2) * exp(i(c + di)atan2(b, a)) * =>... * Re = (pow(a^2 + b^2, c / 2) * exp(-d * atan2(b, a))) * cos(d * log(a^2 + b^2) / 2 + c * atan2(b, a)) * Im = (pow(a^2 + b^2, c / 2) * exp(-d * atan2(b, a))) * sin(d * log(a^2 + b^2) / 2 + c * atan2(b, a)) * * =>... * Re = exp(c * log(sqrt(a^2 + b^2)) - d * atan2(b, a)) * cos(d * log(sqrt(a^2 + b^2)) + c * atan2(b, a)) * Im = exp(c * log(sqrt(a^2 + b^2)) - d * atan2(b, a)) * sin(d * log(sqrt(a^2 + b^2)) + c * atan2(b, a)) * * => * Re = exp(c * logsq2 - d * arg(z_1)) * cos(d * logsq2 + c * arg(z_1)) * Im = exp(c * logsq2 - d * arg(z_1)) * sin(d * logsq2 + c * arg(z_1)) * */ a = Math.exp(P["re"] * loh - P["im"] * arg); b = P["im"] * loh + P["re"] * arg; return new Complex( a * Math.cos(b), a * Math.sin(b)); }, /** * Calculate the complex square root * * @returns {Complex} */ "sqrt": function() { var a = this["re"]; var b = this["im"]; var r = this["abs"](); var re, im; if (a >= 0 && b === 0) { return new Complex(Math.sqrt(a), 0); } if (a >= 0) { re = 0.5 * Math.sqrt(2.0 * (r + a)); } else { re = Math.abs(b) / Math.sqrt(2 * (r - a)); } if (a <= 0) { im = 0.5 * Math.sqrt(2.0 * (r - a)); } else { im = Math.abs(b) / Math.sqrt(2 * (r + a)); } return new Complex(re, b >= 0 ? im : -im); }, /** * Calculate the complex exponent * * @returns {Complex} */ "exp": function() { var tmp = Math.exp(this["re"]); if (this["im"] === 0) { //return new Complex(tmp, 0); } return new Complex( tmp * Math.cos(this["im"]), tmp * Math.sin(this["im"])); }, /** * Calculate the natural log * * @returns {Complex} */ "log": function() { var a = this["re"]; var b = this["im"]; if (b === 0 && a > 0) { //return new Complex(Math.log(a), 0); } return new Complex( logHypot(a, b), Math.atan2(b, a)); }, /** * Calculate the magniture of the complex number * * @returns {number} */ "abs": function() { var a = Math.abs(this["re"]); var b = Math.abs(this["im"]); if (a < 3000 && b < 3000) { return Math.sqrt(a * a + b * b); } if (a < b) { a = b; b = this["re"] / this["im"]; } else { b = this["im"] / this["re"]; } return a * Math.sqrt(1 + b * b); }, /** * Calculate the angle of the complex number * * @returns {number} */ "arg": function() { return Math.atan2(this["im"], this["re"]); }, /** * Calculate the sine of the complex number * * @returns {Complex} */ "sin": function() { var a = this["re"]; var b = this["im"]; return new Complex( Math.sin(a) * Math.cosh(b), Math.cos(a) * Math.sinh(b)); }, /** * Calculate the cosine * * @returns {Complex} */ "cos": function() { var a = this["re"]; var b = this["im"]; return new Complex( Math.cos(a) * Math.cosh(b), -Math.sin(a) * Math.sinh(b)); }, /** * Calculate the tangent * * @returns {Complex} */ "tan": function() { var a = 2 * this["re"]; var b = 2 * this["im"]; var d = Math.cos(a) + Math.cosh(b); return new Complex( Math.sin(a) / d, Math.sinh(b) / d); }, /** * Calculate the cotangent * * @returns {Complex} */ "cot": function() { var a = 2 * this["re"]; var b = 2 * this["im"]; var d = Math.cos(a) - Math.cosh(b); return new Complex( -Math.sin(a) / d, Math.sinh(b) / d); }, /** * Calculate the secant * * @returns {Complex} */ "sec": function() { var a = this["re"]; var b = this["im"]; var d = 0.5 * Math.cosh(2 * b) + 0.5 * Math.cos(2 * a); return new Complex( Math.cos(a) * Math.cosh(b) / d, Math.sin(a) * Math.sinh(b) / d); }, /** * Calculate the cosecans * * @returns {Complex} */ "csc": function() { var a = this["re"]; var b = this["im"]; var d = 0.5 * Math.cosh(2 * b) - 0.5 * Math.cos(2 * a); return new Complex( Math.sin(a) * Math.cosh(b) / d, -Math.cos(a) * Math.sinh(b) / d); }, /** * Calculate the complex arcus sinus * * @returns {Complex} */ "asin": function() { var a = this["re"]; var b = this["im"]; var t1 = new Complex( b * b - a * a + 1, -2 * a * b)['sqrt'](); var t2 = new Complex( t1['re'] - b, t1['im'] + a)['log'](); return new Complex(t2['im'], -t2['re']); }, /** * Calculate the complex arcus cosinus * * @returns {Complex} */ "acos": function() { var a = this["re"]; var b = this["im"]; var t1 = new Complex( b * b - a * a + 1, -2 * a * b)['sqrt'](); var t2 = new Complex( t1["re"] - b, t1["im"] + a)['log'](); return new Complex(Math.PI / 2 - t2["im"], t2["re"]); }, /** * Calculate the complex arcus tangent * * @returns {Complex} */ "atan": function() { var a = this["re"]; var b = this["im"]; if (a === 0) { if (b === 1) { return new Complex(0, Infinity); } if (b === -1) { return new Complex(0, -Infinity); } } var d = a * a + (1.0 - b) * (1.0 - b); var t1 = new Complex( (1 - b * b - a * a) / d, -2 * a / d).log(); return new Complex(-0.5 * t1["im"], 0.5 * t1["re"]); }, /** * Calculate the complex arcus cotangent * * @returns {Complex} */ "acot": function() { var a = this["re"]; var b = this["im"]; if (b === 0) { return new Complex(Math.atan2(1, a), 0); } var d = a * a + b * b; return (d !== 0) ? new Complex( a / d, -b / d).atan() : new Complex( (a !== 0) ? a / 0 : 0, (b !== 0) ?-b / 0 : 0).atan(); }, /** * Calculate the complex arcus secant * * @returns {Complex} */ "asec": function() { var a = this["re"]; var b = this["im"]; if (a === 0 && b === 0) { return new Complex(0, Infinity); } var d = a * a + b * b; return (d !== 0) ? new Complex( a / d, -b / d).acos() : new Complex( (a !== 0) ? a / 0 : 0, (b !== 0) ?-b / 0 : 0).acos(); }, /** * Calculate the complex arcus cosecans * * @returns {Complex} */ "acsc": function() { var a = this["re"]; var b = this["im"]; if (a === 0 && b === 0) { return new Complex(Math.PI / 2, Infinity); } var d = a * a + b * b; return (d !== 0) ? new Complex( a / d, -b / d).asin() : new Complex( (a !== 0) ? a / 0 : 0, (b !== 0) ?-b / 0 : 0).asin(); }, /** * Calculate the complex sinh * * @returns {Complex} */ "sinh": function() { var a = this["re"]; var b = this["im"]; return new Complex( Math.sinh(a) * Math.cos(b), Math.cosh(a) * Math.sin(b)); }, /** * Calculate the complex cosh * * @returns {Complex} */ "cosh": function() { var a = this["re"]; var b = this["im"]; return new Complex( Math.cosh(a) * Math.cos(b), Math.sinh(a) * Math.sin(b)); }, /** * Calculate the complex tanh * * @returns {Complex} */ "tanh": function() { var a = 2 * this["re"]; var b = 2 * this["im"]; var d = Math.cosh(a) + Math.cos(b); return new Complex( Math.sinh(a) / d, Math.sin(b) / d); }, /** * Calculate the complex coth * * @returns {Complex} */ "coth": function() { var a = 2 * this["re"]; var b = 2 * this["im"]; var d = Math.cosh(a) - Math.cos(b); return new Complex( Math.sinh(a) / d, -Math.sin(b) / d); }, /** * Calculate the complex coth * * @returns {Complex} */ "csch": function() { var a = this["re"]; var b = this["im"]; var d = Math.cos(2 * b) - Math.cosh(2 * a); return new Complex( -2 * Math.sinh(a) * Math.cos(b) / d, 2 * Math.cosh(a) * Math.sin(b) / d); }, /** * Calculate the complex sech * * @returns {Complex} */ "sech": function() { var a = this["re"]; var b = this["im"]; var d = Math.cos(2 * b) + Math.cosh(2 * a); return new Complex( 2 * Math.cosh(a) * Math.cos(b) / d, -2 * Math.sinh(a) * Math.sin(b) / d); }, /** * Calculate the complex asinh * * @returns {Complex} */ "asinh": function() { var tmp = this["im"]; this["im"] = -this["re"]; this["re"] = tmp; var res = this["asin"](); this["re"] = -this["im"]; this["im"] = tmp; tmp = res["re"]; res["re"] = -res["im"]; res["im"] = tmp; return res; }, /** * Calculate the complex asinh * * @returns {Complex} */ "acosh": function() { var tmp; var res = this["acos"](); if (res["im"] <= 0) { tmp = res["re"]; res["re"] = -res["im"]; res["im"] = tmp; } else { tmp = res["im"]; res["im"] = -res["re"]; res["re"] = tmp; } return res; }, /** * Calculate the complex atanh * * @returns {Complex} */ "atanh": function() { var a = this["re"]; var b = this["im"]; var noIM = a > 1 && b === 0; var oneMinus = 1 - a; var onePlus = 1 + a; var d = oneMinus * oneMinus + b * b; var x = (d !== 0) ? new Complex( (onePlus * oneMinus - b * b) / d, (b * oneMinus + onePlus * b) / d) : new Complex( (a !== -1) ? (a / 0) : 0, (b !== 0) ? (b / 0) : 0); var temp = x["re"]; x["re"] = logHypot(x["re"], x["im"]) / 2; x["im"] = Math.atan2(x["im"], temp) / 2; if (noIM) { x["im"] = -x["im"]; } return x; }, /** * Calculate the complex acoth * * @returns {Complex} */ "acoth": function() { var a = this["re"]; var b = this["im"]; if (a === 0 && b === 0) { return new Complex(0, Math.PI / 2); } var d = a * a + b * b; return (d !== 0) ? new Complex( a / d, -b / d).atanh() : new Complex( (a !== 0) ? a / 0 : 0, (b !== 0) ?-b / 0 : 0).atanh(); }, /** * Calculate the complex acsch * * @returns {Complex} */ "acsch": function() { var a = this["re"]; var b = this["im"]; if (b === 0) { return new Complex( (a !== 0) ? Math.log(a + Math.sqrt(a * a + 1)) : Infinity, 0); } var d = a * a + b * b; return (d !== 0) ? new Complex( a / d, -b / d).asinh() : new Complex( (a !== 0) ? a / 0 : 0, (b !== 0) ?-b / 0 : 0).asinh(); }, /** * Calculate the complex asech * * @returns {Complex} */ "asech": function() { var a = this["re"]; var b = this["im"]; if (a === 0 && b === 0) { return new Complex(Infinity, 0); } var d = a * a + b * b; return (d !== 0) ? new Complex( a / d, -b / d).acosh() : new Complex( (a !== 0) ? a / 0 : 0, (b !== 0) ?-b / 0 : 0).acosh(); }, /** * Calculate the complex inverse 1/z * * @returns {Complex} */ "inverse": function() { var a = this["re"]; var b = this["im"]; var d = a * a + b * b; return new Complex( a !== 0 ? a / d : 0, b !== 0 ?-b / d : 0); }, /** * Returns the complex conjugate * * @returns {Complex} */ "conjugate": function() { return new Complex(this["re"], -this["im"]); }, /** * Gets the negated complex number * * @returns {Complex} */ "neg": function() { return new Complex(-this["re"], -this["im"]); }, /** * Ceils the actual complex number * * @returns {Complex} */ "ceil": function(places) { places = Math.pow(10, places || 0); return new Complex( Math.ceil(this["re"] * places) / places, Math.ceil(this["im"] * places) / places); }, /** * Floors the actual complex number * * @returns {Complex} */ "floor": function(places) { places = Math.pow(10, places || 0); return new Complex( Math.floor(this["re"] * places) / places, Math.floor(this["im"] * places) / places); }, /** * Ceils the actual complex number * * @returns {Complex} */ "round": function(places) { places = Math.pow(10, places || 0); return new Complex( Math.round(this["re"] * places) / places, Math.round(this["im"] * places) / places); }, /** * Compares two complex numbers * * @returns {boolean} */ "equals": function(a, b) { parse(a, b); // mutates P return Math.abs(P["re"] - this["re"]) <= Complex["EPSILON"] && Math.abs(P["im"] - this["im"]) <= Complex["EPSILON"]; }, /** * Clones the actual object * * @returns {Complex} */ "clone": function() { return new Complex(this["re"], this["im"]); }, /** * Gets a string of the actual complex number * * @returns {string} */ "toString": function() { var a = this["re"]; var b = this["im"]; var ret = ""; if (isNaN(a) || isNaN(b)) { return "NaN"; } if (a !== 0) { ret+= a; } if (b !== 0) { if (a !== 0) { ret+= b < 0 ? " - " : " + "; } else if (b < 0) { ret+= "-"; } b = Math.abs(b); if (1 !== b) { ret+= b; } ret+= "i"; } if (!ret) return "0"; return ret; }, /** * Returns the actual number as a vector * * @returns {Array} */ "toVector": function() { return [this["re"], this["im"]]; }, /** * Returns the actual real value of the current object * * @returns {number|null} */ "valueOf": function() { if (this["im"] === 0) { return this["re"]; } return null; }, /** * Checks if the given complex number is not a number * * @returns {boolean} */ isNaN: function() { return isNaN(this['re']) || isNaN(this['im']); } }; Complex["ZERO"] = new Complex(0, 0); Complex["ONE"] = new Complex(1, 0); Complex["I"] = new Complex(0, 1); Complex["PI"] = new Complex(Math.PI, 0); Complex["E"] = new Complex(Math.E, 0); Complex['EPSILON'] = 1e-16; if (typeof define === "function" && define["amd"]) { define([], function() { return Complex; }); } else if (typeof exports === "object") { module["exports"] = Complex; } else { root["Complex"] = Complex; } })(this);