Numeri complessi in JavaScript

Un mostro immaginario che pensa a un numero reale. (Immagine: DALL-E)

introduzione

Ultimamente ho giocato con l'insieme di Mandelbrot, il che significa che ho dovuto rispolverare la mia memoria su come fare aritmetica con numeri complessi. Ho creato una bella classe JavaScript che aiuta a ingrassare usandoli. Ecco ora funziona (è una spiegazione abbastanza asciutta, ma spero sia utile).

Un rapido aggiornamento

Quando moltiplichiamo due numeri insieme il risultato sarà pari o dispari a seconda che i numeri che stiamo moltiplicando siano:

-1 *  1 = -1
 1 * -1 = -1
 1 *  1 =  1
-1 * -1 =  1

i  ≔ √(-1)
∴ i² =  -1

Un numero complesso è un numero che ha una parte reale e una parte immaginaria . Sono scritti nel form a + bi.

Poiché 0i = 0le linee dei numeri si incontrano in 0, quindi possiamo tracciare questi numeri su un grafico, in genere l'asse x è la linea dei numeri reali e l'asse y è quella immaginaria.

Qualche esempio

3 + 2i tracciati sul piano complesso
3–8i tracciato sul piano complesso
-15 + 9i tracciato sul piano complesso

Aritmetica

Potresti aver notato che i numeri complessi assomigliano molto ai vettori e con l'avvertenza che i * i = -1lo sono. Tutte le operazioni sono solo normali operazioni vettoriali che vengono regolate in base a tale avvertenza.

Codice

Il Complexcostruttore di classi ha due parametri.

  • real
  • imaginary
  • export class Complex {
      constructor(real, imaginary) {
        this.real = real;
        this.imaginary = imaginary;
      }
    }
    

/***
 * Generate a new <code>Complex(0,0)</code>
 * @returns {Complex}
 */
static zero = () => new Complex(0, 0);

/***
 * Returns a string in the form <code>a ± bi</code>.
 * @returns {string}
 */
toString() {
    const operator = this.imaginary < 0 ? '-' : '+';
    return `${this.real} ${operator} ${Math.abs(this.imaginary)}i`;
}

/***
 * Both <i>real</i> and <i>imaginary</i> parts are equal.
 * @param other
 * @returns {boolean}
 */
equals(other) {
 return this.real === other.real && this.imaginary === other.imaginary;
}

add(other) {
 return new Complex(
 this.real + other.real,
 this.imaginary + other.imaginary
 );
}


subtract(other) {
 return new Complex(
 this.real — other.real,
 this.imaginary — other.imaginary
 );
}

Derivazione della formula di moltiplicazione

/***
 * Multiple this Complex with another.<br/>
 * <code>(a + bi)(c + di) = (ac - bd) + (ad + bc)i</code>
 * @param other
 * @returns {Complex}
 */
multiply(other) {
    return new Complex(
        this.real * other.real - this.imaginary * other.imaginary,
        this.real * other.imaginary + this.imaginary * other.real
    );
}

/***
 * <code>(a + bi) / (c + di) = [(ac + bd) / (c^2 + d^2)] + [(bc - ad) / (c^2 + d^2)]i<code>
 * @param other
 */
divide(other) {
    const otherMagnitudeSquared =
        other.real * other.real + other.imaginary * other.imaginary;
    const r =
        (this.real * other.real + this.imaginary * other.imaginary) /
        otherMagnitudeSquared;
    const i =
        (this.imaginary * other.real - this.real * other.imaginary) /
        otherMagnitudeSquared;

    return new Complex(r, i);
}

Classe di applicazione

export class Complex {
    constructor(real, imaginary) {
        this.real = real;
        this.imaginary = imaginary;
    }

    /***
     * Generate a new <code>Complex(0,0)</code>
     * @returns {Complex}
     */
    static zero = () => new Complex(0, 0);

    add(other) {
        return new Complex(
            this.real + other.real,
            this.imaginary + other.imaginary
        );
    }

    subtract(other) {
        return new Complex(
            this.real - other.real,
            this.imaginary - other.imaginary
        );
    }

    /***
     * Multiple this Complex with another.<br/>
     * <code>(a + bi)(c + di) = (ac - bd) + (ad + bc)i</code>
     * @param other
     * @returns {Complex}
     */
    multiply(other) {
        return new Complex(
            this.real * other.real - this.imaginary * other.imaginary,
            this.real * other.imaginary + this.imaginary * other.real
        );
    }

