From ce8018149ec9c21dc62413cc39469fb10f51efdf Mon Sep 17 00:00:00 2001 From: David Luis <95457148+babiabeo@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:21:16 +0700 Subject: [PATCH] feat(complex): Complex 2.0 (#8) --- .github/workflows/ci.yml | 18 +- .gitignore | 9 +- LICENSE | 4 +- README.md | 92 +++++----- deno.json | 16 +- mod.ts | 55 ------ src/_utils.ts | 103 ----------- src/complex.ts | 354 ++++++++++++------------------------ src/complex_test.ts | 63 +++++++ src/math/asin.ts | 41 +++++ src/math/asin_test.ts | 17 ++ src/math/asinh.ts | 34 ++++ src/math/asinh_test.ts | 17 ++ src/math/atan.ts | 29 +++ src/math/atan_test.ts | 11 ++ src/math/atanh.ts | 18 ++ src/math/atanh_test.ts | 11 ++ src/math/exp.ts | 63 +++++++ src/math/exp_test.ts | 56 ++++++ src/math/exponential.ts | 74 -------- src/math/hyperbolic.ts | 161 ---------------- src/math/log.ts | 50 +++++ src/math/log_test.ts | 18 ++ src/math/mod.ts | 23 ++- src/math/pow.ts | 19 ++ src/math/pow_test.ts | 12 ++ src/math/power.ts | 160 ---------------- src/math/sin.ts | 31 ++++ src/math/sin_test.ts | 17 ++ src/math/sinh.ts | 170 +++++++++++++++++ src/math/sinh_test.ts | 103 +++++++++++ src/math/sqrt.ts | 84 +++++++++ src/math/sqrt_test.ts | 38 ++++ src/math/tan.ts | 17 ++ src/math/tan_test.ts | 11 ++ src/math/tanh.ts | 70 +++++++ src/math/tanh_test.ts | 30 +++ src/math/trigonometric.ts | 308 ------------------------------- src/mod.ts | 71 ++++++++ src/utils.ts | 35 ++++ tests/complex_test.ts | 54 ------ tests/exponential_test.ts | 28 --- tests/hyperbolic_test.ts | 85 --------- tests/power_test.ts | 55 ------ tests/trigonometric_test.ts | 161 ---------------- 45 files changed, 1338 insertions(+), 1558 deletions(-) delete mode 100644 mod.ts delete mode 100644 src/_utils.ts create mode 100644 src/complex_test.ts create mode 100644 src/math/asin.ts create mode 100644 src/math/asin_test.ts create mode 100644 src/math/asinh.ts create mode 100644 src/math/asinh_test.ts create mode 100644 src/math/atan.ts create mode 100644 src/math/atan_test.ts create mode 100644 src/math/atanh.ts create mode 100644 src/math/atanh_test.ts create mode 100644 src/math/exp.ts create mode 100644 src/math/exp_test.ts delete mode 100644 src/math/exponential.ts delete mode 100644 src/math/hyperbolic.ts create mode 100644 src/math/log.ts create mode 100644 src/math/log_test.ts create mode 100644 src/math/pow.ts create mode 100644 src/math/pow_test.ts delete mode 100644 src/math/power.ts create mode 100644 src/math/sin.ts create mode 100644 src/math/sin_test.ts create mode 100644 src/math/sinh.ts create mode 100644 src/math/sinh_test.ts create mode 100644 src/math/sqrt.ts create mode 100644 src/math/sqrt_test.ts create mode 100644 src/math/tan.ts create mode 100644 src/math/tan_test.ts create mode 100644 src/math/tanh.ts create mode 100644 src/math/tanh_test.ts delete mode 100644 src/math/trigonometric.ts create mode 100644 src/mod.ts create mode 100644 src/utils.ts delete mode 100644 tests/complex_test.ts delete mode 100644 tests/exponential_test.ts delete mode 100644 tests/hyperbolic_test.ts delete mode 100644 tests/power_test.ts delete mode 100644 tests/trigonometric_test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bf161f..28a78f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: os: - ubuntu-22.04 - windows-2022 - - macOS-12 + - macOS-14 steps: - name: Setup repo @@ -32,7 +32,7 @@ jobs: bun-version: ${{ matrix.bun }} - name: Install packages - run: bunx jsr add @cross/test @std/assert@^0.226.0 + run: bunx jsr add @cross/test @std/assert - name: Run tests canary run: bun test @@ -47,19 +47,19 @@ jobs: os: - ubuntu-22.04 - windows-2022 - - macOS-12 + - macOS-14 steps: - name: Setup repo uses: actions/checkout@v4 - name: Setup Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@v2 with: deno-version: ${{ matrix.deno }} - name: Install packages - run: deno add @cross/test @std/assert@^0.226.0 + run: deno add jsr:@cross/test jsr:@std/assert - name: Run tests run: deno task test:deno @@ -70,11 +70,11 @@ jobs: strategy: fail-fast: false matrix: - node: [21.x] + node: [22.x] os: - ubuntu-22.04 - windows-2022 - - macOS-12 + - macOS-14 steps: - name: Setup repo @@ -101,9 +101,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@v2 with: deno-version: canary - name: Check linting - run: deno task lint \ No newline at end of file + run: deno task lint diff --git a/.gitignore b/.gitignore index b8e94f2..27aef33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -.vscode -deno.lock docs -package.json \ No newline at end of file + +# lock files +package.json +package-lock.json +deno.lock +bun.lockb diff --git a/LICENSE b/LICENSE index 7c75771..ed10d3d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 David (babiabeo) +Copyright (c) 2025 David (babiabeo) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 72c59d2..6db2da0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,30 @@ -# complex +# `@babia/complex` [![JSR](https://jsr.io/badges/@babia/complex)](https://jsr.io/@babia/complex) [![CI](https://github.com/babiabeo/complex/actions/workflows/ci.yml/badge.svg)](https://github.com/babiabeo/complex/actions/workflows/ci.yml) -A package provides implementation of complex numbers and mathematical functions -for complex numbers. +A package providing the implementation of complex numbers and mathematical +functions. + +#### Install + +```sh +# deno +deno add jsr:@babia/complex + +# node.js +npx jsr add @babia/complex +yarn dlx jsr add @babia/complex +pnpm dlx jsr add @babia/complex + +# bun +bunx jsr add @babia/complex +``` ## What is a complex number? -A complex number is an extension of the real numbers. It combines both real and -imaginary components. It can be expressed in form `a + bi`, where: +A complex number is an extension of the real number. It combines both real and +imaginary components. It can be expressed in the standard form `a + bi`, where: - `a`: the real part - `b`: the imaginary part @@ -19,82 +34,57 @@ A real number can be regarded as a complex number `a + 0i`, whose the imaginary part is `0`. A purely imaginary number is a complex number `0 + bi`, whose the real part is `0`. -## `Complex` class +## `complex` class -This package provides an implementation of complex numbers through the `Complex` +This package provides an implementation of complex number through the `complex` class. It has methods to perform basic operations on complex numbers: ```ts -const cmplx1 = new Complex(3, 1); // 3 + i -const cmplx2 = new Complex(2, 9); // 2 + 9i +const cmplx1 = new complex(3, 1); // 3 + i +const cmplx2 = new complex(2, 9); // 2 + 9i cmplx1.add(cmplx2); // (3 + i) + (2 + 9i) = (5 + 10i) // Also works with real numbers -cmplx1.add(3); // (3 + i) + 3 = (3 + i) + (3 + 0i) = (6 + i) +cmplx1.add(3); // (3 + i) + 3 = (6 + i) ``` -Methods `conj()`, `abs()`, `phase()` return the conjugate, absolute value, and -argument of the complex number respectively: +Methods `abs()`, `conj()`, `phase()` return the absolute value, the conjugate, +and the argument of the complex number respectively: ```ts +cmplx2.abs(); // 9.2195445 cmplx2.conj(); // (2 - 9i) -cmplx2.abs(); // ≈ 9.219544457292889 = Math.sqrt(85) -cmplx2.phase(); // 1.3521273809209546 +cmplx2.phase(); // 1.3521274 ``` ## `cmplx` function -For convenience, the `cmplx` function was added in `v1.1.0` to help create -complex numbers more easily. +The `cmplx` function was added in `v1.1.0` to simplify the creation of complex +numbers. ```ts cmplx(2, 3); // 2 + 3i ``` -Unlike the `Complex` class, the `cmplx` function requires the first argument -(the real part). +Unlike `complex` class, the `cmplx` function requires the first argument (the +real part). ```ts cmplx(0); // 0 + 0i ``` -## `ComplexMath` +## `cmath` -Like [`Math`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math), `ComplexMath` provides basic mathematics functionality for -complex numbers. +Besides `complex`, there is also `cmath` which is a collection of mathematical +functions for complex numbers: -- Exponential and logarithm functions +- Power and logarithm functions: `exp`, `log`, `log10`, `sqrt` +- Hyperbolic functions: `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh` +- Trigonometric functions: `sin`, `cos`, `tan`, `asin`, `acos`, `atan` -```ts -ComplexMath.exp(); -ComplexMath.log(); -// ... -``` - -- Hyperbolic functions: - -```ts -ComplexMath.sinh(); -ComplexMath.atanh(); -// ... -``` - -- Power functions: - -```ts -ComplexMath.sqrt(); -ComplexMath.pow(); -// ... -``` - -- Trigonometric functions: - -```ts -ComplexMath.asin(); -ComplexMath.tan(); -// ... -``` +> [!NOTE] +> Do not confuse with `` header in C++ ## Related diff --git a/deno.json b/deno.json index 5891d44..3584976 100644 --- a/deno.json +++ b/deno.json @@ -2,26 +2,30 @@ "name": "@babia/complex", "version": "1.1.1", "exports": { - ".": "./mod.ts", + ".": "./src/mod.ts", "./math": "./src/math/mod.ts" }, "fmt": { "indentWidth": 4 }, "imports": { - "@cross/test": "jsr:@cross/test@^0.0.9", - "@std/assert": "jsr:@std/assert@^0.225.3", + "@cross/test": "jsr:@cross/test@^0.0.10", + "@std/assert": "jsr:@std/assert@^1.0.10", "@babia/complex": "./mod.ts" }, - "exclude": [".vscode", "docs", ".github", "README.md", "LICENSE"], + "exclude": [ + "docs", + ".github", + "LICENSE" + ], "tasks": { "check": "deno publish --dry-run --allow-dirty", "lint": "deno fmt --check && deno lint", "test:bun": "bun test --bail", - "test:deno": "deno test --trace-leaks --doc --parallel --clean", + "test:deno": "deno test --trace-leaks --parallel --clean src", "test:node": "echo '{ \"type\": \"module\" }' > package.json && npx --yes tsx --test", "test": "deno task test:bun && deno task test:deno && deno task test:node", "ok": "deno task lint && deno task test && deno task check ", - "doc:view": "deno doc --html --name=\"@babia/complex\" ./mod.ts" + "doc:view": "deno doc --html --name=\"@babia/complex\" ./src/mod.ts" } } diff --git a/mod.ts b/mod.ts deleted file mode 100644 index 44c6bc8..0000000 --- a/mod.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * A package provides implementation of complex numbers and some mathematical - * functions for complex numbers. - * - * The `Complex` class represents a complex number which has a real part and an - * imaginary part. It is expressed in form `a + bi`, where: - * - `a`: the real part - * - `b`: the imaginary part - * - `i`: the imaginary unit - * - * You can also create a complex number using `cmplx` function. - * - * ```ts - * import { cmplx } from "@babia/complex"; - * - * const cmplx1 = cmplx(1, 9); // 1 + 9i - * const cmplx2 = cmplx(5, 4); // 5 + 4i - * ``` - * - * The `Complex` class has methods to perform basic operations, such as `add()`, - * `sub()`, etc. - * - * ```ts - * import { cmplx } from "@babia/complex"; - * - * const cmplx1 = cmplx(1, 9); // 1 + 9i - * const cmplx2 = cmplx(5, 4); // 5 + 4i - * - * cmplx1.add(cmplx2); // (1 + 9i) + (5 + 4i) - * cmplx1.sub(cmplx2); // (1 + 9i) - (5 + 4i) - * ``` - * - * Besides that, this package also has `ComplexMath` which provides basic - * mathematics functionality for complex numbers, such as `sin()`, `atan()`, - * `log()`, etc. - * - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * const cmplx1 = cmplx(1, 9); // 1 + 9i - * const cmplx2 = cmplx(5, 4); // 5 + 4i - * - * ComplexMath.sqrt(cmplx1); - * ComplexMath.log(cmplx1); - * ComplexMath.pow(cmplx2, 3); - * ``` - * - * Based on http://netlib.sandia.gov/cephes/c9x-complex and - * https://pkg.go.dev/math/cmplx. - * - * @module - */ - -export * from "./src/complex.ts"; -export * as ComplexMath from "./src/math/mod.ts"; diff --git a/src/_utils.ts b/src/_utils.ts deleted file mode 100644 index 4a9b3e1..0000000 --- a/src/_utils.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { complex } from "./complex.ts"; - -export const POS_INF = Number.POSITIVE_INFINITY; - -/** Whether the number is an infinity. */ -export function isInf(n: number): boolean { - return !isNaN2(n) && !Number.isFinite(n); -} - -/** Whether the number is NaN (not-a-number). */ -export function isNaN2(n: number): boolean { - return Number.isNaN(n); -} - -/** Whether the argument is a number. */ -export function isNumber(arg: unknown): arg is number { - return typeof arg === "number"; -} - -/** Gets the sign of number. */ -export function sign(n: number): number { - if (n < 0) { - return -1; - } - - return 1; -} - -/** Calculates the cosh and sinh of the number. */ -export function coshSinh(n: number): [number, number] { - if (Math.abs(n) <= 0.5) { - return [Math.cosh(n), Math.sinh(n)]; - } - - let e = Math.exp(n); - const ei = 0.5 / e; - e = 0.5 * e; - - return [e + ei, e - ei]; -} - -/** Machine epsilon = 2^-53 */ -const MACHEP = 1.1102230246251563e-16; - -/** Taylor series expansion for cosh(2y) - cos(2x). */ -export function tans(a: complex): number { - let x = Math.abs(2 * a.real); - let y = Math.abs(2 * a.imag); - - x = reducePi(x); - - x = x * x; - y = y * y; - - let x2 = 1; - let y2 = 1; - let f = 1; - let rn = 0; - let d = 0; - let t = 0; - - do { - ++rn; - f *= rn; - ++rn; - f *= rn; - x2 *= x; - y2 *= y; - t = y2 + x2; - t /= f; - d += t; - - ++rn; - f *= rn; - ++rn; - f *= rn; - x2 *= x; - y2 *= y; - t = y2 - x2; - t /= f; - d += t; - } while (Math.abs(t / d) > MACHEP); - - return d; -} - -const DP1 = 3.14159265160560607910e0; -const DP2 = 1.98418714791870343106e-9; -const DP3 = 1.14423774522196636802e-17; - -export function reducePi(n: number): number { - let t = n / Math.PI; - - if (t < 0) { - t -= 0.5; - } else { - t += 0.5; - } - - t = Math.trunc(t); - - return ((n - t * DP1) - t * DP2) - t * DP3; -} diff --git a/src/complex.ts b/src/complex.ts index 14c5c95..426fd24 100644 --- a/src/complex.ts +++ b/src/complex.ts @@ -1,46 +1,19 @@ -import { isInf, isNaN2, isNumber, POS_INF } from "./_utils.ts"; - -/** Type alias of {@linkcode Complex}. */ -export type complex = Complex; +import { IS_NUMBER } from "./utils.ts"; /** - * Creates a new complex number. - * - * @param real The real part - * @param imag The imaginary part + * Represents a complex number in rectangular form (standard form) + * which has a real part and an imaginary part. * * @example * ```ts - * import { cmplx } from "@babia/complex"; + * new complex(1, 9); // 1 + 9i + * new complex(14, 6); // 14 + 6i * - * cmplx(0); // 0 + 0i - * cmplx(2.2, 2); // 2.2 + 2i + * // from polar coordinates + * complex.fromPolar(2, Math.PI / 2); // 2i * ``` */ -export function cmplx(real: number, imag?: number): complex { - return new Complex(real, imag); -} - -/** - * Represents a complex number which has a real part and an imaginary part. - * - * @example - * ```ts - * import { Complex } from "@babia/complex"; - * - * new Complex(1, 9); // 1 + 9i - * new Complex(14, 6); // 14 + 6i - * - * // A complex number can be also created from a real number - * Complex.fromReal(4); // 4 + 0i - * Complex.fromReal(32.9); // 32.9 + 0i - * - * // or a purely imaginary number - * Complex.fromImag(11); // 0 + 11i - * Complex.fromImag(6); // 0 + 6i - * ``` - */ -export class Complex { +export class complex { /** The real part of the complex number. */ real: number; /** The imaginary part of the complex number. */ @@ -52,69 +25,22 @@ export class Complex { } /** - * Returns a complex number whose real part - * and imaginary part are both positive infinity. - */ - static Inf(): complex { - return new Complex(POS_INF, POS_INF); - } - - /** - * Returns a complex number whose real part - * and imaginary part are NaN (not-a-number). - */ - static NaN(): complex { - return new Complex(NaN, NaN); - } - - /** - * Creates a new complex number from a real number. - * - * @param real A real number + * Gets a complex number from its polar coordinates. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * Complex.fromReal(2); // 2 + 0i - * Complex.fromReal(0); // 0 + 0i + * complex.fromPolar(2, Math.PI / 2); // 0 + 2i * ``` - */ - static fromReal(real: number): complex { - return new Complex(real); - } - - /** - * Creates a new complex number from a purely imaginary number. - * - * @param imag A purely imaginary number - * - * @example - * ```ts - * import { Complex } from "@babia/complex"; - * - * Complex.fromImag(1); // 0 + i - * Complex.fromImag(10); // 0 + 10i - * ``` - */ - static fromImag(imag: number): complex { - return new Complex(0, imag); - } - - /** - * Returns the complex conjugate of the complex number. * - * @example - * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(3, 9); // 3 + 9i - * - * cmplx.conj(); // 3 - 9i - * ``` + * @param r The absolute value (or magnitude). + * @param phi The phase (or argument). Must be in the range `[-pi, pi]`. + * @returns A complex number in rectangular form `a + bi`. */ - conj(): complex { - return new Complex(this.real, -this.imag); + static fromPolar(r: number, phi: number): complex { + return new complex( + r * Math.cos(phi), + r * Math.sin(phi), + ); } /** @@ -122,11 +48,7 @@ export class Complex { * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(3, 4); // 3 + 4i - * - * cmplx.abs(); // 5 + * new complex(3, 4).abs(); // abs(3 + 4i) = 5 * ``` */ abs(): number { @@ -134,250 +56,208 @@ export class Complex { } /** - * Returns the phase (or argument) of the complex number. + * Returns the argument (or phase) of the complex number. + * The value is in the range `[-pi, pi]`. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(2, 7); // 2 + 7i - * - * cmplx.phase(); // 1.292496667... + * new complex(1, 1).arg(); // arg(1 + i) = pi/4 * ``` */ - phase(): number { + arg(): number { return Math.atan2(this.imag, this.real); } /** - * Whether either the real part or imaginary part of - * the complex number is an infinity. + * Returns the complex conjugate of the complex number. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * new Complex(Infinity, 4).isInf(); // true - * new Complex(21, Infinity).isInf(); // true - * new Complex(0, 17).isInf(); // false + * new complex(-9, 10).conj(); // conj(-9 + 10i) = -9 - 10i * ``` */ - isInf(): boolean { - return isInf(this.real) || isInf(this.imag); + conj(): complex { + return new complex(this.real, -this.imag); } /** - * Whether either the real part or imaginary part of - * the complex number is NaN and neither is an infinity. + * Returns the negation of the complex number. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * new Complex(4, 4).isNaN(); // false - * new Complex(21, Infinity).isNaN(); // false - * new Complex(NaN, NaN).isNaN(); // true + * new complex(-9, 10).neg(); // neg(-9 + 10i) = 9 - 10i * ``` */ - isNaN(): boolean { - if (this.isInf()) { - return false; - } - - return isNaN2(this.real) || isNaN2(this.imag); + neg(): complex { + return new complex(-this.real, -this.imag); } /** - * Whether the complex number is equal to 0. + * Returns the polar coordinates of the complex number + * which consists of two values: + * - `r`: the absolute value (or amplitude). + * - `θ`: the argument (or phase). * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * new Complex(4, 4).isZero(); // false - * new Complex(0, 0).isZero(); // true + * new complex(0, 1).polar(); // polar(i) => r = 1, θ = pi/2 * ``` */ - isZero(): boolean { - return this.real === 0 && this.imag === 0; + polar(): [number, number] { + return [this.abs(), this.arg()]; } /** * Adds a real number to the complex number. * - * @param other A real number. - * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(3, 2); // 3 + 2i - * - * cmplx.add(3); // 6 + 2i + * new complex(5, 5).add(5); // (5 + 5i) + 5 = 10 + 5i * ``` + * + * @param rhs The real number to be added. + * @returns The result of addition. */ - add(other: number): complex; + add(rhs: number): complex; /** * Adds two complex numbers. * - * @param other The other complex number. - * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx1 = new Complex(3, 2); // 3 + 2i - * const cmplx2 = new Complex(4, -1); // 4 - i + * const lhs = new complex(3, 8); + * const rhs = new complex(6, 10); * - * cmplx1.add(cmplx2); // 7 + i + * lhs.add(rhs); // (3 + 8i) + (6 + 10i) = 9 + 18i * ``` + * + * @param rhs The complex number to be added. + * @returns The result of addition. */ - add(other: complex): complex; - add(other: number | complex): complex { - if (isNumber(other)) { - return new Complex(this.real + other, this.imag); + add(rhs: complex): complex; + add(rhs: number | complex): complex { + if (IS_NUMBER(rhs)) { + return new complex(this.real + rhs, this.imag); } - return new Complex( - this.real + other.real, - this.imag + other.imag, + return new complex( + this.real + rhs.real, + this.imag + rhs.imag, ); } /** * Subtracts a real number from the complex number. * - * @param other A real number. - * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(3, 2); // 3 + 2i - * - * cmplx.sub(6); // -3 + 2i + * new complex(5, 5).sub(5); // (5 + 5i) - 5 = 5i * ``` + * + * @param rhs The real number to subtract. + * @returns The result of subtraction. */ - sub(other: number): complex; + sub(rhs: number): complex; /** - * Subtracts two complex numbers. - * - * @param other The other complex number. + * Subtracts a complex number from the complex number. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx1 = new Complex(3, 2); // 3 + 2i - * const cmplx2 = new Complex(4, -1); // 4 - i + * const lhs = new complex(3, 8); + * const rhs = new complex(6, 10); * - * cmplx1.sub(cmplx2); // -1 + 3i + * lhs.sub(rhs); // (3 + 8i) - (6 + 10i) = -3 - 2i * ``` + * + * @param rhs The complex number to subtract. + * @returns The result of subtraction. */ - sub(other: complex): complex; - sub(other: number | complex): complex { - if (isNumber(other)) { - return new Complex(this.real - other, this.imag); + sub(rhs: complex): complex; + sub(rhs: number | complex): complex { + if (IS_NUMBER(rhs)) { + return new complex(this.real - rhs, this.imag); } - return new Complex( - this.real - other.real, - this.imag - other.imag, + return new complex( + this.real - rhs.real, + this.imag - rhs.imag, ); } /** - * Calculates the product of a real number and the complex number. - * - * @param other A real number. + * Returns the product of a real number and the complex number. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(3, 2); // 3 + 2i - * - * cmplx.mult(5); // 15 + 10i + * new complex(4, 5).mul(3); // (4 + 5i) * 3 = 12 + 15i * ``` + * + * @param rhs The real number. + * @returns The result of multiplication. */ - mult(other: number): complex; + mul(rhs: number): complex; /** - * Calculates the product of two complex numbers. - * - * @param other The other complex number. + * Returns the product of two complex numbers. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx1 = new Complex(3, 2); // 3 + 2i - * const cmplx2 = new Complex(4, -1); // 4 - i + * const lhs = new complex(2, 5); + * const rhs = new complex(8, 1); * - * cmplx1.mult(cmplx2); // 14 + 5i + * lhs.mul(rhs); // (2 + 5i) * (8 + i) = 11 + 42i * ``` + * + * @param rhs The other complex number. + * @returns The result of multiplication. */ - mult(other: complex): complex; - mult(other: number | complex): complex { - if (isNumber(other)) { - return new Complex(this.real * other, this.imag * other); + mul(rhs: complex): complex; + mul(rhs: number | complex): complex { + if (IS_NUMBER(rhs)) { + return new complex(this.real * rhs, this.imag * rhs); } - const a = this.real; - const b = this.imag; - const c = other.real; - const d = other.imag; - - const real = a * c - b * d; - const imag = a * d + b * c; - - return new Complex(real, imag); + return new complex( + (this.real * rhs.real) - (this.imag * rhs.imag), + (this.real * rhs.imag) + (this.imag * rhs.real), + ); } /** - * Calculates the quotient of the complex number and a real number. - * - * @param other The other complex number. + * Divides the complex number by a real number. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx = new Complex(3, 2); // 3 + 2i - * - * cmplx.div(2); // 1.5 + i + * new complex(14, 20).div(2); // (14 + 20i) / 2 = 7 + 10i * ``` + * + * @param rhs The real number. + * @returns The result of division. */ - div(other: number): complex; + div(rhs: number): complex; /** - * Calculates the quotient of two complex numbers. - * - * @param other The other complex number. + * Divides the complex number by another complex number. * * @example * ```ts - * import { Complex } from "@babia/complex"; - * - * const cmplx1 = new Complex(3, 2); // 3 + 2i - * const cmplx2 = new Complex(4, -1); // 4 - i + * const lhs = new complex(9, 3); + * const rhs = new complex(1, 1); * - * cmplx1.div(cmplx2); // 0.5882352941176471 + 0.6470588235294118i + * lhs.div(rhs); // (9 + 3i) / (1 + i) = 9/2 + i/2 * ``` + * + * @param rhs The other complex number. + * @returns The result of division. */ - div(other: complex): complex; - div(other: number | complex): complex { - if (isNumber(other)) { - return new Complex(this.real / other, this.imag / other); + div(rhs: complex): complex; + div(rhs: number | complex): complex { + if (IS_NUMBER(rhs)) { + return new complex(this.real / rhs, this.imag / rhs); } - const y = other.real * other.real + other.imag * other.imag; - const q = this.real * other.real + this.imag * other.imag; - const p = this.imag * other.real - this.real * other.imag; - - if (y === 0) { - return Complex.Inf(); - } + const re = this.real * rhs.real + this.imag * rhs.imag; + const im = this.imag * rhs.real - this.real * rhs.imag; + const d = rhs.real * rhs.real + rhs.imag * rhs.imag; - return new Complex(q / y, p / y); + return new complex(re / d, im / d); } toString(): string { diff --git a/src/complex_test.ts b/src/complex_test.ts new file mode 100644 index 0000000..a3ccbf7 --- /dev/null +++ b/src/complex_test.ts @@ -0,0 +1,63 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals, assertEquals } from "@std/assert"; +import { complex } from "./complex.ts"; + +test("Complex parts", () => { + const ex = new complex(3, 5); + + assertEquals(ex.real, 3); + assertEquals(ex.imag, 5); + assertEquals(String(ex), "3 + 5i"); +}); + +test("Complex polar form", () => { + const ex = complex.fromPolar(2, Math.PI / 2); + + assertAlmostEquals(ex.real, 0, Number.EPSILON); + assertEquals(ex.imag, 2); + assertEquals(ex.polar(), [2, Math.PI / 2]); +}); + +test("Complex addition", () => { + const ex1 = new complex(3, 5); + const ex2 = new complex(3, 5); + + assertEquals(ex1.add(ex2), new complex(6, 10)); + assertEquals(ex1.add(2), new complex(5, 5)); +}); + +test("Complex subtraction", () => { + const ex1 = new complex(86, 15); + const ex2 = new complex(21, 9); + + assertEquals(ex1.sub(ex2), new complex(65, 6)); + assertEquals(ex1.sub(7), new complex(79, 15)); +}); + +test("Complex multiplication", () => { + const ex1 = new complex(3, 2); + const ex2 = new complex(4, -1); + + assertEquals(ex1.mul(ex2), new complex(14, 5)); + assertEquals(ex1.mul(3), new complex(9, 6)); +}); + +test("Complex division", () => { + const ex1 = new complex(3, 2); + const ex2 = new complex(4, -1); + + const result = ex1.div(ex2); + assertAlmostEquals(result.real, 0.588235294); + assertAlmostEquals(result.imag, 0.647058824); + + assertEquals(ex1.div(2), new complex(1.5, 1)); +}); + +test("Complex conjugate, absolute value, argument, negation", () => { + const ex = new complex(10, 10); + + assertEquals(ex.abs(), 10 * Math.sqrt(2)); + assertEquals(ex.conj(), new complex(10, -10)); + assertEquals(ex.arg(), Math.PI / 4); + assertEquals(ex.neg(), new complex(-10, -10)); +}); diff --git a/src/math/asin.ts b/src/math/asin.ts new file mode 100644 index 0000000..ec5a357 --- /dev/null +++ b/src/math/asin.ts @@ -0,0 +1,41 @@ +import { complex } from "../complex.ts"; +import { log } from "./log.ts"; +import { sqrt } from "./sqrt.ts"; + +/** + * Returns the inverse sine (or arc sine) of a complex number. + * + * @example + * ```ts + * asin(new complex(-2, 0)); // -1.5707963 + 1.3169590i + * ``` + * + * @param z A complex number. + */ +export function asin(z: complex): complex { + const x = z.real; + const y = z.imag; + + // (a + bi) * i = -b + ai + const iz = new complex(-y, x); + const w = new complex(1 - (x - y) * (x + y), -2 * x * y); + const r = log(sqrt(w).add(iz)); + + // (a + bi) * (-i) = b - ai + return new complex(r.imag, -r.real); +} + +/** + * Returns the inverse cosine (or arc cosine) of a complex number. + * + * @example + * ```ts + * acos(new complex(-2, 0)); // 3.1415927 - 1.3169590i + * ``` + * + * @param z A complex number. + */ +export function acos(z: complex): complex { + const w = asin(z); + return new complex(0.5 * Math.PI - w.real, -w.imag); +} diff --git a/src/math/asin_test.ts b/src/math/asin_test.ts new file mode 100644 index 0000000..b576733 --- /dev/null +++ b/src/math/asin_test.ts @@ -0,0 +1,17 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { acos, asin } from "./asin.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("asin()", () => { + const z = asin(new complex(-2, 0)); + assertAlmostEquals(z.real, -1.5707963, TOLERANCE); + assertAlmostEquals(z.imag, 1.3169579, TOLERANCE); +}); + +test("acos()", () => { + const z = acos(new complex(-2, 0)); + assertAlmostEquals(z.real, 3.1415927, TOLERANCE); + assertAlmostEquals(z.imag, -1.3169579, TOLERANCE); +}); diff --git a/src/math/asinh.ts b/src/math/asinh.ts new file mode 100644 index 0000000..fd2b459 --- /dev/null +++ b/src/math/asinh.ts @@ -0,0 +1,34 @@ +import { complex } from "../complex.ts"; +import { acos, asin } from "./asin.ts"; + +/** + * Returns the inverse hyperbolic sine (or area hyperbolic sine) + * of a complex number. + * + * @example + * ```ts + * asinh(new complex(1, 2)); // 1.4693517 + 1.0634400i + * ``` + * + * @param z A complex number. + */ +export function asinh(z: complex): complex { + const w = asin(new complex(-z.imag, z.real)); + return new complex(w.imag, -w.real); +} + +/** + * Returns the inverse hyperbolic cosine (or area hyperbolic cosine) + * of a complex number. + * + * @example + * ```ts + * acosh(new complex(1, 1)); // 1.0612751 + 0.9045557i + * ``` + * + * @param z A complex number. + */ +export function acosh(z: complex): complex { + const w = acos(z); + return new complex(-w.imag, w.real); +} diff --git a/src/math/asinh_test.ts b/src/math/asinh_test.ts new file mode 100644 index 0000000..42ff53c --- /dev/null +++ b/src/math/asinh_test.ts @@ -0,0 +1,17 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { acosh, asinh } from "./asinh.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("asinh()", () => { + const z = asinh(new complex(1, 2)); + assertAlmostEquals(z.real, 1.4693517, TOLERANCE); + assertAlmostEquals(z.imag, 1.0634400, TOLERANCE); +}); + +test("acosh()", () => { + const z = acosh(new complex(1, 1)); + assertAlmostEquals(z.real, 1.0612751, TOLERANCE); + assertAlmostEquals(z.imag, 0.9045569, TOLERANCE); +}); diff --git a/src/math/atan.ts b/src/math/atan.ts new file mode 100644 index 0000000..5d7bee5 --- /dev/null +++ b/src/math/atan.ts @@ -0,0 +1,29 @@ +import { complex } from "../complex.ts"; + +/** + * Returns the arc tangent of a complex number. + * + * @example + * ```ts + * atan(new complex(0, 2)); // 1.5707963 + 0.5493061i + * ``` + * + * @param z A complex number + */ +export function atan(z: complex): complex { + const x = z.real; + const y = z.imag; + + const x2 = x * x; + let a = 1 - x2 - (y * y); + + let t = 0.5 * Math.atan2(2 * x, a); + const w = t; + + t = y - 1; + a = x2 + (t * t); + t = y + 1; + a = (x2 + (t * t)) / a; + + return new complex(w, 0.25 * Math.log(a)); +} diff --git a/src/math/atan_test.ts b/src/math/atan_test.ts new file mode 100644 index 0000000..ed02170 --- /dev/null +++ b/src/math/atan_test.ts @@ -0,0 +1,11 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { atan } from "./atan.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("atan()", () => { + const z = atan(new complex(0, 2)); + assertAlmostEquals(z.real, 1.5707963, TOLERANCE); + assertAlmostEquals(z.imag, 0.5493061, TOLERANCE); +}); diff --git a/src/math/atanh.ts b/src/math/atanh.ts new file mode 100644 index 0000000..3120db6 --- /dev/null +++ b/src/math/atanh.ts @@ -0,0 +1,18 @@ +import { complex } from "../complex.ts"; +import { atan } from "./atan.ts"; + +/** + * Returns the inverse hyperbolic tangent (or area hyperbolic tangent) + * of a complex number. + + * @example + * ```ts + * atanh(new complex(1, 2)); // 0.1732868 + 1.1780972i + * ``` + * + * @param z A complex number + */ +export function atanh(z: complex): complex { + const w = atan(new complex(-z.imag, z.real)); + return new complex(w.imag, -w.real); +} diff --git a/src/math/atanh_test.ts b/src/math/atanh_test.ts new file mode 100644 index 0000000..4ad30c2 --- /dev/null +++ b/src/math/atanh_test.ts @@ -0,0 +1,11 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { atanh } from "./atanh.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("atanh()", () => { + const z = atanh(new complex(1, 2)); + assertAlmostEquals(z.real, 0.1732868, TOLERANCE); + assertAlmostEquals(z.imag, 1.1780972, TOLERANCE); +}); diff --git a/src/math/exp.ts b/src/math/exp.ts new file mode 100644 index 0000000..821770a --- /dev/null +++ b/src/math/exp.ts @@ -0,0 +1,63 @@ +import { complex } from "../complex.ts"; +import { IS_FINITE, IS_INF, NEG_INF } from "../utils.ts"; + +/** + * Returns the complex exponential of a complex number (the base-e exponential of `z`). + * + * ### Special cases + * + * | real | imaginary | `exp()` | + * | ---------------- | ------------------------ | -------------- | + * | ±0 | ±0 | (1, ±0) | + * | x (any finite x) | ±Inf | (NaN, NaN) | + * | x (any finite x) | NaN | (NaN, NaN) | + * | +Inf | ±0 | (+Inf, ±0) | + * | -Inf | y (any finite y) | +0 \* cis(y) | + * | +Inf | y (any finite nonzero y) | +Inf \* cis(y) | + * | -Inf | ±Inf | (+0, +0) | + * | +Inf | ±Inf | (+Inf, NaN) | + * | -Inf | NaN | (+0, +0) | + * | +Inf | NaN | (+Inf, NaN) | + * | NaN | ±0 | (NaN, ±0) | + * | NaN | NaN | (NaN, NaN) | + * | NaN | ±Inf | (NaN, NaN) | + * | NaN | y (any finite nonzero y) | (NaN, NaN) | + * + * _cis(y) = cos(y) + i sin(y)_ + * + * @example + * ```ts + * exp(new complex(3, 4)); // -13.1287831 - 15.2007845i + * ``` + * + * @param z A complex number. + */ +export function exp(z: complex): complex { + const x = z.real; + const y = z.imag; + + if (y === 0) { + return new complex(Math.exp(x), y); + } + + // exp(+-0 + i y) = cos(y) + i sin(y); + if (x === 0) { + return new complex(Math.cos(y), Math.sin(y)); + } + + // exp(finite|NaN +- i Inf|NaN) = NaN + i NaN + // exp(-Inf +- i Inf|NaN) = 0 + i 0 + // exp(+Inf +- i Inf|NaN) = +Inf + i NaN + if (!IS_FINITE(y)) { + if (!IS_INF(x)) { + return new complex(NaN, NaN); + } + if (x === NEG_INF) { + return new complex(0, 0); + } + return new complex(x, NaN); + } + + const e = Math.exp(x); + return new complex(e * Math.cos(y), e * Math.sin(y)); +} diff --git a/src/math/exp_test.ts b/src/math/exp_test.ts new file mode 100644 index 0000000..ea40105 --- /dev/null +++ b/src/math/exp_test.ts @@ -0,0 +1,56 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals, assertEquals } from "@std/assert"; +import { exp } from "./exp.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("exp() normal cases", () => { + assertEquals(exp(new complex(0, 0)), new complex(1, 0)); + assertEquals(exp(new complex(-0, -0)), new complex(1, -0)); + + const result = exp(new complex(3, 4)); + assertAlmostEquals(result.real, -13.1287831, TOLERANCE); + assertAlmostEquals(result.imag, -15.2007845, TOLERANCE); +}); + +test("exp() special cases", () => { + const rd = 100 * Math.random(); + + assertEquals( + exp(new complex(rd, Infinity)), + new complex(NaN, NaN), + ); + assertEquals( + exp(new complex(rd, -Infinity)), + new complex(NaN, NaN), + ); + assertEquals( + exp(new complex(rd, NaN)), + new complex(NaN, NaN), + ); + + assertEquals(exp(new complex(Infinity, 0)), new complex(Infinity, 0)); + assertEquals(exp(new complex(-Infinity, 30)), new complex(0, -0)); + assertEquals( + exp(new complex(Infinity, 30)), + new complex(Infinity, -Infinity), + ); + assertEquals( + exp(new complex(-Infinity, Infinity)), + new complex(0, 0), + ); + assertEquals( + exp(new complex(Infinity, -Infinity)), + new complex(Infinity, NaN), + ); + assertEquals(exp(new complex(-Infinity, NaN)), new complex(0, 0)); + assertEquals(exp(new complex(Infinity, NaN)), new complex(Infinity, NaN)); + + assertEquals(exp(new complex(NaN, -0)), new complex(NaN, -0)); + assertEquals(exp(new complex(NaN, NaN)), new complex(NaN, NaN)); + assertEquals(exp(new complex(NaN, -Infinity)), new complex(NaN, NaN)); + assertEquals( + exp(new complex(NaN, rd)), + new complex(NaN, NaN), + ); +}); diff --git a/src/math/exponential.ts b/src/math/exponential.ts deleted file mode 100644 index fbfd462..0000000 --- a/src/math/exponential.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { cmplx, type complex } from "../complex.ts"; -import { isInf, isNaN2, POS_INF } from "../_utils.ts"; - -/** - * Returns the complex exponential of the complex argument `a` - * (the base-e exponential of `a`). - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.exp(cmplx(3, 4)); // -13.128783081462158 - 15.200784463067954i - * ``` - */ -export function exp(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (isInf(r)) { - if (r > 0 && i === 0) { - return cmplx(r, i); - } - - if (isInf(i) || isNaN2(i)) { - if (r < 0) { - return cmplx(0); - } - - return cmplx(POS_INF, NaN); - } - } - - if (isNaN2(r) && i === 0) { - return cmplx(r, i); - } - - const e = Math.exp(r); - return cmplx(e * Math.cos(i), e * Math.sin(i)); -} - -/** - * Returns the natural logarithm of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.log(cmplx(3, 4)); // 1.6094379124341003 + 0.9272952180016122i - * ``` - */ -export function log(a: complex): complex { - return cmplx(Math.log(a.abs()), a.phase()); -} - -/** - * Returns the base 10 logarithm of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.log10(cmplx(3, 4)); // 0.6989700043360187 + 0.4027191962733731i - * ``` - */ -export function log10(a: complex): complex { - // log10(a) = log(a) / log(10) = log(a) * log10(e) - return log(a).mult(Math.LOG10E); -} diff --git a/src/math/hyperbolic.ts b/src/math/hyperbolic.ts deleted file mode 100644 index feb33af..0000000 --- a/src/math/hyperbolic.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { cmplx, Complex, type complex } from "../complex.ts"; -import { isInf, isNaN2, POS_INF, sign } from "../_utils.ts"; -import { acos, asin, atan } from "./trigonometric.ts"; - -/** - * Returns the hyperbolic sine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.sinh(cmplx(1, 1)); // 0.6349639147847361 + 1.2984575814159773i - * ``` - */ -export function sinh(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (r === 0 && (isInf(i) || isNaN2(i))) { - return cmplx(r, NaN); - } - - if (isInf(r)) { - if (i === 0) { - return cmplx(r, i); - } - - if (isInf(i) || isNaN2(i)) { - return cmplx(r, NaN); - } - } - - if (i === 0 && isNaN2(r)) { - return cmplx(NaN, i); - } - - return cmplx(Math.sinh(r) * Math.cos(i), Math.cosh(r) * Math.sin(i)); -} - -/** - * Returns the hyperbolic cosine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.cosh(cmplx(1, 1)); // 0.8337300251311491 + 0.9888977057628651i - * ``` - */ -export function cosh(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (r === 0 && (isInf(i) || isNaN2(i))) { - return cmplx(NaN); - } - - if (isInf(r)) { - if (i === 0) { - return cmplx(POS_INF); - } - - if (isInf(i) || isNaN2(i)) { - return cmplx(POS_INF, NaN); - } - } - - if (i === 0 && isNaN2(r)) { - return cmplx(NaN, i); - } - - return cmplx(Math.cosh(r) * Math.cos(i), Math.sinh(r) * Math.sin(i)); -} - -/** - * Returns the hyperbolic tangent of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.tanh(cmplx(1, 1)); // 1.0839233273386948 + 0.27175258531951174i - * ``` - */ -export function tanh(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (isInf(r)) { - return cmplx(sign(r)); - } - - if (i === 0 && isNaN2(r)) { - return cmplx(NaN, i); - } - - const d = Math.cosh(2 * r) + Math.cos(2 * i); - - if (d === 0) { - return Complex.Inf(); - } - - return cmplx(Math.sinh(2 * r) / d, Math.sin(2 * i) / d); -} - -/** - * Returns the inverse hyperbolic sine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.asinh(cmplx(1, 1)); // 1.0612750619050355 + 0.6662394324925153i - * ``` - */ -export function asinh(a: complex): complex { - const w = asin(cmplx(-a.imag, a.real)); - return cmplx(w.imag, -w.real); -} - -/** - * Returns the inverse hyperbolic cosine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.acosh(cmplx(1, 1)); // 1.0612750619050355 + 0.9045568943023813i - * ``` - */ -export function acosh(a: complex): complex { - const w = acos(a); - return cmplx(-w.imag, w.real); -} - -/** - * Returns the inverse hyperbolic tangent of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.atanh(cmplx(1, 1)); // 0.40235947810852507 + 1.0172219678978514i - * ``` - */ -export function atanh(a: complex): complex { - const w = atan(cmplx(-a.imag, a.real)); - return cmplx(w.imag, -w.real); -} diff --git a/src/math/log.ts b/src/math/log.ts new file mode 100644 index 0000000..1a93579 --- /dev/null +++ b/src/math/log.ts @@ -0,0 +1,50 @@ +import { complex } from "../complex.ts"; + +/** + * Returns the natural logarithm of a complex number. + * + * @example + * ```ts + * const z = new complex(3, 1); + * log(z); // log(3 + i) = 1.15129255 + 0.32175055i + * ``` + * + * @param z A complex number. + */ +export function log(z: complex): complex { + const x = Math.abs(z.real); + const y = Math.abs(z.imag); + const r = Math.hypot(x, y); + const phi = z.arg(); + + // if sqrt(0.5) <= r <= sqrt(3), use log1p() for more accurate results + // + // log(x^2 + y^2) log1p((x^2 - 1) + y^2) + // log(r) = log(sqrt(x^2 + y^2)) = -------------- = ---------------------- + // 2 2 + if (0.71 <= r && r <= 1.73) { + const a = Math.max(x, y); + const b = Math.min(x, y); + + return new complex( + 0.5 * Math.log1p((a - 1) * (a + 1) + b * b), + phi, + ); + } + + return new complex(Math.log(r), phi); +} + +/** + * Returns the base-10 logarithm of a complex number. + * + * @example + * ```ts + * const z = new complex(3, 1); + * log10(z); // log10(3 + i) = 0.5 + 0.13973449i + * ``` + */ +export function log10(z: complex): complex { + // log10(z) = log10(e) * log(x) + return log(z).mul(Math.LOG10E); +} diff --git a/src/math/log_test.ts b/src/math/log_test.ts new file mode 100644 index 0000000..94e831f --- /dev/null +++ b/src/math/log_test.ts @@ -0,0 +1,18 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { complex } from "../complex.ts"; +import { log, log10 } from "./log.ts"; + +test("log()", () => { + const ex = log(new complex(3, 4)); + + assertAlmostEquals(ex.real, 1.6094379); + assertAlmostEquals(ex.imag, 0.9272952); +}); + +test("log10()", () => { + const ex = log10(new complex(3, 4)); + + assertAlmostEquals(ex.real, 0.6989700); + assertAlmostEquals(ex.imag, 0.4027192); +}); diff --git a/src/math/mod.ts b/src/math/mod.ts index 33ad0f9..6abc2c0 100644 --- a/src/math/mod.ts +++ b/src/math/mod.ts @@ -1,11 +1,22 @@ /** - * A collection of basic mathematics functionality for complex numbers, - * such as `sin()`, `atan()`, `log()`, etc. + * A collection of mathematical functions for operations on complex numbers, + * + * There are a number of functions providing robust handling for special + * cases where the real or imaginary parts are infinity or NaN, ensuring + * accurate and predictable results in all scenarios. * * @module */ -export * from "./exponential.ts"; -export * from "./hyperbolic.ts"; -export * from "./power.ts"; -export * from "./trigonometric.ts"; +export * from "./asin.ts"; +export * from "./asinh.ts"; +export * from "./atan.ts"; +export * from "./atanh.ts"; +export * from "./exp.ts"; +export * from "./log.ts"; +export * from "./pow.ts"; +export * from "./sin.ts"; +export * from "./sinh.ts"; +export * from "./sqrt.ts"; +export * from "./tan.ts"; +export * from "./tanh.ts"; diff --git a/src/math/pow.ts b/src/math/pow.ts new file mode 100644 index 0000000..f2a3b42 --- /dev/null +++ b/src/math/pow.ts @@ -0,0 +1,19 @@ +import type { complex } from "../complex.ts"; +import { exp } from "./exp.ts"; +import { log } from "./log.ts"; + +/** + * Raises a complex number to the complex `c`-th power. + * + * @example + * ```ts + * const i = new complex(0, 1); + * pow(i, i); // 0.2078796 + 0i + * ``` + * + * @param z The "base" complex number. + * @param c The "exponent" complex number. + */ +export function pow(z: complex, c: complex): complex { + return exp(log(z).mul(c)); +} diff --git a/src/math/pow_test.ts b/src/math/pow_test.ts new file mode 100644 index 0000000..a275f3c --- /dev/null +++ b/src/math/pow_test.ts @@ -0,0 +1,12 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals, assertEquals } from "@std/assert"; +import { complex } from "../complex.ts"; +import { pow } from "./pow.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("pow()", () => { + const i = new complex(0, 1); + const z = pow(i, i); + assertAlmostEquals(z.real, 0.2078796, TOLERANCE); + assertEquals(z.imag, 0); +}); diff --git a/src/math/power.ts b/src/math/power.ts deleted file mode 100644 index 1907b05..0000000 --- a/src/math/power.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { cmplx, Complex, type complex } from "../complex.ts"; -import { isInf, isNumber, POS_INF } from "../_utils.ts"; - -/** - * Returns the square root of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.sqrt(cmplx(4, 0)); // 2 - * ComplexMath.sqrt(cmplx(0, 0)); // 0 - * ``` - */ -export function sqrt(a: complex): complex { - let r = 0; - - if (a.imag === 0) { - if (a.real === 0) { - return cmplx(0, a.imag); - } - - r = Math.sqrt(Math.abs(a.real)); - - if (a.real < 0) { - return cmplx(r, a.imag); - } - - return cmplx(r, a.imag); - } - - if (isInf(a.imag)) { - return cmplx(POS_INF, a.imag); - } - - if (a.real === 0) { - r = Math.sqrt(Math.abs(a.imag) * 0.5); - - if (a.imag > 0) { - return cmplx(r, r); - } - - return cmplx(r, -r); - } - - let x = a.real; - let y = a.imag; - let scale = 0; - - if (Math.abs(x) > 4 || Math.abs(y) > 4) { - x *= 0.25; - y *= 0.25; - scale = 2; - } else { - x *= 1.8014398509481984e16; // 2^54 - y *= 1.8014398509481984e16; - scale = 7.450580596923828125e-9; // 2^-27 - } - - r = cmplx(x, y).abs(); - let t = 0; - - if (x > 0) { - t = Math.sqrt(0.5 * (r + x)); - r = scale * Math.abs((0.5 * y) / t); - t *= scale; - } else { - r = Math.sqrt(0.5 * (r - x)); - t = scale * Math.abs((0.5 * y) / r); - r *= scale; - } - - if (y < 0) { - return cmplx(t, -r); - } - - return cmplx(t, r); -} - -/** - * Returns the `n`-th power of a complex number. - * - * @param a A complex number - * @param n A real number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.pow(cmplx(0, 0), 4); // 0 + 0i - * ComplexMath.pow(cmplx(3, 1), 4); // ≈ 28 + 96i - * ``` - */ -export function pow(a: complex, n: number): complex; -/** - * Raises the complex number to the complex `b`-th power. - * - * @param a A complex number - * @param b The complex `b`-th power - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * const i = cmplx(0, 1); - * - * ComplexMath.pow(i, i); // i^i = e^(-pi / 2) = 0.20787957635076193 - * ``` - */ -export function pow(a: complex, b: complex): complex; -export function pow(a: complex, nb: number | complex): complex { - if (isNumber(nb)) { - nb = cmplx(nb); - } - - if (a.isZero()) { - if (nb.isNaN()) { - return Complex.NaN(); - } - - const real = nb.real; - const imag = nb.imag; - - if (real === 0) { - return cmplx(1); - } - - if (real < 0) { - if (imag === 0) { - return cmplx(POS_INF); - } - - return Complex.Inf(); - } - - return cmplx(0); - } - - const modulus = a.abs(); - if (modulus === 0) { - return cmplx(0); - } - - const arg = a.phase(); - - let r = Math.pow(modulus, nb.real); - let theta = nb.real * arg; - - if (nb.imag !== 0) { - r *= Math.exp(-nb.imag * arg); - theta += nb.imag * Math.log(modulus); - } - - const cos = Math.cos(theta); - const sin = Math.sin(theta); - - return cmplx(r * cos, r * sin); -} diff --git a/src/math/sin.ts b/src/math/sin.ts new file mode 100644 index 0000000..9ac0d8f --- /dev/null +++ b/src/math/sin.ts @@ -0,0 +1,31 @@ +import { complex } from "../complex.ts"; +import { cosh, sinh } from "./sinh.ts"; + +/** + * Returns the sine of a complex number. + * + * @example + * ```ts + * sin(new complex(1, 1)); // 1.2984576 + 0.6349639i + * ``` + * + * @param z A complex number. + */ +export function sin(z: complex): complex { + const w = sinh(new complex(-z.imag, z.real)); + return new complex(w.imag, -w.real); +} + +/** + * Returns the cosine of a complex number. + * + * @example + * ```ts + * cos(new complex(1, 1)); // 0.8337300 - 0.9888977i + * ``` + * + * @param z A complex number. + */ +export function cos(z: complex): complex { + return cosh(new complex(-z.imag, z.real)); +} diff --git a/src/math/sin_test.ts b/src/math/sin_test.ts new file mode 100644 index 0000000..acfa417 --- /dev/null +++ b/src/math/sin_test.ts @@ -0,0 +1,17 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { cos, sin } from "./sin.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("sin()", () => { + const z = sin(new complex(1, 1)); + assertAlmostEquals(z.real, 1.2984576, TOLERANCE); + assertAlmostEquals(z.imag, 0.6349639, TOLERANCE); +}); + +test("cos()", () => { + const z = cos(new complex(1, 1)); + assertAlmostEquals(z.real, 0.8337300, TOLERANCE); + assertAlmostEquals(z.imag, -0.9888977, TOLERANCE); +}); diff --git a/src/math/sinh.ts b/src/math/sinh.ts new file mode 100644 index 0000000..10e2d4a --- /dev/null +++ b/src/math/sinh.ts @@ -0,0 +1,170 @@ +import { complex } from "../complex.ts"; +import { copysign, IS_FINITE, IS_INF, POS_INF } from "../utils.ts"; + +/** + * Returns the hyperbolic sine of a complex number. + * + * ### Special cases + * + * | real | imaginary | `sinh()` | + * | ------------------------ | ------------------------ | -------------- | + * | ±0 | ±0 | (±0, ±0) | + * | ±0 | ±Inf | (±0, NaN) | + * | ±0 | NaN | (±0, NaN) | + * | x (any finite nonzero x) | ±Inf | (NaN, NaN) | + * | x (any finite nonzero x) | NaN | (NaN, NaN) | + * | ±Inf | ±0 | (±Inf, ±0) | + * | ±Inf | ±Inf | (±Inf, NaN) | + * | ±Inf | NaN | (±Inf, NaN) | + * | ±Inf | y (any finite nonzero y) | ±Inf \* cis(y) | + * | NaN | ±0 | (NaN, ±0) | + * | NaN | NaN | (NaN, NaN) | + * | NaN | ±Inf | (NaN, NaN) | + * | NaN | y (any finite nonzero y) | (NaN, NaN) | + * + * _cis(y) = cos(y) + i sin(y)_ + * + * @example + * ```ts + * sinh(new complex(1, 1)); // 0.6349639 + 1.2984576i + * ``` + * + * @param z A complex number. + */ +export function sinh(z: complex): complex { + const x = z.real; + const y = z.imag; + + // normal cases + if (IS_FINITE(x) && IS_FINITE(y)) { + if (y === 0) { + return new complex(Math.sinh(x), y); + } + return new complex( + Math.sinh(x) * Math.cos(y), + Math.cosh(x) * Math.sin(y), + ); + } + + // sinh(+-0 +- i Inf) = +-0 + i NaN + // The sign of the real part is unspecified. + // + // sinh(+-0 +- i NaN) = +-0 + i NaN + // + // sinh(x +- i Inf) = NaN + i NaN + // sinh(x +- i NaN) = NaN + i NaN + if (IS_FINITE(x) && !IS_FINITE(y)) { + if (x === 0) { + return new complex(x, NaN); + } + return new complex(NaN, NaN); + } + + // sinh(+-Inf +- i 0) = +-Inf +- i 0 + // sinh(NaN +- i 0) = NaN +- i 0 + if (!IS_FINITE(x) && y === 0) { + return new complex(x, y); + } + + // sinh(+-Inf +- i Inf) = +-Inf + i NaN + // sinh(+-Inf +- i NaN) = +-Inf + i NaN + // The sign of the real part is unspecified. + // + // sinh(+-Inf + y) = +-Inf * (cos(y) + i sin(y)) + if (IS_INF(x)) { + if (!IS_FINITE(y)) { + return new complex(x, NaN); + } + return new complex(x * Math.cos(y), POS_INF * Math.sin(y)); + } + + // sinh(NaN +- i NaN) = NaN + i NaN + // sinh(NaN +- i Inf) = NaN + i NaN + // sinh(NaN + i y) = NaN + i NaN + return new complex(NaN, NaN); +} + +/** + * Returns the hyperbolic cosine of a complex number. + * + * ### Special cases + * + * | real | imaginary | `cosh()` | + * | ------------------------ | ------------------------ | -------------- | + * | ±0 | ±0 | (1, ±0) | + * | ±0 | ±Inf | (NaN, ±0) | + * | ±0 | NaN | (NaN, ±0) | + * | x (any finite nonzero x) | ±Inf | (NaN, NaN) | + * | x (any finite nonzero x) | NaN | (NaN, NaN) | + * | ±Inf | ±0 | (±Inf, ±0) | + * | ±Inf | ±Inf | (±Inf, NaN) | + * | ±Inf | NaN | (±Inf, NaN) | + * | ±Inf | y (any finite nonzero y) | ±Inf \* cis(y) | + * | NaN | ±0 | (NaN, ±0) | + * | NaN | NaN | (NaN, NaN) | + * | NaN | ±Inf | (NaN, NaN) | + * | NaN | y (any finite nonzero y) | (NaN, NaN) | + * + * _cis(y) = cos(y) + i sin(y)_ + * + * @example + * ```ts + * cosh(new complex(1, 1)); // 0.8337300 + 0.9888977i + * ``` + * + * @param z A complex number. + */ +export function cosh(z: complex): complex { + const x = z.real; + const y = z.imag; + + // normal cases + if (IS_FINITE(x) && IS_FINITE(y)) { + if (y === 0) { + return new complex(Math.cosh(x), y); + } + return new complex( + Math.cosh(x) * Math.cos(y), + Math.sinh(x) * Math.sin(y), + ); + } + + // cosh(+-0 +- i Inf) = NaN +- i 0 + // cosh(+-0 +- i NaN) = NaN +- i 0 + // The sign of the imaginary part is unspecified. Choice = the sign of the real part + // + // cosh(x +- i Inf) = NaN + i NaN + // cosh(x +- i NaN) = NaN + i NaN + if (IS_FINITE(x) && !IS_FINITE(y)) { + if (x === 0) { + return new complex(NaN, copysign(0, x)); + } + return new complex(NaN, NaN); + } + + // cosh(+-Inf +- i 0) = +-Inf +- i 0 + // + // cosh(NaN +- i 0) = NaN +- i 0 + // The sign of the imaginary part is unspecified. + if (!IS_FINITE(x) && y === 0) { + return new complex(x, y); + } + + // cosh(+-Inf +- i Inf) = +-Inf + i NaN + // The sign of the real part is unspecified. + // + // cosh(+-Inf +- i NaN) = +-Inf + i NaN + // + // cosh(+-Inf + y) = +-Inf * (cos(y) + i sin(y)) + if (IS_INF(x)) { + if (!IS_FINITE(y)) { + return new complex(x, NaN); + } + return new complex(x * Math.cos(y), POS_INF * Math.sin(y)); + } + + // cosh(NaN +- i NaN) = NaN + i NaN + // cosh(NaN +- i Inf) = NaN + i NaN + // cosh(NaN + i y) = NaN + i NaN + return new complex(NaN, NaN); +} diff --git a/src/math/sinh_test.ts b/src/math/sinh_test.ts new file mode 100644 index 0000000..b73b01d --- /dev/null +++ b/src/math/sinh_test.ts @@ -0,0 +1,103 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals, assertEquals } from "@std/assert"; +import { cosh, sinh } from "./sinh.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("sinh() normal cases", () => { + assertEquals(sinh(new complex(0, 0)), new complex(0, 0)); + assertEquals(sinh(new complex(-0, -0)), new complex(-0, -0)); + + const result = sinh(new complex(1, 1)); + assertAlmostEquals(result.real, 0.6349639, TOLERANCE); + assertAlmostEquals(result.imag, 1.2984576, TOLERANCE); +}); + +test("sinh() special cases", () => { + const rd = 100 * Math.random(); + + assertEquals(sinh(new complex(0, Infinity)), new complex(0, NaN)); + assertEquals(sinh(new complex(0, -Infinity)), new complex(0, NaN)); + assertEquals(sinh(new complex(-0, NaN)), new complex(-0, NaN)); + + assertEquals( + sinh(new complex(rd, Infinity)), + new complex(NaN, NaN), + ); + assertEquals( + sinh(new complex(rd, -Infinity)), + new complex(NaN, NaN), + ); + assertEquals( + sinh(new complex(rd, NaN)), + new complex(NaN, NaN), + ); + + assertEquals(sinh(new complex(Infinity, 0)), new complex(Infinity, 0)); + assertEquals( + sinh(new complex(-Infinity, Infinity)), + new complex(-Infinity, NaN), + ); + assertEquals(sinh(new complex(Infinity, NaN)), new complex(Infinity, NaN)); + assertEquals( + sinh(new complex(-Infinity, 10)), + new complex(Infinity, -Infinity), + ); + + assertEquals(sinh(new complex(NaN, -0)), new complex(NaN, -0)); + assertEquals(sinh(new complex(NaN, NaN)), new complex(NaN, NaN)); + assertEquals(sinh(new complex(NaN, -Infinity)), new complex(NaN, NaN)); + assertEquals( + sinh(new complex(NaN, rd)), + new complex(NaN, NaN), + ); +}); + +test("cosh() normal cases", () => { + assertEquals(cosh(new complex(0, 0)), new complex(1, 0)); + assertEquals(cosh(new complex(-0, -0)), new complex(1, -0)); + + const result = cosh(new complex(1, 1)); + assertAlmostEquals(result.real, 0.8337300, TOLERANCE); + assertAlmostEquals(result.imag, 0.9888977, TOLERANCE); +}); + +test("cosh() special cases", () => { + const rd = 100 * Math.random(); + + assertEquals(cosh(new complex(0, Infinity)), new complex(NaN, 0)); + assertEquals(cosh(new complex(0, -Infinity)), new complex(NaN, 0)); + assertEquals(cosh(new complex(-0, NaN)), new complex(NaN, -0)); + + assertEquals( + cosh(new complex(rd, Infinity)), + new complex(NaN, NaN), + ); + assertEquals( + cosh(new complex(rd, -Infinity)), + new complex(NaN, NaN), + ); + assertEquals( + cosh(new complex(rd, NaN)), + new complex(NaN, NaN), + ); + + assertEquals(cosh(new complex(Infinity, 0)), new complex(Infinity, 0)); + assertEquals( + cosh(new complex(-Infinity, Infinity)), + new complex(-Infinity, NaN), + ); + assertEquals(cosh(new complex(Infinity, NaN)), new complex(Infinity, NaN)); + assertEquals( + cosh(new complex(-Infinity, 10)), + new complex(Infinity, -Infinity), + ); + + assertEquals(cosh(new complex(NaN, -0)), new complex(NaN, -0)); + assertEquals(cosh(new complex(NaN, NaN)), new complex(NaN, NaN)); + assertEquals(cosh(new complex(NaN, -Infinity)), new complex(NaN, NaN)); + assertEquals( + cosh(new complex(NaN, rd)), + new complex(NaN, NaN), + ); +}); diff --git a/src/math/sqrt.ts b/src/math/sqrt.ts new file mode 100644 index 0000000..2e523af --- /dev/null +++ b/src/math/sqrt.ts @@ -0,0 +1,84 @@ +import { complex } from "../complex.ts"; +import { copysign, IS_INF, IS_NAN, POS_INF, sign } from "../utils.ts"; + +/** + * Returns the square root of a complex number. + * + * ### Special cases + * + * | real | imaginary | `sqrt()` | + * | ----------------- | ---------------- | ------------ | + * | ±0 | ±0 | (+0, ±0) | + * | x (including NaN) | ±Inf | (+Inf, ±Inf) | + * | x (any finite x) | NaN | (NaN, NaN) | + * | -Inf | y (any finite y) | (+0, ±Inf) | + * | +Inf | y (any finite y) | (+Inf, ±0) | + * | -Inf | NaN | (NaN, +Inf) | + * | +Inf | NaN | (+Inf, NaN) | + * | NaN | y | (NaN, NaN) | + * | NaN | NaN | (NaN, NaN) | + * + * @example + * ```ts + * sqrt(new complex(-4, 0)); // 0 + 2i + * ``` + * + * @param z A complex number. + */ +export function sqrt(z: complex): complex { + let x = z.real; + let y = z.imag; + + if (x === 0 && y === 0) { + return new complex(0, y); + } + + // sqrt(x +- i Inf) = Inf +- i Inf + // sqrt(NaN +- i Inf) = Inf +- i Inf + if (IS_INF(y)) { + return new complex(POS_INF, y); + } + + // sqrt(NaN + i y) = NaN + i NaN + // sqrt(NaN + i NaN) = NaN + i NaN + if (IS_NAN(x)) { + return new complex(NaN, NaN); + } + + // sqrt(-Inf + i y) = 0 + i Inf + // sqrt(-Inf + i NaN) = NaN +- i Inf + // sqrt(+Inf + i y) = Inf + i 0 + // sqrt(+Inf + i NaN) = Inf + i NaN + if (IS_INF(x)) { + if (sign(x)) { + return new complex(Math.abs(y - y), copysign(x, y)); + } + return new complex(x, copysign(y - y, y)); + } + + let scale; + // Rescale avoiding overflow and underflow + if (Math.abs(x) > 4 || Math.abs(y) > 4) { + x *= 0.25; + y *= 0.25; + scale = 2; + } else { + x *= 1.8014398509481984e16; // 2**54 + y *= 1.8014398509481984e16; + scale = 7.450580596923828125e-9; // 2**-27 + } + + let t; + let r = Math.hypot(x, y); + if (x >= 0) { + t = Math.sqrt(0.5 * (r + x)); + r = scale * Math.abs(y / (2 * t)); + t *= scale; + } else { + r = Math.sqrt(0.5 * (r - x)); + t = scale * Math.abs(y / (2 * r)); + r *= scale; + } + + return new complex(t, copysign(r, y)); +} diff --git a/src/math/sqrt_test.ts b/src/math/sqrt_test.ts new file mode 100644 index 0000000..1bb18d2 --- /dev/null +++ b/src/math/sqrt_test.ts @@ -0,0 +1,38 @@ +import { test } from "@cross/test"; +import { assertEquals } from "@std/assert"; +import { complex } from "../complex.ts"; +import { sqrt } from "./sqrt.ts"; + +test("sqrt() normal cases", () => { + assertEquals(sqrt(new complex(-0, -0)), new complex(0, -0)); + + const z = sqrt(new complex(-4, 0)); + assertEquals(z.real, 0); + assertEquals(z.imag, 2); + + const w = sqrt(new complex(-4, -0)); + assertEquals(w.real, 0); + assertEquals(w.imag, -2); +}); + +test("sqrt() special cases", () => { + const rd = 100 * Math.random(); + + assertEquals( + sqrt(new complex(-10, Infinity)), + new complex(Infinity, Infinity), + ); + assertEquals( + sqrt(new complex(NaN, Infinity)), + new complex(Infinity, Infinity), + ); + assertEquals(sqrt(new complex(rd, NaN)), new complex(NaN, NaN)); + + assertEquals(sqrt(new complex(-Infinity, rd)), new complex(0, Infinity)); + assertEquals(sqrt(new complex(Infinity, -rd)), new complex(Infinity, -0)); + assertEquals(sqrt(new complex(-Infinity, NaN)), new complex(NaN, Infinity)); + assertEquals(sqrt(new complex(Infinity, NaN)), new complex(Infinity, NaN)); + + assertEquals(sqrt(new complex(NaN, rd)), new complex(NaN, NaN)); + assertEquals(sqrt(new complex(NaN, NaN)), new complex(NaN, NaN)); +}); diff --git a/src/math/tan.ts b/src/math/tan.ts new file mode 100644 index 0000000..6d487d2 --- /dev/null +++ b/src/math/tan.ts @@ -0,0 +1,17 @@ +import { complex } from "../mod.ts"; +import { tanh } from "./tanh.ts"; + +/** + * Returns the tangent of a complex number. + * + * @example + * ```ts + * tan(new complex(1, 1)); // 0.2717526 + 1.0839233i + * ``` + * + * @param z A complex number + */ +export function tan(z: complex): complex { + const w = tanh(new complex(-z.imag, z.real)); + return new complex(w.imag, -w.real); +} diff --git a/src/math/tan_test.ts b/src/math/tan_test.ts new file mode 100644 index 0000000..ed5399d --- /dev/null +++ b/src/math/tan_test.ts @@ -0,0 +1,11 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals } from "@std/assert"; +import { tan } from "./tan.ts"; +import { complex } from "../complex.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("tan()", () => { + const z = tan(new complex(1, 1)); + assertAlmostEquals(z.real, 0.2717526, TOLERANCE); + assertAlmostEquals(z.imag, 1.0839233, TOLERANCE); +}); diff --git a/src/math/tanh.ts b/src/math/tanh.ts new file mode 100644 index 0000000..6037b92 --- /dev/null +++ b/src/math/tanh.ts @@ -0,0 +1,70 @@ +import { complex } from "../mod.ts"; +import { copysign, IS_FINITE, IS_NAN } from "../utils.ts"; + +/** + * Returns the hyperbolic tangent of a complex number. + * + * ### Special cases + * + * | real | imaginary | `tanh()` | + * | ------------------------ | ----------------- | ---------- | + * | ±0 | ±0 | (±0, ±0) | + * | ±0 | ±Inf | (±0, NaN) | + * | ±0 | NaN | (±0, NaN) | + * | x (any finite nonzero x) | ±Inf | (NaN, NaN) | + * | x (any finite nonzero x) | NaN | (NaN, NaN) | + * | ±Inf | ±Inf | (±1, ±0) | + * | ±Inf | y (any finite y) | (±1, ±0) | + * | ±Inf | NaN | (±1, +0) | + * | NaN | ±0 | (NaN, ±0) | + * | NaN | y (any nonzero y) | (NaN, NaN) | + * | NaN | NaN | (NaN, NaN) | + * + * @example + * ```ts + * tanh(new complex(1, 1)); // 1.0839233 + 0.2717526i + * ``` + * + * @param z A complex number. + */ +export function tanh(z: complex): complex { + const x = z.real; + const y = z.imag; + + // tanh(NaN +- i 0) = NaN +- i 0 + // tanh(NaN + i y) = NaN + i NaN (y != 0) + // + // tanh(+-Inf +- i Inf) = +-1 +- i 0 + // The sign of the imaginary part is unspecified. + // + // tanh(+-Inf + i y) = +-1 +- i 0 + // + // tanh(+-Inf + i NaN) = +-1 + i 0 + // The sign of the imaginary part is unspecified. + if (!IS_FINITE(x)) { + if (IS_NAN(x)) { + return new complex(x, y === 0 ? y : x * y); + } + return new complex( + copysign(1, x), + copysign(0, y), + ); + } + + // tanh(+-0 +- i Inf) = +-0 + i NaN + // tanh(+-0 + i NaN) = +-0 + i NaN + // tanh(x +- i Inf) = NaN + i NaN (x != 0) + // tanh(x +- i NaN) = NaN + i NaN (x != 0) + if (!IS_FINITE(y)) { + return new complex(x ? y - y : x, y - y); + } + + // Kahan's algorithm + const t = Math.tan(y); + const beta = 1 + t * t; // 1 / cos^2(x) = 1 + tan^2(x) + const s = Math.sinh(x); + const rho = Math.sqrt(1 + s * s); // = cosh(x) + const d = 1 + beta * s * s; + + return new complex((beta * rho * s) / d, t / d); +} diff --git a/src/math/tanh_test.ts b/src/math/tanh_test.ts new file mode 100644 index 0000000..38ceae9 --- /dev/null +++ b/src/math/tanh_test.ts @@ -0,0 +1,30 @@ +import { test } from "@cross/test"; +import { assertAlmostEquals, assertEquals } from "@std/assert"; +import { complex } from "../complex.ts"; +import { tanh } from "./tanh.ts"; +import { TOLERANCE } from "../utils.ts"; + +test("tanh() normal cases", () => { + assertEquals(tanh(new complex(0, -0)), new complex(0, -0)); + + const z = tanh(new complex(1, 1)); + assertAlmostEquals(z.real, 1.0839233, TOLERANCE); + assertAlmostEquals(z.imag, 0.2717526, TOLERANCE); +}); + +test("tanh() special cases", () => { + const rd = 100 * Math.random(); + + assertEquals(tanh(new complex(0, Infinity)), new complex(0, NaN)); + assertEquals(tanh(new complex(-0, NaN)), new complex(-0, NaN)); + assertEquals(tanh(new complex(rd, -Infinity)), new complex(NaN, NaN)); + assertEquals(tanh(new complex(rd, NaN)), new complex(NaN, NaN)); + + assertEquals(tanh(new complex(Infinity, -Infinity)), new complex(1, -0)); + assertEquals(tanh(new complex(-Infinity, -rd)), new complex(-1, -0)); + assertEquals(tanh(new complex(Infinity, NaN)), new complex(1, 0)); + + assertEquals(tanh(new complex(NaN, 0)), new complex(NaN, 0)); + assertEquals(tanh(new complex(NaN, rd)), new complex(NaN, NaN)); + assertEquals(tanh(new complex(NaN, NaN)), new complex(NaN, NaN)); +}); diff --git a/src/math/trigonometric.ts b/src/math/trigonometric.ts deleted file mode 100644 index 8bc007c..0000000 --- a/src/math/trigonometric.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { cmplx, Complex, type complex } from "../complex.ts"; -import { - coshSinh, - isInf, - isNaN2, - POS_INF, - reducePi, - sign, - tans, -} from "../_utils.ts"; -import { log } from "./exponential.ts"; -import { sqrt } from "./power.ts"; - -/** - * Returns the sine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.sin(cmplx(1, 1)); // 1.2984575814159773 + 0.6349639147847361i - * ``` - */ -export function sin(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (i === 0 && (isInf(r) || isNaN2(r))) { - return cmplx(NaN, i); - } - - if (isInf(i)) { - if (r === 0) { - return cmplx(r, i); - } - - if (isNaN2(r) || isInf(r)) { - return cmplx(NaN, i); - } - } - - if (r === 0 && isNaN2(i)) { - return cmplx(r, i); - } - - const [cosh, sinh] = coshSinh(i); - - return cmplx( - Math.sin(r) * cosh, - Math.cos(r) * sinh, - ); -} - -/** - * Returns the cosine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.cos(cmplx(1, 1)); // 0.8337300251311491 - 0.9888977057628651i - * ``` - */ -export function cos(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (i === 0 && (isInf(r) || isNaN2(r))) { - return cmplx(NaN); - } - - if (isInf(i)) { - if (r === 0) { - return cmplx(POS_INF); - } - - if (isNaN2(r) || isInf(r)) { - return cmplx(POS_INF, NaN); - } - } - - if (r === 0 && isNaN2(i)) { - return cmplx(NaN); - } - - const [cosh, sinh] = coshSinh(i); - - return cmplx( - Math.cos(r) * cosh, - -Math.sin(r) * sinh, - ); -} - -/** - * Returns the tangent of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.tan(cmplx(1, 1)); // 0.27175258531951174 + 1.0839233273386948i - * ``` - */ -export function tan(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (isInf(i)) { - return cmplx(0, sign(i)); - } - - if (r === 0 && isNaN2(i)) { - return cmplx(r, i); - } - - let d = Math.cosh(2 * i) + Math.cos(2 * r); - - if (Math.abs(d) < 0.25) { - d = tans(a); - } - - if (d === 0) { - return Complex.Inf(); - } - - return cmplx( - Math.sin(2 * r) / d, - Math.sinh(2 * i) / d, - ); -} - -/** - * Returns the cotangent of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.cot(cmplx(1, 1)); // 0.2176215618544027 - 0.868014142895925i - * ``` - */ -export function cot(a: complex): complex { - const r = a.real; - const i = a.imag; - - let d = Math.cosh(2 * i) - Math.cos(2 * r); - - if (Math.abs(d) < 0.25) { - d = tans(a); - } - - if (d === 0) { - return Complex.Inf(); - } - - return cmplx( - Math.sin(2 * r) / d, - -Math.sinh(2 * i) / d, - ); -} - -/** - * Returns the arc sine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.asin(cmplx(1, 1)); // 0.6662394324925153 + 1.0612750619050355i - * ``` - */ -export function asin(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (i === 0 && Math.abs(r) <= 1) { - return cmplx(Math.asin(r), i); - } - - if (r === 0 && Math.abs(i) <= 1) { - return cmplx(r, Math.asinh(i)); - } - - if (isNaN2(i)) { - if (r === 0) { - return cmplx(r, NaN); - } - - if (isInf(r)) { - return cmplx(NaN, r); - } - - return Complex.NaN(); - } - - if (isInf(i)) { - if (isNaN2(r)) { - return cmplx(r, i); - } - - if (isInf(r)) { - return cmplx(sign(r) * Math.PI / 4, i); - } - - return cmplx(0, i); - } - - if (isInf(r)) { - return cmplx( - sign(r) * (Math.PI / 2), - sign(i) * r, - ); - } - - const ct = cmplx(-i, r); // (a + bi) * i = -b + ai - - const aa = cmplx((r - i) * (r + i), 2 * r * i); - const a1 = cmplx(1 - aa.real, -aa.imag); - const a2 = log(sqrt(a1).add(ct)); - - return cmplx(a2.imag, -a2.real); // (a + bi) * (-i) = b - ai -} - -/** - * Returns the arc cosine of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.acos(cmplx(1, 1)); // 0.9045568943023813 - 1.0612750619050355i - * ``` - */ -export function acos(a: complex): complex { - const as = asin(a); - return cmplx((Math.PI / 2) - as.real, -as.imag); -} - -/** - * Returns the arc tangent of the complex number. - * - * @param a A complex number - * - * @example - * ```ts - * import { ComplexMath, cmplx } from "@babia/complex"; - * - * ComplexMath.atan(cmplx(1, 1)); // 1.0172219678978514 + 0.40235947810852507i - * ``` - */ -export function atan(a: complex): complex { - const r = a.real; - const i = a.imag; - - if (i === 0) { - return cmplx(Math.atan(r), i); - } - - if (r === 0 && Math.abs(i) <= 1) { - return cmplx(r, Math.atanh(i)); - } - - if (isInf(r) || isInf(i)) { - if (isNaN2(r)) { - return cmplx(NaN); - } - - return cmplx(sign(r) * (Math.PI / 2)); - } - - if (isNaN2(r) || isNaN2(i)) { - return Complex.NaN(); - } - - const r2 = r * r; - - let aa = 1 - r2 - (i * i); - if (aa === 0) { - return Complex.Inf(); - } - - let t = 0.5 * Math.atan2(2 * r, aa); - const w = reducePi(t); - - t = i - 1; - aa = r2 + (t * t); - - if (aa === 0) { - return Complex.Inf(); - } - - t = i + 1; - aa = (r2 + (t * t)) / aa; - - return cmplx(w, 0.25 * Math.log(aa)); -} diff --git a/src/mod.ts b/src/mod.ts new file mode 100644 index 0000000..73f221a --- /dev/null +++ b/src/mod.ts @@ -0,0 +1,71 @@ +/** + * A package providing implementation of complex numbers and mathematical + * functions. + * + * The {@linkcode complex} class represents a complex number which has a + * real part and an imaginary part. It is expressed in standard form + * `a + bi`, where: + * - `a`: the real part + * - `b`: the imaginary part + * - `i`: the imaginary unit + * + * You can also create a complex number using {@linkcode cmplx} function. + * + * ```ts + * const cmplx1 = cmplx(1, 9); // 1 + 9i + * const cmplx2 = cmplx(5, 4); // 5 + 4i + * ``` + * + * The {@linkcode complex} class has methods to perform basic operations, + * such as `add()`, `sub()`, etc. + * + * ```ts + * const cmplx1 = cmplx(1, 9); // 1 + 9i + * const cmplx2 = cmplx(5, 4); // 5 + 4i + * + * cmplx1.add(cmplx2); // (1 + 9i) + (5 + 4i) + * cmplx1.sub(cmplx2); // (1 + 9i) - (5 + 4i) + * ``` + * + * Besides that, this package also has `cmath` which is a collection of + * mathematics functionality for complex numbers, such as `sin()`, `atan()`, + * `log()`, etc. + * + * ```ts + * const cmplx1 = cmplx(1, 9); // 1 + 9i + * const cmplx2 = cmplx(5, 4); // 5 + 4i + * + * cmath.sqrt(cmplx1); + * cmath.log(cmplx1); + * cmath.pow(cmplx2, 3); + * ``` + * + * Based on + * - https://en.cppreference.com/w/cpp/header/complex + * - https://git.musl-libc.org/cgit/musl/tree/src/complex + * - https://pkg.go.dev/math/cmplx. + * + * @module + */ + +import { complex } from "./complex.ts"; +import * as cmath from "./math/mod.ts"; + +/** + * Creates a new complex number. + * + * @example + * ```ts + * cmplx(3); // 3 + 0i + * cmplx(9, -7); // 9 - 7i + * ``` + * + * @param re The real part. + * @param im The imaginary part. + * @returns A new complex number. + */ +function cmplx(re: number, im?: number): complex { + return new complex(re, im); +} + +export { cmath, cmplx, complex }; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..61d6cfc --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,35 @@ +export const TOLERANCE = 1e-7; + +export const POS_INF = Number.POSITIVE_INFINITY; +export const NEG_INF = Number.NEGATIVE_INFINITY; + +export function IS_NAN(x: number): boolean { + return Number.isNaN(x); +} + +export function IS_FINITE(x: number): boolean { + return Number.isFinite(x); +} + +export function IS_INF(x: number): boolean { + return !IS_NAN(x) && !IS_FINITE(x); +} + +export function IS_NUMBER(x: unknown): x is number { + return typeof x === "number"; +} + +/** Gets the sign bit from the number. `true` if the number is negative. */ +export function sign(x: number): boolean { + if (x === 0) { + // Whether x is positive or negative zero + const inf = 1 / x; + return inf === NEG_INF; + } + + return Math.sign(x) < 0; +} + +export function copysign(mag: number, sgn: number): number { + return sign(mag) === sign(sgn) ? mag : -mag; +} diff --git a/tests/complex_test.ts b/tests/complex_test.ts deleted file mode 100644 index 07c02ea..0000000 --- a/tests/complex_test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { test } from "@cross/test"; -import { assertAlmostEquals, assertEquals } from "@std/assert"; -import { cmplx } from "../src/complex.ts"; - -test("Complex parts", () => { - const ex = cmplx(3, 5); - - assertEquals(ex.real, 3); - assertEquals(ex.imag, 5); - assertEquals(String(ex), "3 + 5i"); -}); - -test("Complex addition", () => { - const ex1 = cmplx(3, 5); - const ex2 = cmplx(3, 5); - - assertEquals(ex1.add(ex2), cmplx(6, 10)); - assertEquals(ex1.add(2), cmplx(5, 5)); -}); - -test("Complex subtraction", () => { - const ex1 = cmplx(86, 15); - const ex2 = cmplx(21, 9); - - assertEquals(ex1.sub(ex2), cmplx(65, 6)); - assertEquals(ex1.sub(7), cmplx(79, 15)); -}); - -test("Complex multiplication", () => { - const ex1 = cmplx(3, 2); - const ex2 = cmplx(4, -1); - - assertEquals(ex1.mult(ex2), cmplx(14, 5)); - assertEquals(ex1.mult(3), cmplx(9, 6)); -}); - -test("Complex division", () => { - const ex1 = cmplx(3, 2); - const ex2 = cmplx(4, -1); - - const result = ex1.div(ex2); - - assertAlmostEquals(result.real, 0.588235294); - assertAlmostEquals(result.imag, 0.647058824); - assertEquals(ex1.div(2), cmplx(1.5, 1)); -}); - -test("Complex conjugate, absolute value, argument", () => { - const ex = cmplx(10, 10); - - assertEquals(ex.abs(), 10 * Math.sqrt(2)); - assertEquals(ex.conj(), cmplx(10, -10)); - assertEquals(ex.phase(), Math.PI / 4); -}); diff --git a/tests/exponential_test.ts b/tests/exponential_test.ts deleted file mode 100644 index b3475f2..0000000 --- a/tests/exponential_test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test } from "@cross/test"; -import { assertAlmostEquals, assertEquals } from "@std/assert"; -import { exp, log, log10 } from "../src/math/mod.ts"; -import { cmplx } from "../src/complex.ts"; - -test("Complex exponential", () => { - assertEquals(exp(cmplx(Infinity)), cmplx(Infinity)); - assertEquals(exp(cmplx(-Infinity, NaN)), cmplx(0)); - - const ex = exp(cmplx(3, 4)); - - assertAlmostEquals(ex.real, -13.1287831); - assertAlmostEquals(ex.imag, -15.2007845); -}); - -test("Complex natural logarithm", () => { - const ex = log(cmplx(3, 4)); - - assertAlmostEquals(ex.real, 1.6094379); - assertAlmostEquals(ex.imag, 0.9272952); -}); - -test("Complex base 10 logarithm", () => { - const ex = log10(cmplx(3, 4)); - - assertAlmostEquals(ex.real, 0.6989700); - assertAlmostEquals(ex.imag, 0.4027192); -}); diff --git a/tests/hyperbolic_test.ts b/tests/hyperbolic_test.ts deleted file mode 100644 index f59de6f..0000000 --- a/tests/hyperbolic_test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { test } from "@cross/test"; -import { assertAlmostEquals, assertEquals } from "@std/assert"; -import { acosh, asinh, atanh, cosh, sinh, tanh } from "../src/math/mod.ts"; -import { cmplx } from "../src/complex.ts"; - -test("Complex hyperbolic sine", () => { - assertEquals( - sinh(cmplx(0, Infinity)), - cmplx(0, NaN), - ); - assertEquals( - sinh(cmplx(Infinity, Infinity)), - cmplx(Infinity, NaN), - ); - assertEquals( - sinh(cmplx(NaN, 0)), - cmplx(NaN, 0), - ); - - const ex = sinh(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.6349639); - assertAlmostEquals(ex.imag, 1.2984576); -}); - -test("Complex hyperbolic cosine", () => { - assertEquals( - cosh(cmplx(0, Infinity)), - cmplx(NaN, 0), - ); - assertEquals( - cosh(cmplx(Infinity, Infinity)), - cmplx(Infinity, NaN), - ); - assertEquals( - cosh(cmplx(NaN, 0)), - cmplx(NaN, 0), - ); - - const ex = cosh(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.8337300); - assertAlmostEquals(ex.imag, 0.9888977); -}); - -test("Complex hyperbolic tangent", () => { - assertEquals( - tanh(cmplx(-Infinity, 32)), - cmplx(-1, 0), - ); - assertEquals( - tanh(cmplx(Infinity, Infinity)), - cmplx(1, 0), - ); - assertEquals( - tanh(cmplx(NaN, 0)), - cmplx(NaN, 0), - ); - - const ex = tanh(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 1.0839233); - assertAlmostEquals(ex.imag, 0.2717526); -}); - -test("Complex inverse hyperbolic sine", () => { - const ex = asinh(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 1.0612751); - assertAlmostEquals(ex.imag, 0.6662394); -}); - -test("Complex inverse hyperbolic cosine", () => { - const ex = acosh(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 1.0612751); - assertAlmostEquals(ex.imag, 0.9045569); -}); - -test("Complex inverse hyperbolic tangent", () => { - const ex = atanh(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.4023595); - assertAlmostEquals(ex.imag, 1.0172220); -}); diff --git a/tests/power_test.ts b/tests/power_test.ts deleted file mode 100644 index 51b3264..0000000 --- a/tests/power_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { test } from "@cross/test"; -import { assertAlmostEquals, assertEquals } from "@std/assert"; -import { pow, sqrt } from "../src/math/mod.ts"; -import { cmplx, Complex } from "../src/complex.ts"; -import { POS_INF } from "../src/_utils.ts"; - -test("Complex square root", () => { - assertEquals(sqrt(cmplx(0)), cmplx(0)); - assertEquals(sqrt(cmplx(4)), cmplx(2)); - assertEquals( - sqrt(cmplx(4, Infinity)), - cmplx(Infinity, Infinity), - ); - - const ex = sqrt(cmplx(5, 9)); - - assertAlmostEquals(ex.real, 2.7654683); - assertAlmostEquals(ex.imag, 1.6272108); -}); - -test("Complex with real power", () => { - assertEquals( - pow(cmplx(0), NaN), - cmplx(NaN, NaN), - ); - assertEquals(pow(cmplx(0), 32), cmplx(0)); - assertEquals( - pow(cmplx(0), -4), - cmplx(Infinity, 0), - ); - - const ex = pow(cmplx(3, 1), 4); - - assertAlmostEquals(ex.real, 28); - assertAlmostEquals(ex.imag, 96); -}); - -test("Complex with complex power", () => { - assertEquals( - pow(cmplx(0), Complex.NaN()), - cmplx(NaN, NaN), - ); - assertEquals( - pow(cmplx(0), cmplx(0, 9)), - cmplx(1), - ); - assertEquals( - pow(cmplx(0), cmplx(-1)), - cmplx(POS_INF, 0), - ); - assertEquals( - pow(cmplx(0, 1), cmplx(0, 1)), - cmplx(Math.pow(Math.E, -Math.PI / 2)), - ); -}); diff --git a/tests/trigonometric_test.ts b/tests/trigonometric_test.ts deleted file mode 100644 index a1350d9..0000000 --- a/tests/trigonometric_test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { test } from "@cross/test"; -import { assertAlmostEquals, assertEquals } from "@std/assert"; -import { - acos, - asin, - asinh, - atan, - atanh, - cos, - cot, - sin, - sinh, - tan, - tanh, -} from "../src/math/mod.ts"; -import { cmplx } from "../src/complex.ts"; - -const POS_I = cmplx(0, 1); -const NEG_I = cmplx(0, -1); - -test("Complex sine", () => { - assertEquals( - sin(cmplx(Infinity)), - cmplx(NaN), - ); - assertEquals( - sin(cmplx(0, -Infinity)), - cmplx(0, -Infinity), - ); - assertEquals( - sin(cmplx(NaN, -Infinity)), - cmplx(NaN, -Infinity), - ); - - const ex = sin(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 1.2984576); - assertAlmostEquals(ex.imag, 0.6349639); - - // sin(a) = (-i) * sinh(a * i) - - const a = cmplx(2, 3); - const x = sin(a); - const y = sinh(a.mult(POS_I)).mult(NEG_I); - - assertAlmostEquals(x.real, y.real); - assertAlmostEquals(x.imag, y.imag); -}); - -test("Complex cosine", () => { - assertEquals( - cos(cmplx(Infinity)), - cmplx(NaN), - ); - assertEquals( - cos(cmplx(0, -Infinity)), - cmplx(Infinity), - ); - assertEquals( - cos(cmplx(NaN, -Infinity)), - cmplx(Infinity, NaN), - ); - - const ex = cos(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.8337300); - assertAlmostEquals(ex.imag, -0.9888977); -}); - -test("Complex tangent", () => { - assertEquals( - tan(cmplx(0, NaN)), - cmplx(0, NaN), - ); - assertEquals( - tan(cmplx(3, -Infinity)), - cmplx(0, -1), - ); - - const ex = tan(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.2717526); - assertAlmostEquals(ex.imag, 1.0839233); - - // tan(a) = (-i) * tanh(a * i) - - const a = cmplx(2, 3); - const x = tan(a); - const y = tanh(a.mult(POS_I)).mult(NEG_I); - - assertAlmostEquals(x.real, y.real); - assertAlmostEquals(x.imag, y.imag); -}); - -test("Complex cotangent", () => { - const ex = cot(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.2176216); - assertAlmostEquals(ex.imag, -0.8680141); -}); - -test("Complex arc sine", () => { - assertEquals( - asin(cmplx(-Infinity, NaN)), - cmplx(NaN, -Infinity), - ); - assertEquals( - asin(cmplx(0, NaN)), - cmplx(0, NaN), - ); - assertEquals( - asin(cmplx(Infinity, Infinity)), - cmplx(Math.PI / 4, Infinity), - ); - - const ex = asin(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.6662394); - assertAlmostEquals(ex.imag, 1.0612750); - - // asin(a) = (-i) * asinh(a * i) - - const a = cmplx(2, 3); - const x = asin(a); - const y = asinh(a.mult(POS_I)).mult(NEG_I); - - assertAlmostEquals(x.real, y.real); - assertAlmostEquals(x.imag, y.imag); -}); - -test("Complex arc cosine", () => { - const ex = acos(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 0.9045569); - assertAlmostEquals(ex.imag, -1.0612750); -}); - -test("Complex arc tangent", () => { - assertEquals( - atan(cmplx(NaN, -Infinity)), - cmplx(NaN), - ); - assertEquals( - atan(cmplx(Infinity, Infinity)), - cmplx(Math.PI / 2), - ); - - const ex = atan(cmplx(1, 1)); - - assertAlmostEquals(ex.real, 1.0172220); - assertAlmostEquals(ex.imag, 0.4023595); - - // atan(a) = (-i) * atanh(a * i) - - const a = cmplx(2, 3); - const x = atan(a); - const y = atanh(a.mult(POS_I)).mult(NEG_I); - - assertAlmostEquals(x.real, y.real); - assertAlmostEquals(x.imag, y.imag); -});