Source: complex.mjs

// @ts-check

/**
 * @typedef {{real: number, imag: number}} Cobj
 * An object representing a complex number with real and imaginary parts.
 */

/**
 * @typedef {Int8Array | Uint8Array | Uint8ClampedArray | Int16Array |
 * Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array |
 * BigInt64Array | BigUint64Array} TypedArray
 * A union type representing all possible TypedArrays.
 */

/**
 * @template {Typed<TypedArray>} T
 * @typedef {T} Typed A generic type representing a TypedArray.
 */

/**
 * @template T
 * @typedef {function(new:T, ...any)} Newable
 * A constructor type that can be instantiated with the `new` keyword.
 */

/**
 * @typedef {function(new:Complex, number, number)} TC
 * A constructor for typeof Complex.
 * @param {number} [re=0] The real part of the complex number.
 * @param {number} [im=0] The imaginary part of the complex number.
 */

/**
 * @template {Ctor<TC>} C
 * @typedef {C} Ctor A generic type representing a `Complex` constructor.
 */

/**
 * @template {TC} C
 * @typedef {InstanceType<C>} Cinst
 * A generic type representing an instance of a constructor `Complex`.
 */

/**
 * @template {Ctor<TC>} C
 * @typedef {Ctor<C> | {} | void} Cvoid
 * A union type representing either a constructor `Complex`, an object, or void.
 * Used to hint that a static method can also be invoked "bare".
 */

/**
 * Type of callback Complex.compare().
 * @callback Compare
 * @param {Complex} a The first complex number to compare.
 * @param {Cobj} b The second complex number to compare.
 * @return {number} -1 if a < b, 0 if a == b, or +1 if a > b.
 */

/**
 * An immutable complex number class with real and imaginary parts.
 * @name Complex
 * @class Complex
 */
export default class Complex {
    /**
     * The cached hashcode for this complex number.
     * @type {number | null}
     */
    #hashCode = null;

    /**
     * The cached string representation for this complex number.
     * @type {string | null}
     */
    #$ = null;

    /**
     * Create a new instance of the Complex class.
     * @constructor
     * @param {number} [re=0] The real part of the complex number.
     * @param {number} [im=0] The imaginary part of the complex number.
     */
    constructor(re = 0, im = 0) {
        /**
         * The real part of the complex number.
         * @readonly
         * @const
         * @member {number}
         */
        this.real = +re || 0;

        /**
         * The imaginary part of the complex number.
         * @readonly
         * @const
         * @member {number}
         */
        this.imag = +im || 0;

        /**
         * The constructor function for this instance.
         * @type {Newable<this> & typeof Complex}
         */
        this.constructor;

        /**
         * The prototype object of this instance.
         * @type {this}
         */
        this.__proto__;

        /*
         * Define the real & imag properties of this Complex object as readonly.
         * And thus making class Complex itself immutable:
         */
        Object.defineProperties(this, Complex._IMMUTABLE);
    }

    /**
     * A property descriptor object that defines a property as read-only
     * by setting its attribute writable to false.
     * @protected
     * @static
     * @readonly
     * @const {PropertyDescriptor}
     */
    static _READONLY = Object.freeze({ writable: false });

    /**
     * Map of property descriptors that define the real & imag properties of a
     * Complex object as read-only; so instances of class Complex are immutable.
     * @protected
     * @static
     * @readonly
     * @const {PropertyDescriptorMap}
     */
    static _IMMUTABLE = Object.freeze({
        real: this._READONLY, imag: this._READONLY
    });

    /**
     * Property's name to check if an object is a TypedArray.
     * @protected
     * @static
     * @readonly
     * @const {'BYTES_PER_ELEMENT'}
     */
    static _TYPED = 'BYTES_PER_ELEMENT';

    /**
     * Static property's name to check if some constructor is of type Complex.
     * @protected
     * @static
     * @readonly
     * @const {'fromRI'}
     */
    static _METHOD = 'fromRI';

    /**
     * A Float64Array buffer used to store the binary representation of the
     * real or imaginary part of a complex number for hashcode computation.
     * @protected
     * @static
     * @readonly
     * @type {Float64Array}
     */
    static _hashFBuf = new Float64Array(1);