    /***
     * <code>(a + bi) / (c + di) = [(ac + bd) / (c^2 + d^2)] + [(bc - ad) / (c^2 + d^2)]i<code>
     * @param other
     */
    divide(other) {
        const otherMagnitudeSquared =
            other.real * other.real + other.imaginary * other.imaginary;
        const r =
            (this.real * other.real + this.imaginary * other.imaginary) /
            otherMagnitudeSquared;
        const i =
            (this.imaginary * other.real - this.real * other.imaginary) /
            otherMagnitudeSquared;

        return new Complex(r, i);
    }

    magnitude() {
        return Math.sqrt(
            this.real * this.real + this.imaginary * this.imaginary
        );
    }

    /***
     * Returns a string in the form <code>a ± bi</code>.
     * @returns {string}
     */
    toString() {
        const operator = this.imaginary < 0 ? '-' : '+';
        return `${this.real} ${operator} ${Math.abs(this.imaginary)}i`;
    }

    /***
     * Both <i>real</i> and <i>imaginary</i> parts are equal.
     * @param other
     * @returns {boolean}
     */
    equals(other) {
        return this.real === other.real && this.imaginary === other.imaginary;
    }
}

Vitest è usato qui.

import { describe, it, expect } from 'vitest';
import { Complex } from './Complex.js';

describe('Calling zero()', () => {
    const zero = Complex.zero();
    it('should return 0 real part', () => {
        expect(zero.real).toBe(0);
    });

    it('should return 0 imaginary part', () => {
        expect(zero.imaginary).toBe(0);
    });
});

describe('Creating a new number', () => {
    const expectedReal = Math.random();
    const expectedImaginary = Math.random();

    const actual = new Complex(expectedReal, expectedImaginary);

    it(`should return the expected real part (${expectedReal})`, () => {
        expect(actual.real).toBe(expectedReal);
    });

    it(`should return the expected imaginary part (${expectedImaginary})`, () => {
        expect(actual.imaginary).toBe(expectedImaginary);
    });
});

it('Should correctly calculate the magnitude', () => {
    const dummyReal = Math.random();
    const dummyImaginary = Math.random();
    const expected = Math.sqrt(
        dummyReal * dummyReal + dummyImaginary * dummyImaginary
    );

    const actual = new Complex(dummyReal, dummyImaginary).magnitude();

    console.dir({ dummyReal, dummyImaginary, actual, expected });

    expect(actual).toBe(expected);
});

describe('Equality', () => {
    it('should return true when equal', () => {
        const complex1 = new Complex(Math.random(), Math.random());
        const complex2 = new Complex(complex1.real, complex1.imaginary);

        const actual = complex1.equals(complex2);

        expect(actual).toBe(true);
    });

    it('should return false when real part differs', () => {
        const complex1 = new Complex(Math.random(), Math.random());
        const complex2 = new Complex(complex1.real + 1, complex1.imaginary);

        const actual = complex1.equals(complex2);

        expect(actual).toBe(false);
    });

    it('should return true when equal', () => {
        const complex1 = new Complex(Math.random(), Math.random());
        const complex2 = new Complex(complex1.real, complex1.imaginary + 1);

        const actual = complex1.equals(complex2);

        expect(actual).toBe(false);
    });
});

describe('Arithmetic', () => {
    const complex1 = new Complex(6, 3);
    const complex2 = new Complex(7, -5);

    describe('Add', () => {
        const expectedReal = complex1.real + complex2.real;
        const expectedImaginary = complex1.imaginary + complex2.imaginary;

        const actual = complex1.add(complex2);

        it(`Real part should be ${expectedReal}`, () => {
            expect(actual.real).toBe(expectedReal);
        });

        it(`Imaginary part should be ${expectedImaginary}`, () => {
            expect(actual.imaginary).toBe(expectedImaginary);
        });
    });

    describe('Subtract', () => {
        const expectedReal = complex1.real - complex2.real;
        const expectedImaginary = complex1.imaginary - complex2.imaginary;

        const actual = complex1.subtract(complex2);

        it(`Real part should be ${expectedReal}`, () => {
            expect(actual.real).toBe(expectedReal);
        });

        it(`Imaginary part should be ${expectedImaginary}`, () => {
            expect(actual.imaginary).toBe(expectedImaginary);
        });
    });

    describe('Multiply', () => {
        const expectedReal = complex1.real + complex2.real;
        const expectedImaginary = complex1.imaginary + complex2.imaginary;

        const actual = complex1.add(complex2);

        it(`Real part should be ${expectedReal}`, () => {
            expect(actual.real).toBe(expectedReal);
        });

        it(`Imaginary part should be ${expectedImaginary}`, () => {
            expect(actual.imaginary).toBe(expectedImaginary);
        });
    });

    describe('Divide', () => {
        const expectedReal = 27 / 74;
        const expectedImaginary = 51 / 74;

        const actual = complex1.divide(complex2);
        it(`should have correct real`, () => {
            expect(actual.real).toBe(expectedReal);
        });

        it(`should have correct imaginary`, () => {
            expect(actual.imaginary).toBe(expectedImaginary);
        });
    });
});

