So, I was writing a (personal) library for complex numbers. I know there’s an existing library for that, but some of the syntax kinda bugged me and, wouldn’t you know it, I already made it anyway.
But, I ended up overloading functions quite excessively. I only recently came to the discovery that, in JavaScript, you’re actually not supposed to overload functions. Apparently, it makes the code run slower, and is generally not supported, even though it can still be done. This is…not ideal. Because on top of a library that defines general functions for complex numbers, I also created a separate file which utilizes these functions to define certain special functions for complex arguments (e.g. the Gamma function, the erf function, the dilogarithm, elliptic integrals, etc.) I’ve attached a copy of my complex number library below, and even though it’s pretty long, I’ll summarize in the next paragraph what my main worries are. Because I still don’t know the full details on when function overloading is appropriate in JavaScript.
I overloaded the add, sub, mul, and div functions to work for both complex-with-complex and complex-with-double parameters. Overall, the compiler doesn’t seem to have too much trouble with this; it knows which one’s which. But, I still hear it can cause some problems, as the compiler has to constantly check which version of the function it’s using based on the parameters. In addition, I’m very cautious on whether or not my pow function works right, seeing as how I have three versions (one for complex exponents, one for double exponents, one for integer exponents), and it’s overloaded from a preexisting function for floats, which might make said preexisting function run slower. I’m also unsure on whether or not functions like the equals (which can take different numbers of the same argument) will work properly. They seem to work, but I’m not sure.
Like I said, I only recently discovered I wasn’t supposed to be overloading in java, and am still kinda hesitant to fix this problem, as it seems many of processing’s functions are already overloaded to begin with (rect, str, point), but on the other hand, those might be very specific cases. Help would be much appreciated, thanks!
//////////////////////////////////////////////////////////COMPLEX CLASS////////////////////////////////////////////////////////////////////////////////////////
class complex { //this object represents a complex number
//members
double re, im; //there are only two members of this class: the real and imaginary parts
//constructors
complex() { re=im=0; } //default constructor
complex(double x, double y) { re=x; im=y; } //normal constructor, inputs of real and imaginary part
complex(double x) { re=x; im=0; } //alternate constructor, inputs of real part with imaginary part assumed 0
//basic functions
complex copy() { return new complex(re,im); } //this creates a copy of the complex number on the left (without copying the memory address)
void set(double x, double y) { re=x; im=y; } //this allows us to change the values of our complex number
void set(complex z) { re=z.re; im=z.im; } //this allows us to set one complex equal to another, without copying references
boolean equals(complex a) { return (re==a.re && im==a.im); } //this is true if these two complex numbers are equal
boolean equals(double x, double y) { return (re==x && im==y ); } //this is true if the complex number formed on the inside is equal to the complex # we apply this to
boolean equals(double x) { return equals(x,0); } //this is true if the complex number equals the real input parameter
boolean isInf() { return (Math.abs(re)==inf || Math.abs(im)==inf); } //this is true if the complex number is infinite
//cast complex to a string
String Str() { //this generates this complex number in text form
if(isInf()) { //special case: input is infinite
if(Math.abs(im)==inf) { return "Complex Overflow"; } //if the complex part is infinite, return a complex overflow
else if(re<0) { return "Negative Overflow"; } //otherwise, if the real part is negative infinity, return a negative overflow
else { return "Overflow"; } //otherwise, return a positive overflow
}
complex sto=copy(); //this will store the input, but have any uneven roundoff errors removed
if(Math.abs(re)<1e-12 && Math.abs(im)>1e11*Math.abs(re)) { sto.re=0; } //if the real part is tiny but the imaginary part isn't, remove the real part
if(Math.abs(im)<1e-12 && Math.abs(re)>1e11*Math.abs(im)) { sto.im=0; } //and vice versa
String ret=""; //generate and empty string to fill with our number
if(sto.re!=0||sto.im==0) { ret+=str(sto.re); } //we will display the real part if it's nonzero or if the entire # is 0
if(sto.re!=0&&sto.im> 0) { ret+="+"; } //display a + separating the real & imaginary parts ONLY if there is both a real part & a positive imaginary part
if (str(sto.im).equals("1")) { ret+="i"; } //imag part is 1; print i instead of 1i
else if(str(sto.im).equals("-1")) { ret+="-i"; } //imag part is -1: print -i instead of -1i
else if(sto.im!=0) { ret+=str(sto.im)+"i"; } //otherwise, as long as the imaginary part isn't 0, display the imaginary part followed by i
return ret; //return the result
}
//basic math functions (single argument)
double abs2() { return re*re+im*im; } //absolute square
double abs() { return Math.hypot(re,im); } //absolute value
double arg() { //polar argument
if(im==0) { return re>=0 ? 0 : Math.PI; } //real number: return either 0 or π
if(re==0) { return sgn(im)*HALFPI; } //imaginary number: return ±π/2
return Math.atan2(im, re); //general case: find the angle with atan2
}
complex neg () { return new complex(-re,-im); } //negation
complex conj() { return new complex( re,-im); } //complex conjugate
complex mulI() { return new complex(-im, re); } //multiply by i
complex divI() { return new complex( im,-re); } //divide by i
complex inv() { //reciprocal
if(im==0) { return new complex(1.0/re); } //real number: return 1/(real part)
if(re==0) { return iTimes(-1.0/im); } //imaginary number: return -i/(imag part)
if(isInf()) { return new complex(); } //infinite number: return 0
if(Math.abs(abs2())==inf) { //the absolute square is an overflow:
double k=Math.max(Math.abs(re),Math.abs(im)); //pick the biggest of the two components (let's call this k)
complex sto2=div(k); //divide the number by k
return sto2.inv().div(k); //take the reciprocal, divide back by k
}
return conj().div(abs2()); //general case: return the conjugate over the absolute square
}
complex sq () { return mul(this); } //z²
complex cub() { return mul(sq()); } //z³
complex sqrt() { //√(z)
if(4096*Math.abs(im)>=Math.abs(re)) { //if the imaginary part isn't too small, (NOTE THE EQUALS, TO INCLUDE 0+0i)
double mag=abs(); //calculate the magnitude
return new complex(Math.sqrt(mag/2+re/2), sgn(im)*Math.sqrt(mag/2-re/2)); //use the standard formula to find the square root
}
//otherwise, the standard formula is imprecise and we must approximate
complex res; //this is our result
double root=Math.sqrt(Math.abs(re)); //find the √ of the real part
if(re>0) { res=new complex(root,im/(2*root)); } //real part is positive:
else { res=new complex(im/(2*root),root).mul(sgn(im)); } //real part is negative:
return res.add(div(res)).div(2); //return the mean between res and z/res
}
complex exp() { //e^z
if(im==0) { return new complex(Math.exp(re)); } //real number : return e^(real part)
if(re==0) { return new complex(Math.cos(im),Math.sin(im)); } //imag number : return cos(imag)+sin(imag)*i
return new complex(Math.cos(im),Math.sin(im)).mul(Math.exp(re)); //general case: return e^(real)*(cos(imag)+sin(imag)*i)
}
complex ln() { //ln(z)
if(re==0||im==0) { return new complex(Math.log(abs()), arg()); } //real/imaginary number: return ln|z|+arg(z)
if(abs2()==inf) { //absolute square is an overflow:
double k=Math.max(Math.abs(re),Math.abs(im)); //pick the biggest of the two components (let's call this k)
complex sto2=div(k); //divide the number by k
return sto2.ln().add(Math.log(k)); //take the log and add back ln(k)
}
return new complex(Math.log(abs2())/2, arg()); //general case: return ln(|z|²)/2+arg(z)i
}
int csgn() { //complex sgn
return (re>0 || re==0&&im>=0)?1:-1; //return 1 or -1, depending on if whether z==√(z²)
}
//simple math functions (multivalued)
complex add(double a) { return new complex(re+a, im ); } //overload the 4 basic arithmetic operations, both for
complex sub(double a) { return new complex(re-a, im ); } //one double & one complex...
complex mul(double a) { return new complex(re*a, im*a); }
complex div(double a) { return new complex(re/a, im/a); }
complex add(complex a) { return new complex(re+a.re, im+a.im); } //...and for 2 complexes
complex sub(complex a) { return new complex(re-a.re, im-a.im); }
complex mul(complex a) { return new complex(re*a.re-im*a.im, re*a.im+im*a.re); } //NOTE: there is a way to do this with only 3 products, but it might cause roundoff in some cases
complex div(complex a) { return mul(a.inv()); } //make sure to look into it and have it implemented if/when appropriate
void addeq(complex a) { set(add(a)); } // +=
void subeq(complex a) { set(sub(a)); } // -=
void muleq(complex a) { set(mul(a)); } // *=
void diveq(complex a) { set(div(a)); } // /=
void negeq() { set(neg()); } // negate-equals
void muleq(double a) { set(mul(a)); } // complex*=double
complex pow(int a) { //compute compex z ^ integer a (exponentiation by squaring)
if(im==0) { return new complex(dPow(re,a)); } //input is real: use the other implementation for doubles (resulting in about 1/4 the # of multiplications)
if(a<0) { return inv().pow(-a); } //a is negative: return (1/z)^(-a)
//general case:
complex ans=one.copy(); //return value: z^a (init to 1 in case a==0)
int ex=a; //copy of a
complex iter=this.copy(); //z ^ (2 ^ (whatever digit we're at))
boolean inits=false; //true once ans is initialized (to something other than 1)
while(ex!=0) { //loop through all a's digits (if a==0, exit loop, return 1)
if((ex&1)==1) {
if(inits) { ans.muleq(iter); } //mult ans by iter ONLY if this digit is 1
else { ans.set(iter); inits=true; } //if ans still = 1, set ans=iter (instead of multiplying by iter)
}
ex >>= 1; //remove the last digit
if(ex!=0) { iter=iter.sq(); } //square the iterator (unless the loop is over)
}
return ans; //return the result
}
complex pow(double a) { //complex z ^ double a
if(Math.abs(a)<=2147483647 && a%1==0) { return pow((int)a); } //if the exponent is an integer, there's a faster (and more accurate) way of doing this
if(im==0 && (re>0||a%1==0)) { return new complex(Math.pow(re,a)); } //if the base is real and non-negative, or it's negative but the exponent is an integer, we'll just use the built in power function
double mag, abssq=abs2(); //we now have to take the a-th power of the magnitude
if(abssq==inf) { mag=Math.pow(abs(),a ); } //if the absolute square is an overflow, use the absolute value
else { mag=Math.pow(abssq,a/2); } //otherwise, use the absolute square
complex unit=iTimes(a*arg()).exp(); //create a phaser with angle a*θ
return unit.mul(mag); //return the magnitude times the phaser
}
complex pow(complex a) { //complex z ^ complex a
if(equals(e)) { return a.exp(); } //z==e : return e^a
if(a.im==0) { return pow(a.re); } //a is real : return complex z ^ double a.re
return ln().mul(a).exp(); //general case: return e to the power of the log times a
}
complex cbrt() { //cube root of complex z
if(im==0) { return new complex(Math.cbrt(re)); } //z is real : return cbrt(re(z))
return iTimes(arg()/3).exp().mul(Math.cbrt(abs())); //general case: return e^(arg(z)/3)*cbrt(|z|)
}
}
/////////////////////////////////////////////////////////////////////////FRIEND FUNCTIONS////////////////////////////////////////////////////////////////////////////////////////
//here, we set global variables inf=∞, e=euler's number, i=i
double inf=1.0D/0;
double e=2.7182818284590459D;
double HALFPI=1.57079632679489662D;
complex i=new complex(0,1);
complex one=new complex(1);
//we will now overload (most of) the functions from complex so that the complex numbers can be called as parameters
complex iTimes(double x) { return new complex(0,x); } //a "constructor" that returns the input times i
String str(complex z) { return z.Str(); } //this generates the complex number in text form
double re(complex z) { return z.re; } //real & imaginary parts
double im(complex z) { return z.im; }
double abs2(complex z) { return z.abs2(); } //square absolute value
double abs(complex z) { return z.abs(); } //absolute value
double arg(complex z) { return z.arg(); } //polar argument
complex conj(complex z) { return z.conj(); } //complex conjugate
complex inv (complex z) { return z.inv(); } //1/z
complex sq (complex z) { return z.sq(); } //z²
complex cub (complex z) { return z.cub(); } //z cubed
complex sqrt(complex z) { return z.sqrt(); } //√(z)
complex exp(complex z) { return z.exp(); } //e^z
complex ln(complex z) { return z.ln(); } //ln(z)
complex add(complex a, double b) { return a.add(b); } //these functions perform the four basic arithmetic
complex sub(complex a, double b) { return a.sub(b); } //operations on one real and one complex number
complex mul(complex a, double b) { return a.mul(b); }
complex div(complex a, double b) { return a.div(b); }
complex add(double a, complex b) { return b.add(a); } //these functions perform the four basic arithmetic
complex sub(double a, complex b) { return b.neg().add(a); } //operations on one real and one complex number
complex mul(double a, complex b) { return b.mul(a); }
complex div(double a, complex b) { return inv(b).mul(a); }
complex add(complex a, complex b) { return a.add(b); } //these functions perform the four basic arithmetic
complex sub(complex a, complex b) { return a.sub(b); } //operations on two complex numbers
complex mul(complex a, complex b) { return a.mul(b); }
complex div(complex a, complex b) { return a.div(b); }
complex pow(complex a, int b) { return a.pow(b); } //this computes a to the power of an integer
complex pow(complex a, double b) { return a.pow(b); } //this computes a to the power of a real double
complex pow(complex a, complex b) { return a.pow(b); } //this computes a to the power of another complex number
complex cosh(complex a) { //cosh of complex a
if(a.im==0) { return new complex(Math.cosh(a.re)); } //If the input is real, just return the cosh
if(a.re==0) { return new complex(Math.cos (a.im)); } //If the input is imaginary, just return the cosine
if(a.re>709) { return exp(a.sub(0.6931471805599453D)); } //if the input is too large, return e^(|x|-ln(2))
if(a.re<-709) { return exp(a.add(0.6931471805599453D).neg()); }
complex part=exp(a);
return part.add(part.inv()).div(2); //otherwise, return (e^x+e^-x)/2
}
complex sinh(complex a) { //sinh of complex a
if(a.im==0) { return new complex(Math.sinh(a.re)); } //If the input is real, just return the sinh
if(a.re==0) { return iTimes(Math.sin(a.im)); } //If the input is imaginary, just return the sine times i
if(a.re>709) { return exp(a.sub(0.6931471805599453D)); } //if the input is too large, return ±e^(|x|-ln(2))
if(a.re<-709) { return exp(a.add(0.6931471805599453D).neg()).neg(); }
complex part=exp(a);
return part.sub(part.inv()).div(2); //otherwise, return (e^x-e^-x)/2
}
complex tanh(complex a) { //tanh of complex a
if(a.im==0) { return new complex(Math.tanh(a.re)); } //input is real : return the tanh
if(a.re==0) { return iTimes(Math.tan(a.im)); } //input is imaginary : return the tan times i
if(Math.abs(a.re)>300) { return new complex(a.csgn()); } //input is very large: return ±1
complex part=exp(a.mul(2));
return part.sub(1).div(part.add(1)); //general case: return (e^(2x)-1)/(e^(2x)+1)
}
complex cos(complex a) { return cosh(a.mulI()); } //3 main trig functions
complex sin(complex a) { return sinh(a.mulI()).divI(); }
complex tan(complex a) { return tanh(a.mulI()).divI(); }
complex acosh(complex a) { //arcosh of complex a
if(a.im==0&&Math.abs(a.re)<=1) { return iTimes(Math.acos(a.re)); } //a is real between -1 & 1: return the acos times i
if(a.abs2()>1E15D) { //input is very large: return an asymptotic approximation
complex sto=sq(inv(a)).div(4);
return ln(a).add(0.6931471805599453D).sub(sto.add(sq(sto).mul(1.5D)));
}
return ln(a.add( sqrt(sub(sq(a),1)).mul(a.csgn()) )); //general case: return ln(x+csgn(x)√(x²-1))
}
complex asinh(complex a) { //arsinh of complex a
if(a.re==0&&Math.abs(a.im)<=1) { return iTimes(Math.asin(a.im)); } //a is imaginary between -i & i: return asin times i
int sign=a.csgn();
if(a.abs2()>1E15D) { //input is very large: return an asymptotic approximation
complex sto=sq(inv(a)).div(4);
return ln(a.mul(sign)).add(0.6931471805599453D).add(sto.sub(sq(sto).mul(1.5D))).mul(sign);
}
return ln(a.mul(sign).add(sqrt(add(sq(a),1)))).mul(sign); //general case: return csgn(x)ln(|x|+√(x²+1))
}
complex atanh(complex a) { //artanh of complex a
if(a.re==0) { return iTimes(Math.atan(a.im)); } //a is imaginary: return atan times i
complex ans=ln(add(1,a).div(sub(1,a))).div(2); //general case : atanh(x)=ln((1+x)/(1-x))/2
if(a.im==0&&a.re>1) { ans=conj(ans); } //(special case): a is real & >1: take the conjugate
return ans; //return answer
}
complex acos(complex a) {
if(a.im==0 && Math.abs(a.re)<=1) { return new complex(Math.acos(a.re)); } //if a is within the bounds, return a simple arccosine
return asinh(a.mulI()).mulI().add(HALFPI); //otherwise, return π/2-arcsine
}
complex asin(complex a) { return asinh(a.mulI()).divI(); } //3 main inverse trig functions
complex atan(complex a) { return atanh(a.mulI()).divI(); }
//I tacked on some double functions here, mostly because I didn't feel like making a whole new tab dedicated to double functions
Double Double(String string) {
if(string.equals("∞")) { return inf; } // ∞: return ∞
if(string.equals("-∞")) { return -inf; } //-∞: return -∞
return new Double(string); //otherwise, return the number
}
String unsplice(String s, int chop1, int chop2) { //this takes a string and returns the same string with everything between chop1 and chop2 removed
return s.substring(0, chop1) + s.substring(chop2, s.length());
}
String str(double dub) { //this converts a double to a string, formatted so it doesn't have too many digits or any leading zeros
//first we check for some special cases:
if(dub==0) { return "0"; } //if the number is 0, return "0" (this is specified to make sure "-0" isn't returned)
else if(dub!=dub) { return "undefined"; } //if the number is NaN, return "undefined"
//we'll now convert the number into a string, and unsplice from it any trailing zeros
//(for the purposes of keeping comments short, a decimal point with nothing but zeros after it will also be considered a "trailing zero")
String res; //"RESult"; this is the string we will return
int cut1; //position of the 1st cut (right before the first trailing zero)
int cut2; //position of the 2nd cut (right after the coefficient)
if(Math.abs(dub)<1e10 && Math.abs(dub)>=1e-3) { //if the number is normal sized, we will return a coefficient w/ no base or exponent
res=String.format("%1.12f",dub); //format the number to 12 decimal places
cut2=res.length(); //since the text is only a coefficient, cut2 happens at the end of the string
}
else { //number is very large / small: return in full scientific notation
res=String.format("%1.12E",dub); //format the number to 12 decimal places with scientific notation
cut2=res.indexOf("E"); //the coefficient ends right before the "E"
//before we move on to remove any trailing zeros, let's first remove any unecessary +s or 0s in the exponent
if(res.charAt(cut2+2)=='0') { res=unsplice(res,cut2+2,cut2+3); } //if there's a 0 after the "E+" or "E-", remove it
if(res.charAt(cut2+1)=='+') { res=unsplice(res,cut2+1,cut2+2); } //if there's a + after the "E", remove it
}
cut1=cut2; //at first, we'll assume there are no trailing zeros
while(res.charAt(cut1-1)=='0') { cut1--; } //continually decrement cut1 until there are no more zeros before it
if (res.charAt(cut1-1)=='.') { cut1--; } //after that, if there's a decimal point before cut1, decrement cut1 once more
if(cut1!=cut2) { res=unsplice(res,cut1,cut2); } //now, we just remove everything between the two cuts (if applicable)
return res; //return the resulting string
}
double dPow(double d, int a) { //compute double d ^ integer a (exponentiation by squaring)
if(a<0) { return dPow(1/d,-a); } //a is negative: return (1/d)^(-a)
//general case:
double ans=1; //return value, d^a (init to 1 in case a==0)
int ex=a; //copy of a
double iter=d; //d ^ (2 ^ (whatever digit we're at))
boolean inits=false; //true once ans is initialized
while(ex!=0) { //loop through all of a's digits
if((ex&1)==1) {
if(inits) { ans*=iter; } //multiply ans by iter ONLY if this digit is 1
else { ans=iter; inits=true; } //if ans still = 1, set ans=iter (instead of multiplying by iter)
}
ex >>= 1; //remove the last digit
if(ex!=0) { iter*=iter; } //square the iterator (unless the loop is over)
}
return ans; //return the result
}
int sgn(double d) { return d>=0 ? 1 : -1; }