Komplexe Zahlen in JavaScript

Apr 17 2023
Einführung Ich habe in letzter Zeit mit der Mandelbrot-Menge herumgespielt, was dazu geführt hat, dass ich meine Erinnerung daran, wie man mit komplexen Zahlen rechnet, abstauben musste. Ich habe eine nette JavaScript-Klassenhilfe erstellt, die sie verwendet.
Ein imaginäres Monster, das an eine reelle Zahl denkt. (Bild: DALL-E)

Einführung

Ich habe in letzter Zeit mit der Mandelbrot-Menge gespielt, was dazu geführt hat, dass ich meine Erinnerung daran, wie man mit komplexen Zahlen rechnet, abstauben musste. Ich habe eine nette JavaScript-Klassenhilfe erstellt, die sie verwendet. Hier ist jetzt, es funktioniert (es ist eine ziemlich trockene Erklärung, aber ich hoffe, es ist nützlich).

Eine schnelle Auffrischung

Wenn wir zwei Zahlen miteinander multiplizieren, ist das Ergebnis ungerade oder gerade , je nachdem, ob die Zahlen, die wir multiplizieren, lauten:

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

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

Eine komplexe Zahl ist eine Zahl, die einen Realteil und einen Imaginärteil hat . Sie sind in der Form geschrieben a + bi.

Da 0i = 0sich die Zahlenlinien bei treffen 0, können wir diese Zahlen in einem Diagramm darstellen, wobei die x-Achse normalerweise die reelle Zahlenlinie und die y-Achse die imaginäre ist.

Einige Beispiele

3 + 2i aufgetragen auf der komplexen Ebene
3–8i aufgetragen auf der komplexen Ebene
-15 + 9i aufgetragen auf der komplexen Ebene

Arithmetik

Sie haben vielleicht bemerkt, dass komplexe Zahlen sehr wie Vektoren aussehen, und mit dem Vorbehalt, dass i * i = -1sie es sind. Alle Operationen sind nur reguläre Vektoroperationen, die basierend auf dieser Einschränkung angepasst werden.

Code

Der ComplexKlassenkonstruktor hat zwei Parameter.

  • 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
 );
}

Herleitung der Multiplikationsformel

/***
 * 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);
}

Anwendungsklasse

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;
    }
}

Hier kommt Vitest zum Einsatz.

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);
    });
});

Ich muss zugeben, es hat Spaß gemacht, diese Ableitungen wiederzuentdecken, ich habe mich schon lange nicht mehr mit Vektorrechnung beschäftigt.

Das war nicht gerade ein aufregender Artikel, er ist sehr trocken, aber die Funktionen sind wichtig, wenn Sie mit Mandelbrot- oder Julia-Mengen oder allem spielen wollen, was komplexe Zahlen benötigt.

Danke fürs Lesen.