complex.js
Complex.js is a focused JavaScript implementation of complex-number arithmetic. It constructs values from real and imaginary components, polar coordinates, arrays, numbers, or strings such as `3 + 4i`, then provides immutable-style methods for arithmetic, roots, powers, logarithms, trigonometry, conjugates, magnitude, and phase. It is a small numerical building block rather than a general algebra system: you get one complex-number type, constants, parsing, formatting, and TypeScript declarations, but no matrices, units, symbolic expressions, arbitrary precision, or equation parser.
Install Complex.js when you need a compact, typed complex-number value with a wide elementary function set. Pick a broader mathematics package for matrices or expressions, and verify branch and floating-point behavior before using it in sensitive numerical code.
Use it if
- You need complex arithmetic in JavaScript without adopting a full mathematics suite
- You want the same zero-dependency package in ESM, CommonJS, TypeScript, Node.js, or a direct browser script
- Your inputs arrive in several practical forms, including strings, rectangular pairs, arrays, and polar coordinates
- You need elementary complex functions such as powers, logarithms, roots, circular trig, and hyperbolic trig
- You need matrices, units, symbolic expressions, statistics, or an expression evaluator; the README documents only a complex-number type, while mathjs covers the broader job
- You need arbitrary-precision results; the public declarations store `re` and `im` as JavaScript numbers, so every operation uses binary floating-point
- You need configurable branch conventions for inverse trigonometric functions; the README explicitly fixes `acot` to the textbook real range and documents a jump across the negative real axis
- You need strict validation of numeric object fields at the TypeScript boundary; the runtime parser accepts object shapes, but the README warns that non-Number attributes cause undefined behavior
- You expect IEEE-style signed complex infinities; the source collapses non-finite complex values into a single `Complex.INFINITY`, and the tests show division by zero returning that sentinel
Setup reality
Installation is only `npm install complex.js`; there are no runtime dependencies, peer dependencies, credentials, native extensions, generated files, or configuration. Version 2.4.3 exposes separate CommonJS and ESM builds through `exports`, and its `complex.d.ts` file supplies TypeScript types, so `require('complex.js')` and `import Complex from 'complex.js'` both work. A browser can load `dist/complex.min.js` directly, but the README's script-tag example assumes you have copied that file or otherwise arranged a URL; npm does not configure a CDN or global asset path for you. The first surprise is semantic rather than operational. Methods return new `Complex` objects, so assign the result of `add`, `mul`, or `round` instead of expecting mutation. String parsing throws `SyntaxError: Invalid Param` for malformed input and accepts a library-specific grammar, not general mathematical expressions. Objects must contain one of the documented complete pairs such as `{ re, im }` or `{ abs, arg }`, and the README warns that their values must be Numbers. Results use ordinary floating-point, `equals()` applies the fixed `Complex.EPSILON` value of 1e-15, `toString()` also hides components below that threshold, and branch cuts matter for inverse trig and logarithms. Infinity is modeled as a library sentinel rather than preserving every signed component. Those choices are reasonable, but tests around branch boundaries, zero, NaN, Infinity, and rounding belong in any numerical application that depends on exact conventions.
Patterns
Construct rectangular complex valuesconstruct-values
import Complex from 'complex.js';
const a = new Complex(3, 4);
const b = Complex({ re: -2, im: 0.5 });
const c = Complex([7, -1]);
console.log(a.re, a.im); // 3 4Calling `Complex(...)` without `new` is supported. Object fields must be numbers, and extra fields are not preserved.
Parse a complex-number stringparse-string
import Complex from 'complex.js';
function parseComplex(input) {
try {
return Complex(input);
} catch (error) {
if (error instanceof SyntaxError) return null;
throw error;
}
}
console.log(parseComplex('3 - 4i')?.toString());The parser understands complex literals, not arbitrary expressions. Invalid text throws `SyntaxError: Invalid Param`.
Construct from polar coordinatesuse-polar-form
import Complex from 'complex.js';
const radius = 2;
const angle = Math.PI / 3;
const z = Complex({ abs: radius, arg: angle });
console.log(z.re, z.im);
console.log(z.abs(), z.arg());Angles are in radians. `{ r, phi }` is also accepted, but do not mix names from the two documented pairs.
Chain arithmetic without mutationbasic-arithmetic
import Complex from 'complex.js';
const z = Complex(2, 3);
const result = z.add(4).mul(0, 1).div(2);
console.log(z.toString()); // 2 + 3i
console.log(result.toString()); // -1.5 + 3iArithmetic methods return new objects. The original value remains unchanged unless you assign the result back.
Find both roots of a real quadraticsolve-quadratic
import Complex from 'complex.js';
function quadraticRoots(a, b, c) {
if (a === 0) throw new RangeError('a must be nonzero');
const discriminant = Complex(b * b - 4 * a * c).sqrt();
return [
Complex(-b).add(discriminant).div(2 * a),
Complex(-b).sub(discriminant).div(2 * a),
];
}
console.log(quadraticRoots(1, 4, 5).map(String)); // ['-2 + i', '-2 - i']Unlike `Math.sqrt`, `Complex(...).sqrt()` keeps negative discriminants inside the complex domain.
Read magnitude and phasemagnitude-phase
import Complex from 'complex.js';
const z = Complex(-3, 4);
const magnitude = z.abs();
const phaseRadians = z.arg();
const phaseDegrees = phaseRadians * 180 / Math.PI;
console.log({ magnitude, phaseDegrees }); // { magnitude: 5, phaseDegrees: ... }`arg()` uses the principal angle returned by `atan2`, in radians, so the result lies on the usual branch around the negative real axis.
Compute a conjugate and reciprocalconjugate-inverse
import Complex from 'complex.js';
const z = Complex(1, 2);
console.log(z.conjugate().toString()); // 1 - 2i
console.log(z.inverse().toString()); // 0.2 - 0.4i
console.log(z.mul(z.inverse()).round(12).toString()); // 1The reciprocal of zero is the library's complex Infinity sentinel; test `z.isZero()` first when that should be an application error.
Use roots, powers, exponentials, and logspowers-and-logs
import Complex from 'complex.js';
const root = Complex(-4).sqrt();
const squared = root.pow(2);
const roundTrip = Complex(2, 3).log().exp();
console.log(root.toString()); // 2i
console.log(squared.round(12).toString());
console.log(roundTrip.round(12).toString());Complex logarithms and non-integer powers use principal branches. Values across a branch cut can jump even when inputs are close.
Evaluate complex trigonometric functionstrigonometric-functions
import Complex from 'complex.js';
const z = Complex(1, 0.5);
const values = {
sin: z.sin(),
cos: z.cos(),
tanh: z.tanh(),
asin: z.asin(),
};
for (const [name, value] of Object.entries(values)) {
console.log(name, value.toString());
}Inverse functions follow documented principal-branch choices. In particular, `acot` uses a textbook real-axis convention with a jump on the negative real axis.
Rotate a 2D point around a centerrotate-point
import Complex from 'complex.js';
function rotate(point, center, radians) {
const turn = Complex({ abs: 1, arg: radians });
return Complex(point).sub(center).mul(turn).add(center);
}
const rotated = rotate([2, 1], [1, 1], Math.PI / 2);
console.log(rotated.round(12).toVector()); // [1, 2]This treats the real component as x and the imaginary component as y. Round only for display or tolerance checks, not after every operation.
Compare results with the built-in tolerancecompare-results
import Complex from 'complex.js';
const calculated = Complex(0.1 + 0.2, 0);
console.log(calculated.equals(0.3)); // true
function closeTo(a, b, tolerance = 1e-10) {
return Complex(a).sub(b).abs() <= tolerance;
}
console.log(closeTo(calculated, 0.3));`equals()` uses the fixed absolute tolerance `Complex.EPSILON`, which is 1e-15. Use an application-specific absolute or relative tolerance when scales vary.
Serialize components and reject non-finite resultsserialize-and-guard
import Complex from 'complex.js';
function toRecord(value) {
const z = Complex(value);
if (!z.isFinite()) throw new RangeError('finite complex value required');
return { re: z.re, im: z.im, label: z.toString() };
}
console.log(toRecord('2+3i'));`valueOf()` returns `null` when the imaginary component is nonzero. Store `re` and `im` explicitly instead of relying on numeric coercion or formatted strings.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| mathjs | npm | Choose it when complex values are one part of a larger job involving matrices, units, expressions, or symbolic work |
| @stdlib/complex-float64 | npm | Choose it for a modular scientific-computing API built around explicit double-precision complex functions |
| numbers | npm | Choose it when you also need broader numerical algorithms and can accept an older, less focused package |