    /**
     * A Uint8Array buffer view that shares the same memory block as _hashFBuf,
     * used to access the individual bytes of the real or imaginary part of a
     * complex number for hashcode computation.
     * @protected
     * @static
     * @readonly
     * @type {Uint8Array}
     */
    static _hashIBuf = new Uint8Array(this._hashFBuf.buffer);

    /**
     * A complex number representing zero.
     * @static
     * @readonly
     * @const {Complex}
     */
    static ZERO = new this().resetCaches();

    /**
     * A complex number representing 1 + 0i.
     * @static
     * @readonly
     * @const {Complex}
     */
    static _1_0i = new this(1, 0).resetCaches();

    /**
     * A complex number representing -1 + 0i.
     * @static
     * @readonly
     * @const {Complex}
     */
    static _1_0i_neg = new this(-1, 0).resetCaches();

    /**
     * A complex number representing 0 + 1i.
     * @static
     * @readonly
     * @const {Complex}
     */
    static _0_1i = new this(0, 1).resetCaches();

    /**
     * A complex number representing 0 - 1i.
     * @static
     * @readonly
     * @const {Complex}
     */
    static _0_1i_neg = new this(0, -1).resetCaches();

    /**
     * Pi. Ratio of the circumference of a circle to its diameter.
     * @static
     * @readonly
     * @const {3.141592653589793}
     */
    static PI = /** @type {3.141592653589793} */ (Math.PI);

    /**
     * Tau. Ratio of the circumference of a circle to its radius (2pi).
     * @static
     * @readonly
     * @const {6.283185307179586}
     */
    static TAU = /** @type {6.283185307179586} */ (2 * this.PI);

    /**
     * The conversion factor from radians to degrees (180 / Math.PI).
     * @static
     * @readonly
     * @const {57.29577951308232}
     */
    static RAD_TO_DEG = /** @type {57.29577951308232} */ (180 / this.PI);

    /**
     * The conversion factor from degrees to radians (Math.PI / 180).
     * @static
     * @readonly
     * @const {.017453292519943295}
     */
    static DEG_TO_RAD = /** @type {.017453292519943295} */ (this.PI / 180);

    /**
     * The default maximum difference allowed when testing complex numbers.
     * @type {number}
     * @default
     */
    static #epsilon = 1E-8;

    /**
     * Get the default maximum difference allowed when testing complex numbers.
     * @static
     * @return {number} The maximum difference allowed.
     */
    static get epsilon() {
        return Complex.#epsilon;
    }

    /**
     * Set the default maximum difference allowed when testing complex numbers.
     * @static
     * @param {number} norm The max diff allowed. Must be > 0 and < 1.
     * @throws {RangeError} If epsilon to be set is <= 0 or >=1.
     */
    static set epsilon(norm) {
        if (norm <= 0 || norm >= 1)
            throw RangeError('Range must be between 0 & 1 (exclusive)!');

        Complex.#epsilon = +norm;
    }

    /**
     * Convert an angle in radians to degrees.
     * @static
     * @param {number} rad The angle in radians.
     * @return {number} The angle in degrees.
     */
    static toDeg(rad) {
        return rad * Complex.RAD_TO_DEG;
    }

    /**
     * Convert an angle in degrees to radians.
     * @static
     * @param {number} deg The angle in degrees.
     * @return {number} The angle in radians.
     */
    static toRad(deg) {
        return deg * Complex.DEG_TO_RAD;
    }

    /**
     * Callback for comparing 2 complex numbers by their real & imaginary parts.
     * @static
     * @param {Complex} a The first complex number to compare.
     * @param {Cobj} b The second complex number to compare.
     * @return {number} -1 if a < b, 0 if a == b, or +1 if a > b.
     */
    static compare(a, b) {
        return a.compareTo(b);
    }

    /**
     * Returns a new array with the elements sorted by their real value.
     * Numbers w/ same real value are then sorted by their imaginary value.
     * The original array or iterable is unchanged.
     * @static
     * @template {Cinst<TC>} I Instance of Complex type.
     * @param {Iterable<I>} z The original array or iterable.
     * @param {Compare} [sort=Complex.compare] The sorting callback.
     * @return {I[]} A new sorted array.
     */
    static sort(z, sort = Complex.compare) {
        return [...z].sort(sort);
    }