describe('toString()', () => {
    it('for positive imaginary part', () => {
        const dummyReal = 1;
        const dummyImaginary = 1;

        const expected = `${dummyReal} + ${dummyImaginary}i`;

        const actual = new Complex(dummyReal, dummyImaginary).toString();

        expect(actual).toBe(expected);
    });

    it('for zero imaginary part', () => {
        const dummyReal = 1;
        const dummyImaginary = 0;

        const expected = `${dummyReal} + ${dummyImaginary}i`;

        const actual = new Complex(dummyReal, dummyImaginary).toString();

        expect(actual).toBe(expected);
    });

    it('for negative imaginary part', () => {
        const dummyReal = 1;
        const dummyImaginary = -1;

        const expected = `${dummyReal} - ${Math.abs(dummyImaginary)}i`;

        const actual = new Complex(dummyReal, dummyImaginary).toString();

        expect(actual).toBe(expected);
    });
});

Devo ammettere che è stato abbastanza divertente riscoprire quelle derivazioni, non ho fatto matematica vettoriale per molto tempo.

Questo non era esattamente un articolo entusiasmante, è molto secco ma le funzioni sono importanti se vuoi giocare con gli insiemi di Mandelbrot o Julia o qualsiasi cosa richieda numeri complessi.

Grazie per aver letto.

Suggested posts

Come sono passato da Math a CS

Come sono passato da Math a CS

Il processo di ammissione dell'autunno 2023 sta volgendo al termine e sono in corso rinvii/rifiuti. Congratulazioni se sei stato accettato nel programma dei tuoi sogni all'UW.

Le mie risorse di codifica più utilizzate

Le mie risorse di codifica più utilizzate

Questa settimana ho ricevuto un'e-mail da un membro della mailing list quotidiana. Volevano sapere: quali risorse visiti giornalmente, settimanalmente e mensilmente? Ottima domanda! Sono felice di provare a condividere alcune informazioni sul mio flusso di lavoro e sulle migliori risorse che utilizzo.

Related posts

VITA SENZA LIMITI

Quando cresci tendi a sentirti dire che il mondo è così com'è e che la tua vita è vivere la tua vita dentro il mondo, senza mai rischiare la vita, basta seguire un percorso prestabilito e morire alla fine, e cercare di non farlo sfondare troppo i muri. Ma questa è una vita molto limitata.

Raccolta di OSINT per la caccia alle minacce

Raccolta di OSINT per la caccia alle minacce

Ehi, cibernetici! Bentornati alla serie OSINT per la caccia alle minacce. Nel primo articolo, ti ho fornito una panoramica di OSINT e della sua importanza nella caccia alle minacce.

L'assassinio di Jordan Neely e il problema del razzismo "patriottico".

Gli Stati Uniti possono fare il passo essenziale per non equiparare il comportamento razzista all'essere un buon americano?

L'assassinio di Jordan Neely e il problema del razzismo "patriottico".

[Avviso di attivazione: questo saggio include schermate di persone che elogiano l'assassino di Jordan Neely.] I funzionari delle forze dell'ordine hanno finalmente - e forse con riluttanza - accusato Daniel Penny, l'uomo che ha soffocato Jordan Neely fino alla sua morte, per quell'omicidio.

Niente più password: come le passkey sostituiranno la tua password

Niente più password: come le passkey sostituiranno la tua password

Per evitare ambiguità verranno utilizzati i seguenti termini; Autenticatore; Il telefono cellulare utilizzato per effettuare il login Sito web; Il sito Web o l'applicazione che l'utente sta utilizzando per effettuare la richiesta di accesso al Servizio; Il fornitore che fornisce il servizio dato, ad es