    /**
     * @template {Typed<TypedArray>} T Specific type of the passed TypedArray.
     * @overload
     * @param {T} arr The TypedArray to clone.
     * @return {T} A new TypedArray of same datatype containing
     * the elements of the input TypedArray.
     *
    */
    /**
     * @template A The type of elements in the Array or Iterable object.
     * @overload
     * @param {Iterable<A>} arr The Array or Iterable object to clone.
     * @return {A[]} A new Array containing the elements of the input Array
     * or Iterable object.
     */
    /**
     * @static
     * @param {T | Iterable<A>} arr Array, TypedArray or Iterable to clone.
     * @description Clones an Array, a TypedArray or an Iterable object
     * as an Array or TypedArray of same datatype.
     */
    static arrClone(arr) {
        return Complex._TYPED in arr ? arr.slice() : [...arr];
    }

    /**
     * Check if a function is a Complex datatype constructor.
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @param {Cvoid<C>} c The constructor function to check.
     * @return {c is Newable<Cinst<C>>}
     * True if param c is or inherits from Complex class.
     */
    static isComplex(c) {
        return !!c && Complex._METHOD in c;
    }

    /**
     * Get input constructor c if typeof Complex; or fallback to Complex.
     * @protected
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @template {Newable<Cinst<C>>} N Constructor for typeof Complex.
     * @param {Cvoid<C>} c The constructor function to verify.
     * @return {N} Param c if type Complex; otherwise a Complex constructor.
     */
    static _ctor(c) {
        return /** @type {N} */ (Complex.isComplex(c) ? c : this || Complex);
    }

    /**
     * Create a new instance of a given Complex constructor function.
     * @protected
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @param {Cvoid<C>} c An existing Complex constructor.
     * @param {*} args Real & imaginary number parts.
     * @return {Cinst<C>} A new instance of a complex number.
     */
    static _new(c, ...args) {
        return new (this._ctor(c))(...args);
    }

    /**
     * Return a complex number given values for the real & imaginary parts.
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @this {Cvoid<C>} Constructor for a Complex object or its subclass.
     * @param {number} [re=0] The real part.
     * @param {number} [im=0] The imaginary part.
     * @param {*} _args Extended parameters.
     * @return {Cinst<C>} The complex number of given real & imaginary parts.
     */
    static fromRI(re, im, ..._args) {
        return Complex._new(this, re, im, ..._args);
    }

    /**
     * Return a new complex number using given object's props real & imag.
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @this {Cvoid<C>} Constructor for a Complex object or its subclass.
     * @param {Cobj} z The Complex object to copy from.
     * @param {*} _args Extended parameters.
     * @return {Cinst<C>} Duplicate of the given Complex object.
     */
    static fromZ({ real, imag }, ..._args) {
        return Complex._new(this, real, imag, ..._args);
    }

    /**
     * Return a complex number given polar coordinates.
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @this {Cvoid<C>} Constructor for a Complex object or its subclass.
     * @param {number} mod The length of the complex number.
     * @param {number} rad The angle subtended by the number.
     * @param {*} _args Extended parameters.
     * @return {Cinst<C>} Complex of given length & orientation.
     */
    static fromPolar(mod, rad, ..._args) {
        const re = mod * Math.cos(rad), im = mod * Math.sin(rad);
        return Complex._new(this, re, im, ..._args);
    }

    /**
     * Return a complex number of a given size but random orientation.
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @this {Cvoid<C>} Constructor for a Complex object or its subclass.
     * @param {number} [mod=1] The length of the complex number.
     * @param {*} _args Extended parameters.
     * @return {Cinst<C>} Complex of given length & random orientation.
     */
    static fromRandom(mod = 1, ..._args) {
        const
          ang = Complex.TAU * Math.random(),
          re = mod * Math.cos(ang),
          im = mod * Math.sin(ang);

        return Complex._new(this, re, im, ..._args);
    }

    /**
     * Given an array of complex numbers return a new array containing a clone
     * of those that represent real numbers. The original array is unchanged.
     * @static
     * @template {Ctor<TC>} C Extends typeof Complex.
     * @this {Cvoid<C>} Constructor for a Complex object or its subclass.
     * @param {Iterable<Complex>} z The original array or iterable.
     * @param {number} [epsilon=Complex.epsilon] Max diffference allowed.
     * @param {*} _args Extended parameters.
     * @return {Array<Cinst<C>>} Array of Complex representing real numbers.
     */
    static filterRealRoots(z, epsilon = Complex.#epsilon, ..._args) {
        const ctor = Complex._ctor(this), r = [];

        for (const c of z)
            c.isReal(epsilon) && r.push(new ctor(c.real, 0, ..._args));

        return r;
    }

    /**
     * If the original array contains more than 1 element the new array is
     * returned with duplicates removed leaving just unique vales.
     * In all cases a clone is returned and the original array is unchanged.
     * @static
     * @template {Cinst<TC>} I Instance of Complex type.
     * @param {Iterable<I>} z The original array or iterable.
     * @param {number} [epsilon=Complex.epsilon] Max diffference allowed.
     * @return {I[]} A new array with duplicates removed.
     */
    static removeDuplicates(z, epsilon = Complex.#epsilon) {
        const zs = [...z], len = zs.length;
        if (len < 2) return zs; // Can't have duplicates, so exit now!

        const nz = [ zs.sort(Complex.compare)[0] ];
        var i = 0, idx = 0;

        while (++i < len) {
            const c = zs[i];
            if (!c.equals(nz[idx], epsilon)) nz.push(c), ++idx;
        }

        return nz;
    }

    /**
     * Compute the absolute value (modulus or magnitude) of this complex number.
     * @return {number} The absolute length value of this complex number.
     */
    abs() {
        return Math.sqrt(this.real * this.real + this.imag * this.imag);
        // return Math.hypot(this.real, this.imag); // Likely slower!
    }

    /**
     * Compute the squared absolute value of this complex number.
     * @return {number} The squared absolute value of this complex number.
     */
    abs_squared() {
        return this.real * this.real + this.imag * this.imag;
    }

    /**
     * Add another complex number or a real number to this complex number.
     * @param {Cobj | number} n The complex or real number to add.
     * @return {this} A new complex number representing the sum.
     */
    add(n) {
        const { constructor, real, imag } = this;

        return typeof n == 'object' ?
            new constructor(+n.real + real, +n.imag + imag) :
            new constructor(+n + real, imag);
    }

    /**
     * Calculate the inverse cosine of this complex number.
     * @return {this} A new complex number representing the result.
     */
    acos() {
        const z = this.squared().sub(1).sqrt().add(this).log().
                       mult(Complex._0_1i_neg);

        return z.real < 0 && z.negate() || z;
    }

    /**
     * Calculate the inverse hyperbolic cosine of this complex number.
     * @return {this} A new complex number representing the result.
     */
    acosh() {
        const z = this.squared().sub(Complex._1_0i).sqrt().add(this).log();
        return z.real < 0 && z.negate() || z;
    }

    /**
     * Calculate the argument (phase) of this complex number in radians.
     * @return {number} The argument of this complex number in radians.
     */
    arg() {
        return Math.atan2(this.imag, this.real);
    }

    /**
     * Calculate the inverse sine of this complex number.
     * @return {this} A new complex number representing the result.
     */
    asin() {
        const t2 = this.squared().sub(Complex._1_0i).negate().sqrt();
        return this.mult(Complex._0_1i).add(t2).log().mult(Complex._0_1i_neg);
    }

    /**
     * Calculate the inverse hyperbolic sine of this complex number.
     * @return {this} A new complex number representing the result.
     */
    asinh() {
        return this.squared().add(Complex._1_0i).sqrt().add(this).log();
    }

    /**
     * Calculate the inverse tangent of this complex number.
     * @return {this} A new complex number representing the result.
     */
    atan() {
        const _01 = this.constructor.fromZ(Complex._0_1i);
        return _01.half().mult( _01.add(this).div( _01.sub(this) ).log() );
    }

    /**
     * Calculate the inverse hyperbolic tangent of this complex number.
     * @return {this} A new complex number representing the result.
     */
    atanh() {
        const _10 = this.constructor.fromZ(Complex._1_0i);
        return _10.add(this).div( _10.sub(this) ).log().half();
    }

    /**
     * Calculate the cube root of this complex number.
     * @return {this} A new complex number representing the result.
     */
    cbrt() {
        const { cbrt, atan2, cos, sin } = Math, mag = cbrt( this.abs() );
        var theta = atan2(this.imag, this.real);
        if (theta < 0) theta += Complex.TAU;
        return new this.constructor(cos(theta /= 3) * mag, sin(theta) * mag);
    }

    /**
     * Creates a copy of the same type as this complex instance.
     * Although it's unnecessary given a Complex object is already immutable.
     * @return {this} A new complex object with the same real and imaginary
     * values as this instance.
     */
    clone() {
        return new this.constructor(...this).resetCaches();
    }

    /**
     * Calculate the conjugate of this complex number.
     * @return {this} A new complex number representing the conjugate.
     */
    conj() {
        return new this.constructor(this.real, -this.imag);
    }

    /**
     * This method compares this complex number with another.
     * The main purpose of this is to define an appropriate sort order.
     * Returns:
     *  Zero if the numbers are the same when using the equals method.
     *  Negative if considered less than z.
     *  Positive if considered larger than z.
     * @param {Cobj} z the complex number to compare with.
     * @return {number} -1, 0 or +1
     */
    compareTo(z) {
        return this.equals(z) ? 0 : this.real - z.real || this.imag - z.imag;
    }

    /**
     * Calculate the cosine of this complex number.
     * @return {this} A new complex number representing the result.
     */
    cos() {
        const t = this.mult(Complex._0_1i);
        return t.exp().add( t.negate().exp() ).half();
    }

    /**
     * Calculate the cosecant of this complex number.
     * @return {this} A new complex number representing the result.
     */
    cosec() {
        return this.sin().pow(-1);
    }

    /**
     * Calculate the hyperbolic cosecant of this complex number.
     * @return {this} A new complex number representing the result.
     */
    cosech() {
        return this.sinh().pow(-1);
    }

    /**
     * Calculate the hyperbolic cosine of this complex number.
     * @return {this} A new complex number representing the result.
     */
    cosh() {
        return this.exp().add( this.negate().exp() ).half();
    }

    /**
     * Returns the cotangent of this complex number.
     * @return {this} The cotangent of this complex number.
     */
    cot() {
        return this.cos().div( this.sin() );
    }

    /**
     * Returns the hyperbolic cotangent of this complex number.
     * @return {this} The hyperbolic cotangent of this complex number.
     */
    coth() {
        return this.cosh().div( this.sinh() );
    }

    /**
     * Returns the cube of this complex number.
     * @return {this} The cube of this complex number.
     */
    cubed() {
        const
          { constructor, real, imag } = this,

          re = real**3 - 3 * real * imag * imag,
          im = 3 * real * real * imag**4;

        return new constructor(re, im);
    }

    /**
     * Divides this complex number by another complex number or a scalar.
     * @param {(Cobj | number)} n The divisor.
     * @return {this} The quotient of the division.
     */
    div(n) {
        const { constructor, real, imag } = this;

        if (typeof n == 'object') {
            const
              { real: r, imag: i } = n,
              m = r*r + i*i;

            var
              re = (r * real + i * imag) / m,
              im = (r * imag - i * real) / m;
        }

        else var re = real / n, im = imag / n;

        return new constructor(re, im);
    }

    /**
     * Checks if this complex number is equal to another complex number within
     * a given tolerance.
     * @param {Cobj} z The other complex number to compare with.
     * @param {number} [epsilon=Complex.epsilon] The tolerance for equality.
     * @return {boolean} True if the two numbers are equal within the given
     * tolerance, false otherwise.
     */
    equals({ real, imag }, epsilon = Complex.#epsilon) {
        return Math.abs(this.real - real) < epsilon &&
               Math.abs(this.imag - imag) < epsilon;
    }

    /**
     * Returns the exponential of this complex number.
     * @return {this} The exponential of this complex number.
     */
    exp() {
        const
          a = Math.exp(this.real),
          b = Math.cos(this.imag),
          c = Math.sin(this.imag);

        return this.constructor.fromZ(Complex._0_1i).mult(c).add(b).mult(a);
    }

    /**
     * Generates a hashcode for this complex number.
     * @param {number} [hash=17] Optional initial seed for the hashcode.
     * @return {number} The hashcode for this complex number.
     */
    hashCode(hash = 0) {
        if (!hash && this.#hashCode != null) return this.#hashCode;

        hash = ~~hash || 17;

        for (const z of this) { // Iterate over [real & imag] values.
            Complex._hashFBuf[0] = z;
            for (const int8 of Complex._hashIBuf) hash = hash * 31 + int8 | 0;
        }

        return this.#hashCode = hash;
    }

    /**
     * Checks if this complex number is real within a given tolerance.
     * @param {number} [epsilon=Complex.epsilon] The tolerance for checking
     * if the imaginary part is zero.
     * @return {boolean} True if the imaginary part is zero within the given
     * tolerance, false otherwise.
     */
    isReal(epsilon = Complex.#epsilon) {
        return Math.abs(this.imag) <= epsilon;
    }

    /**
     * Checks if this complex number is zero within a given tolerance.
     * @param {number} [epsilon=Complex.epsilon] The tolerance for checking
     * if both the real and imaginary parts are zero.
     * @return {boolean} True if both the real and imaginary parts are zero
     * within the given tolerance, false otherwise.
     */
    isZero(epsilon = Complex.#epsilon) {
        return Math.abs(this.real) < epsilon && Math.abs(this.imag) < epsilon;
    }

    /**
     * Returns the natural logarithm of this complex number.
     * @return {this} The natural logarithm of this complex number.
     */
    log() {
        const re = Math.log( this.abs() );
        const im = this.real < 0 && !this.imag ? Complex.PI : this.arg();
        return new this.constructor(re, im);
    }

    /**
     * Multiplies this complex number by another complex number or a scalar.
     * @param {(Cobj | number)} n The multiplicand.
     * @return {this} The product of the multiplication.
     */
    mult(n) {
        const { constructor, real, imag } = this;

        if (typeof n == 'object') return new constructor(
            n.real * real - n.imag * imag,
            n.imag * real + n.real * imag
        );

        return new constructor(n * real, n * imag);
    }

    /**
     * Returns the half (divided by 2) of this complex number.
     * @return {this} Half of this complex number.
     */
    half() {
        return this.mult(.5);
    }

    /**
     * Returns the negation of this complex number.
     * @return {this} The negation of this complex number.
     */
    negate() {
        return this.mult(-1);
    }

    /**
     * Returns the normalization of this complex number.
     * @return {this} The normalization of this complex number.
     */
    norm() {
        return this.div( this.abs() );
    }

    /**
     * Returns the power of this complex number raised to another complex
     * number or a scalar.
     * @param {(Cobj | number)} n The exponent.
     * @return {this} Power of this complex number raised to given exponent.
     */
    pow(n) {
        return this.log().mult(n).exp();
    }

    /**
     * Returns the nth roots of this complex number (min 1).
     * @param {number} [n=1] The degree of the roots.
     * @return {this[]} An array containing the nth roots of this
     * complex number.
     */
    roots(n = 1) {
        const
          { constructor, real, imag } = this,

          delta = Complex.TAU / (n = ~~Math.abs(n) || 1),
          arg = Math.atan2(imag, real) / n,
          mod = Math.pow(this.modSq(), 1 / n);

        return Array.from(
            { length: n },
            (_, r) => constructor.fromPolar(mod, r * delta + arg)
        );
    }

    /**
     * Returns the sine of this complex number.
     * @return {this} The sine of this complex number.
     */
    sin() {
        const t1 = this.mult(Complex._0_1i).exp().mult(Complex._0_1i_neg);
        const t2 = this.mult(Complex._0_1i_neg).exp().mult(Complex._0_1i_neg);
        return t1.sub(t2).half();
    }

    /**
     * Returns the hyperbolic sine of this complex number.
     * @return {this} The hyperbolic sine of this complex number.
     */
    sinh() {
        return this.exp().sub( this.negate().exp() ).half();
    }

    /**
     * Returns the square of this complex number.
     * @return {this} The square of this complex number.
     */
    squared() {
        const
          { constructor, real, imag } = this,

          re = (real + imag) * (real - imag), // Same as: real*real - imag*imag
          im = 2 * real * imag;

        return new constructor(re, im);
    }

    /**
     * Returns the square root of this complex number.
     * @return {this} The square root of this complex number.
     */
    sqrt() {
        const
          { constructor, real, imag } = this,

          abs = this.abs(),
          re = Math.sqrt(.5 * (abs + real)),
          im = Math.sqrt(.5 * (abs - real));

        return new constructor(re, imag >= 0 ? im : -im);
    }

    /**
     * Subtracts another complex or a real number from this complex number.
     * @param {(Cobj | number)} n The subtrahend.
     * @return {this} The difference between the two numbers.
     */
    sub(n) {
        const { constructor, real, imag } = this;

        return typeof n == 'object' ?
            new constructor(real - n.real, imag - n.imag) :
            new constructor(real - n, imag);
    }

    /**
     * Returns the tangent of this complex number.
     * @return {this} The tangent of this complex number.
     */
    tan() {
        return this.sin().div( this.cos() );
    }

    /**
     * Returns the hyperbolic tangent of this complex number.
     * @return {this} The hyperbolic tangent of this complex number.
     */
    tanh() {
        return this.sinh().div( this.cosh() );
    }

    /**
     * Prints this complex number to the console.
     * @param {number} [precision=16] Number of significant digits to display.
     * @return {this} This complex number.
     * @chainable
     */
    print(precision) {
        console.log( this.$(precision) );
        return this;
    }

    /**
     * Returns a string representation of this complex number.
     * @param {number} [precision=16] Number of significant digits to display.
     * @param {string} [$=''] Extra info to append to the string representation.
     * @return {string} A string representation of this complex number.
     */
    $(precision = 0, $ = '') {
        if (!precision && !$ && this.#$ != null) return this.#$;

        const
          sgn = this.imag < 0 && '-' || '+',
          rep = this.real.toPrecision(precision ||= 16),
          imp = Math.abs(this.imag).toPrecision(precision);

        return this.#$ = `( ${rep} | ${sgn}${imp}j )${$}`;
    }

    /**
     * Returns a string representation of this complex number.
     * @return {string} A string representation of this complex number.
     */
    toString() {
        return this.$();
    }

    /**
     * Resets to default values both #hashCode & #$ internal caches.
     * @return {this} This complex number.
     * @chainable
     */
    resetCaches() {
        this.hashCode(17);
        this.$(16, '');
        return this;
    }

    /**
     * Returns the name of the constructor of this complex number.
     * @return {string} The name of the constructor of this complex number.
     */
    get [Symbol.toStringTag]() {
        return this.constructor.name;
    }

    /**
     * Converts this complex number to a primitive value.
     * @param {'default' | 'number' | 'string'} hint Type hint for conversion.
     * @return {number | string} The primitive value of this complex number.
     */
    [Symbol.toPrimitive](hint) {
        return hint == 'number' ? this.real : this.toString();
    }

    /**
     * Gets an iterator for the real and imaginary parts of this complex number.
     * @generator
     * @yield {number}
     * @return {IterableIterator<number>}
     * An iterator for the real and imaginary parts of this complex number.
     */
    *[Symbol.iterator]() {
        yield this.real;
        yield this.imag;
    }
}

/**
 * Compute the absolute value (modulus or magnitude) of this complex number.
 * @method mod
 * @memberof Complex
 * @instance
 * @return {number} The absolute length value of this complex number.
 * @description Alias of Complex::abs().
 */
Complex.prototype.mod = Complex.prototype.abs;

/**
 * Compute the squared absolute value of this complex number.
 * @method modSq
 * @memberof Complex
 * @instance
 * @return {number} The squared absolute value of this complex number.
 * @description Alias of Complex::abs_squared().
 */
Complex.prototype.modSq = Complex.prototype.abs_squared;

/**
 * Multiplies this complex number by another complex number or a scalar.
 * @method mul
 * @memberof Complex
 * @instance
 * @param {(Cobj | number)} n The multiplicand.
 * @return {this} The product of the multiplication.
 * @description Alias of Complex::mult().
 */
Complex.prototype.mul = Complex.prototype.mult;

// Freeze the Complex class to prevent further modifications to
// its static properties & methods:
Object.freeze(Complex);