Complex numbers in pure Luau

i was bored, so i decided to add complex numbers to luau

This implementation may not be 100% correct as there’s a few functions in this library I vibecoded because I don’t really know them myself, but the basic operations (+, -, * and /) have been tested (as I do know how to test for these)

Complex Source Code
--!native
--!optimize 2

-- Complex Numbers
-- metatablecat 2026
-- This implementation may not be 100% complete, but feel free to contribute!

-- REMARK: Right now, real numbers will remain in complex notation for performance reasons
-- you can return them to reals by simply indexing the part of the complex number you want

export type Complex = typeof(setmetatable({}::{
	Real: number,
	Imaginary: number
}, {}::{
	__add: (self: Complex, other: number | Complex) -> Complex,
	__sub: (self: Complex, other: number | Complex) -> Complex,
	__mul: (self: Complex, other: number | Complex) -> Complex,
	__div: (self: Complex, other: number | Complex) -> Complex,

	__tostring: (self: Complex) -> string,
}))

-- some methods are useful in the metamethod
local COMPLEX_NEW
local function COMPLEX_TEST(complex: Complex, allowReals: boolean): string
	if type(complex) == "number" and allowReals then return "real" end
	if type(complex) ~= "table" then
		error("Attempt to call a complex number method with a non-complex value", 2)
	end

	return "complex"
end

local function COMPLEX_MUL(a: number, bi: number, c: number, di: number): (number, number)
	-- lower level method for just returning the raw numbers
	-- without expensively rehashing the result
	-- (a + bi)(c + di) = ac - (bd)i, (ad + cb)i
	return a*c - bi*di, a*di + bi*c
end

local COMPLEX_MT = {
	__add = function(lhs: Complex, rhs: number | Complex): Complex
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)
		
		if rhsType == "real" then
			return COMPLEX_NEW(lhs.Real + rhs, lhs.Imaginary)
		end
		
		return COMPLEX_NEW(lhs.Real + rhs.Real, lhs.Imaginary + rhs.Imaginary)
	end,
	
	__sub = function(lhs: Complex, rhs: number | Complex): Complex
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)
		
		if rhsType == "real" then
			return COMPLEX_NEW(lhs.Real - rhs, lhs.Imaginary)
		end
		
		return COMPLEX_NEW(lhs.Real - rhs.Real, lhs.Imaginary - rhs.Imaginary)
	end,
	
	__mul = function(lhs: Complex, rhs: number | Complex): Complex
		-- TIP: This is just the distributive property
		-- z(a + bi) or (a + bi)(c + di) for complex numbers
		
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)
		
		if rhsType == "real" then
			return COMPLEX_NEW(lhs.Real * rhs, lhs.Imaginary * rhs)
		end

		return COMPLEX_NEW(COMPLEX_MUL(lhs.Real, lhs.Imaginary, rhs.Real, rhs.Imaginary))
	end,
	
	__div = function(lhs: Complex, rhs: number | Complex): Complex
		-- For this, if its not a number, we first need to figure out the conjugate of the RHS
		-- then multiply both the numerator and denominator by the conjugate,
		-- the denominator will then ALWAYS be real, so we just divide
		
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)
		
		local nz, ni = lhs.Real, lhs.Imaginary
		local dz, di
		if rhsType == "real" then
			dz = rhs
		else
			nz, ni = COMPLEX_MUL(nz, ni, rhs.Real, -rhs.Imaginary)
			-- shortuct: a conjugate is an expression that gives us a difference
			-- of two squares, so we can just use a^2 + b^2 for dz
			dz = rhs.Real^2 + rhs.Imaginary^2
		end
		
		return COMPLEX_NEW(nz / dz, ni / dz)
	end,
	
	__tostring = function(c: Complex): string
		if c.Real == 0 then
			if c.Imaginary == 0 then
				return "(0 + 0i)"
			else
				return `{c.Imaginary}i`
			end
		else
			if c.Imaginary == 0 then
				return tostring(c.Real)
			end
			
			local complexSign = if math.sign(c.Imaginary) == -1 then "-" else "+"
			local abs = math.abs(c.Imaginary)
			
			if abs == 1 then
				return `({c.Real} {complexSign} i)`
			end
			
			return `({c.Real} {complexSign} {abs}i)`
		end
	end,
}

local memoized: {[string]: Complex} = setmetatable({}, {__mode = "v"})
local function complex_hash(a: number, bi: number): string
	return `{a} + {bi}i`
end

function COMPLEX_NEW(real: number?, imaginary: number?): Complex
	local hash = complex_hash(real or 0, imaginary or 0)
	local comp = memoized[hash]
	
	if not comp then	
		comp = setmetatable({
			Real = real or 0,
			Imaginary = imaginary or 0,
		}, COMPLEX_MT)
		
		memoized[hash] = comp
	end
	
	return comp
end

local function COMPLEX_conjugate(comp: Complex): Complex
	COMPLEX_TEST(comp, false)
	return COMPLEX_NEW(comp.Real, -comp.Imaginary)
end

local function COMPLEX_normalize(comp: Complex): Complex
	COMPLEX_TEST(comp, false)
	return comp.Real ^ 2 + comp.Imaginary ^ 2
end

local function COMPLEX_abs(comp: Complex): number
	COMPLEX_TEST(comp, false)
	return math.sqrt(comp.Real^2 + comp.Imaginary^2)
end

local function COMPLEX_argument(comp: Complex): number
	COMPLEX_TEST(comp, false)
	return math.atan2(comp.Imaginary, comp.Real)
end

local function COMPLEX_polar(comp: Complex): (number, number)
	-- TODO: should this return a Vector2 if we're in the Roblox namespace?
	return COMPLEX_abs(comp), COMPLEX_argument(comp)
end

local function COMPLEX_unpack(comp: Complex): (number, number)
	return comp.Real, comp.Imaginary
end

local function COMPLEX_isReal(comp: Complex): boolean
	return comp.Imaginary == 0
end

local function COMPLEX_sqrt(comp: Complex): Complex
	local r, theta = COMPLEX_polar(comp)
	local sr = math.sqrt(r)
	
	return COMPLEX_NEW(
		sr * math.cos(theta / 2),
		sr * math.sin(theta / 2)
	)
end

local function COMPLEX_exp(comp: Complex): Complex
	local er = math.exp(comp.Real)

	return COMPLEX_NEW(
		er * math.cos(comp.Imaginary),
		er * math.sin(comp.Imaginary)
	)
end

local function COMPLEX_log(comp: Complex): Complex
	return COMPLEX_NEW(
		math.log(COMPLEX_abs(comp)),
		COMPLEX_argument(comp)
	)
end

return {
	new = COMPLEX_NEW,

	zero = COMPLEX_NEW(0, 0),
	i = COMPLEX_NEW(0, 1),
	one = COMPLEX_NEW(1, 0),
	
	abs = COMPLEX_abs,
	argument = COMPLEX_argument,
	conjugate = COMPLEX_conjugate,
	normalize = COMPLEX_normalize,
	
	polar = COMPLEX_polar,
	unpack = COMPLEX_unpack,
	isReal = COMPLEX_isReal,
	
	exp = COMPLEX_exp,
	log = COMPLEX_log,
	sqrt = COMPLEX_sqrt	
}

This is currently missing support for the ^ operator currently, but I’m sure some of you math nerds will tell me how to implement it lol.

6 Likes

Having a little fun, the performance on this is absolutely horrendous, well because its my first go at this, but hey, here’s a cool fractal

Code to generate
local AssetService = game:GetService("AssetService")
local Complex = require(script.Parent.Complex)

local function domain(min: number, max: number, samples): {number}
	local output = table.create(samples)
	
	for i = 1, samples do
		table.insert(output, min + (max - min) * (i - 1) / (samples - 1))
	end
	
	return output
end

local image = AssetService:CreateEditableImage {Size = Vector2.new(500, 500)}
local bound = 2
local max_iter = 50

local y_domain = domain(-2, 2, 500)
local x_domain = domain(-2, 2, 500)

local function mandelbrot(z, c)
	-- complex hasn't implemented ^, so we'll assume p is always 2 for now
	return z*z + c
end

local c = os.clock()
local iter = table.create(500)
for _, y in y_domain do
	row = table.create(500)
	for _, x in x_domain do
		local z = Complex.zero
		local c = Complex.new(x, y)
		
		local didInfConverge = false
		for iter = 1, max_iter do
			if Complex.abs(z) > bound then
				table.insert(row, iter - 1)
				didInfConverge = true
				break
			end
			
			z = mandelbrot(z, c)
			if Complex.isNan(z) then
				z = c
			end
		end
		
		if not didInfConverge then
			table.insert(row, 0)
		end
	end
	
	table.insert(iter, row)
end

print("generate mandelbrot:", os.clock() - c)

c = os.clock()
-- plot the iteration array
local imageBuffer = buffer.create(500^2 * 4)
local function packRGBAatXY(
	image: buffer,
	x: number,
	y: number,
	sx: number,
	sy: number,
	rgb: Color3,
	alpha: number
)
	-- stored as RGBA in little endian (weird?)
	local pixelIndex = 4 * (y * sx + x)
	
	buffer.writeu8(image, pixelIndex + 0, rgb.R * 255)
	buffer.writeu8(image, pixelIndex + 1, rgb.G * 255)
	buffer.writeu8(image, pixelIndex + 2, rgb.B * 255)
	buffer.writeu8(image, pixelIndex + 3, alpha * 255)
end

for yPixel, row in iter do
	for xPixel, iterations in row do
		local alpha = math.clamp(iterations / max_iter, 0, 1)
		local color = Color3.fromHSV(2/3, 1, alpha)
		packRGBAatXY(imageBuffer, xPixel-1, yPixel-1, 500, 500, color, 1-alpha)
	end
end

image:WritePixelsBuffer(Vector2.new(), Vector2.new(500, 500), imageBuffer)
script.Parent.ImageLabel.ImageContent = Content.fromObject(image)
print("render image:", os.clock() - c)
5 Likes

It is not luau it’s lua. They keep changing every single wording and people just follow for some reason. For example, friends to connections, groups to communities, games to experiences and now lua to luau. There is no different between lua and “luau” just the wording.

Literal animate script BY ROBLOX:

Is this ragebait? https://luau.org

8 Likes


2026, theyre changing everything

1 Like

sure, this is absolutely luau, not lua.

  • --!native
    luau compiler directive. lua does not have compiler directives at all.

  • --!optimize 2
    luau optimization directive. nonexistent in lua.

  • export type Complex = ...
    luau exported type alias syntax. lua has no type system. zero support for type, let alone export type.

  • typeof(setmetatable(...)) used in a type context
    luau type operator. lua’s typeof is not a language-level type constructor in this sense.

  • {} :: { ... }
    luau type ascription operator. :: is invalid syntax in lua.

  • structural table type syntax like
    { Real: number, Imaginary: number }
    luau type grammar. lua does not support field type annotations.

  • function type syntax inside the metatable type:
    __add: (self: Complex, other: number | Complex) -> Complex
    every piece of this is luau:

    • typed parameters
    • union types (number | Complex)
    • arrow return types (-> Complex)
    • function types as values in a table type
  • union types such as number | Complex
    luau feature. lua does not have union types.

  • optional types like number?
    luau shorthand for number | nil. not valid lua syntax.

  • typed function parameters:
    function COMPLEX_TEST(complex: Complex, allowReals: boolean)
    the : Complex and : boolean are luau type annotations. lua rejects this.

  • typed function return annotations:
    ): string
    ): (number, number)
    also luau-only.

  • typed local variable:
    local memoized: {[string]: Complex}
    luau type annotation on a local. lua does not allow this.

  • map/index type syntax {[string]: Complex}
    luau typed table syntax. not lua.

  • backtick string interpolation:
    `{c.Imaginary}i`
    `{a} + {bi}i`
    luau string interpolation. lua only supports quoted strings with concatenation.

  • expression-form if:
    local complexSign = if math.sign(...) == -1 then "-" else "+"
    luau if-expression. lua only supports statement-form if ... then ... end.

  • math.sign
    provided by roblox’s luau runtime. not part of standard lua 5.1 math library.

  • math.atan2
    also part of the roblox luau environment, not stock lua 5.1.

every single one of those would cause a syntax error or undefined symbol in plain lua.

this is luau: extended syntax, static typing, compiler directives, interpolation, expression-level conditionals, roblox runtime math extensions.

lua cannot even lex half of this file.

5 Likes

ad hominem
The fact of it being ai doesnt change the fact that it is correct.

4 Likes

“”“complex numbers”“” are a scam like what do you mean “imaginary number line” BRO THAT IS JUST THE Y AXIS ON A 2D CARTESIAN COORDINATE PLANE.
you dont just add an “imaginary” “number” because your math doesn’t add up your imagination does not belong in real mathematics.

Yes complex numbers do not work in real math as real math works in the set of real numbers R :joy:

But the difference between a complex number and a vec2 is that a complex number is literally just 2 numbers multiplied together, so you can do normal math on it. Whereas vec2 math is it’s own thing

2 Likes

is this ragebait :sob:

luau is a language derived from lua 5.1, it was developed at first for roblox

its re-written from the ground up and is not only more optimised but has more features like its type checker and annotations and native codegen support

its backwards compatible with lua 5.1 btw

(just in case u r serious)

3 Likes

Heads up, I’m rewriting this library to use buffers and represent them in exponential form instead, that is the form image. It makes exponents cleaner and is quicker to calculate basic operations (since solving sines and cosines is quicker than finding the length/arg).

I may also choose to store both forms within one type but only store one pair depending on how you create it, then use a bitflag to correctly determine if you’ve got cartesian or polar contained in the number

1 Like
--!optimize 2
--!native

type Complex = buffer

const function UNSAFE_COMPLEX_components(complex: buffer): (number, number)
	return buffer.readf32(complex, 0), buffer.readf32(complex, 4)
end

const function UNSAFE_COMPLEX_toCartesian(complex: buffer): (number, number)
	local modulus, argument = UNSAFE_COMPLEX_components(complex)
	return modulus * math.cos(argument), modulus * math.sin(argument)
end

const REAL = 0
const COMPLEX = 1
const function getNumberType(nType: number | Complex, allowReal: boolean): number
	local t = type(nType)
	if t == "number" and allowReal then return REAL end
	if t == "buffer" and buffer.len(nType) == 8 then return COMPLEX end
	
	error(`Unknown type {t}, expected complex`)
end

const function COMPLEX_new(modulus: number, argument: number): buffer
	local complex = buffer.create(8)
	buffer.writef32(complex, 0, modulus)
	buffer.writef32(complex, 4, argument)
	return complex
end

const function COMPLEX_fromCartesian(re: number?, im: number?): buffer
	re = re or 0
	im = im or 0
	
	return COMPLEX_new((re^2 + im^2)^0.5, math.atan2(im, re))
end

const function COMPLEX_mul(lhs: number | Complex, rhs: number | Complex): buffer
	-- multiply modulus, add arguments
	-- unless using scalar
	
	local c1t = getNumberType(lhs, true)
	local c2t = getNumberType(rhs, true)
	
	if c1t == REAL then
		if c2t == REAL then return lhs * rhs end
		local mod, arg = UNSAFE_COMPLEX_components(rhs)
		
		return COMPLEX_new(mod * lhs, arg)
	else
		local mod1, arg1 = UNSAFE_COMPLEX_components(lhs)
		if c2t == REAL then return COMPLEX_new(mod1 * rhs, arg1) end
		
		local mod2, arg2 = UNSAFE_COMPLEX_components(rhs)
		return COMPLEX_new(mod1 * mod2, arg1 + arg2)
	end
end

const function COMPLEX_div(lhs: number | Complex, rhs: number | Complex): buffer
	local c1t = getNumberType(lhs, true)
	local c2t = getNumberType(rhs, true)

	if c1t == REAL then
		if c2t == REAL then return lhs * rhs end
		local mod, arg = UNSAFE_COMPLEX_components(rhs)

		return COMPLEX_new(lhs / mod, arg)
	else
		local mod1, arg1 = UNSAFE_COMPLEX_components(lhs)
		if c2t == REAL then return COMPLEX_new(mod1 / rhs, arg1) end

		local mod2, arg2 = UNSAFE_COMPLEX_components(rhs)
		return COMPLEX_new(mod1 / mod2, arg1 - arg2)
	end
end

const function COMPLEX_add(lhs: number | Complex, rhs: number | Complex): Complex
	local c1t = getNumberType(lhs, true)
	local c2t = getNumberType(rhs, true)
	
	if c1t == REAL then
		if c2t == REAL then return lhs + rhs end
		
		local re, im = UNSAFE_COMPLEX_toCartesian(rhs)
		return COMPLEX_fromCartesian(re + lhs, im)
	else
		local re1, im1 = UNSAFE_COMPLEX_toCartesian(lhs)
		if c2t == REAL then return COMPLEX_fromCartesian(re1 + rhs, im1) end
		
		local re2, im2 = UNSAFE_COMPLEX_toCartesian(rhs)
		return COMPLEX_fromCartesian(re1 + re2, im1 + im2)
	end
end

const function COMPLEX_sub(lhs: number | Complex, rhs: number | Complex): Complex
	local c1t = getNumberType(lhs, true)
	local c2t = getNumberType(rhs, true)

	if c1t == REAL then
		if c2t == REAL then return lhs - rhs end

		local re, im = UNSAFE_COMPLEX_toCartesian(rhs)
		return COMPLEX_fromCartesian(lhs - re, im)
	else
		local re1, im1 = UNSAFE_COMPLEX_toCartesian(lhs)
		if c2t == REAL then return COMPLEX_fromCartesian(re1 - rhs, im1) end

		local re2, im2 = UNSAFE_COMPLEX_toCartesian(rhs)
		return COMPLEX_fromCartesian(re1 - re2, im1 - im2)
	end
end

const e = math.exp(1)

const function COMPLEX_exp(lhs: number | Complex, rhs: number | Complex): Complex
	local c1t = getNumberType(lhs, true)
	local c2t = getNumberType(rhs, true)
	
	-- De Moivre's theorem: z^n = r^n(cos n0 + isin n0) when z = r(cos 0 + isin 0)
	if c1t == REAL then
		if c2t == REAL then return lhs ^ rhs end
		
		local re, im = UNSAFE_COMPLEX_toCartesian(rhs)
		
		-- r^a( cos(b ln r) + i sin(b ln r) )
		-- this formula is defined for r^z where z is a cartesian complex number
		-- the resulting number is a polar number

		return COMPLEX_new(lhs ^ re, im * math.log(lhs))
	else
		local re1, im1 = UNSAFE_COMPLEX_components(lhs)
		if c2t == REAL then return COMPLEX_new(re1 ^ rhs, im1 * rhs) end
		
		local re2, im2 = UNSAFE_COMPLEX_components(rhs)
		error("Complex to a complex is expensive and is currently unsupported")
	end
end

type OutputForm = "cartesian"|"polar"|"exp"
const PI_UNICODE = "\u{03C0}"

return {
	components = function(complex: buffer): (number, number)
		getNumberType(complex, false)
		return UNSAFE_COMPLEX_components(complex)
	end,
	
	cartesian_components = function(complex: buffer): (number, number)
		getNumberType(complex, false)
		return UNSAFE_COMPLEX_toCartesian(complex)
	end,
	
	new = COMPLEX_new,
	fromCartesian = COMPLEX_fromCartesian,
	
	add = COMPLEX_add,
	sub = COMPLEX_sub,
	mul = COMPLEX_mul,
	div = COMPLEX_div,
	exp = COMPLEX_exp,
	
	i = COMPLEX_fromCartesian(0, 1),
	re = COMPLEX_fromCartesian(1, 0),
	
	conjugate = function(complex: buffer): Complex
		-- arctan2 is defined such that this is a lot simpler to calculate
		local mod, arg = UNSAFE_COMPLEX_components(complex)
		return COMPLEX_new(mod, -arg)
	end,
	
	abs = function(complex: buffer | number): number
		local c1t = getNumberType(complex, true)
		if c1t == REAL then
			return math.abs(complex)
		end
		
		return buffer.readf32(complex, 0)
	end,
	
	arg = function(complex: buffer): number
		local c1t = getNumberType(complex, false)
		return buffer.readf32(complex, 4)
	end,
	
	tostring = function(complex: buffer, form: OutputForm?): string
		if buffer.readf32(complex, 0) == 0 then return "0" end  -- argument is undefined when mod = 0
		
		if not form or form == "cartesian" then
			local re, im = UNSAFE_COMPLEX_toCartesian(complex)
			
			if im == 0 then
				return tostring(re)
			end
			
			local sign = if math.sign(im) == -1 then "-" else "+"
			if math.abs(im) == 1 then
				if re == 0 then
					return `{sign}i`
				end
				return `{re} {sign} i`
			end
			
			if re == 0 then return `{sign}{im}i` end
			return `{re} {sign} {math.abs(im)}i`
		else
			local mod, arg = UNSAFE_COMPLEX_components(complex)
			if mod == 0 then return "0" end 
			
			if form == "polar" then
				if mod == 1 then
					return `cos({arg}) + i sin({arg})`
				end
				return `{mod}(cos{arg}) + i sin({arg})`
			end
			
			if mod == 1 then
				return `e^{arg}i{PI_UNICODE}`
			else
				return `{mod}(e^{arg}i{PI_UNICODE})`
			end
		end
	end,
}

Haven’t fully tested it because Vinegar is a broken piece of software that needs to be fixed.

Hey! Apologies for the bump :sweat_smile:

This is a great module! However while reading it over, I noticed there were a few type warnings and a missing __pow metamethod. Bonus side, you already have all the methods needed!

COMPLEX_exp and COMPLEX_log

__pow = function(lhs: Complex, rhs: number | Complex): Complex
	COMPLEX_TEST(lhs, false)
	local rhsType = COMPLEX_TEST(rhs, true)
	local rhs = if rhsType == "real" then COMPLEX_NEW(rhs::number) else rhs :: Complex
	
	return COMPLEX_exp(rhs * COMPLEX_log(lhs))
end
I based this on a paper I've used in the past

I also verified it with Desmos just to be sure, and it was spot on!

(0.12900959407446697 + 0.03392409290517014i)

(-11.000000000000004 - 1.9999999999999973i)

(123.5097982436449 + 19.24395327925415i)

Here’s the full source with the patches, hope you enjoy :grin:

Edited Source Code
--!native
--!optimize 2

-- Complex Numbers
-- metatablecat 2026
-- This implementation may not be 100% complete, but feel free to contribute!

-- REMARK: Right now, real numbers will remain in complex notation for performance reasons
-- you can return them to reals by simply indexing the part of the complex number you want

export type Complex = typeof(setmetatable({}::{
	Real: number,
	Imaginary: number
}, {}::{
	__add: (self: Complex, other: number | Complex) -> Complex,
	__sub: (self: Complex, other: number | Complex) -> Complex,
	__mul: (self: Complex, other: number | Complex) -> Complex,
	__div: (self: Complex, other: number | Complex) -> Complex,
	__pow: (self: Complex, other: number | Complex) -> Complex,

	__tostring: (self: Complex) -> string,
}))

-- some methods are useful in the metamethod
local COMPLEX_NEW
local function COMPLEX_TEST(complex: number | Complex, allowReals: boolean): string
	if type(complex) == "number" and allowReals then return "real" end
	if type(complex) ~= "table" then
		error("Attempt to call a complex number method with a non-complex value", 2)
	end

	return "complex"
end

local function COMPLEX_MUL(a: number, bi: number, c: number, di: number): (number, number)
	-- lower level method for just returning the raw numbers
	-- without expensively rehashing the result
	-- (a + bi)(c + di) = ac - (bd)i, (ad + cb)i
	return a*c - bi*di, a*di + bi*c
end

local function COMPLEX_abs(comp: Complex): number
	COMPLEX_TEST(comp, false)
	return math.sqrt(comp.Real^2 + comp.Imaginary^2)
end

local function COMPLEX_argument(comp: Complex): number
	COMPLEX_TEST(comp, false)
	return math.atan2(comp.Imaginary, comp.Real)
end

local function COMPLEX_exp(comp: Complex): Complex
	local er = math.exp(comp.Real)

	return COMPLEX_NEW(
		er * math.cos(comp.Imaginary),
		er * math.sin(comp.Imaginary)
	)
end

local function COMPLEX_log(comp: Complex): Complex
	return COMPLEX_NEW(
		math.log(COMPLEX_abs(comp)),
		COMPLEX_argument(comp)
	)
end

local COMPLEX_MT = {
	__add = function(lhs: Complex, rhs: number | Complex): Complex
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)

		if rhsType == "real" then
			return COMPLEX_NEW(lhs.Real + rhs :: number, lhs.Imaginary)
		end

		return COMPLEX_NEW(lhs.Real + (rhs :: Complex).Real, lhs.Imaginary + (rhs :: Complex).Imaginary)
	end,

	__sub = function(lhs: Complex, rhs: number | Complex): Complex
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)

		if rhsType == "real" then
			return COMPLEX_NEW(lhs.Real - rhs :: number, lhs.Imaginary)
		end

		return COMPLEX_NEW(lhs.Real - (rhs :: Complex).Real, lhs.Imaginary - (rhs :: Complex).Imaginary)
	end,

	__mul = function(lhs: Complex, rhs: number | Complex): Complex
		-- TIP: This is just the distributive property
		-- z(a + bi) or (a + bi)(c + di) for complex numbers

		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)

		if rhsType == "real" then
			return COMPLEX_NEW(lhs.Real * rhs :: number, lhs.Imaginary * rhs :: number)
		end

		return COMPLEX_NEW(COMPLEX_MUL(lhs.Real, lhs.Imaginary, (rhs :: Complex).Real, (rhs :: Complex).Imaginary))
	end,

	__div = function(lhs: Complex, rhs: number | Complex): Complex
		-- For this, if its not a number, we first need to figure out the conjugate of the RHS
		-- then multiply both the numerator and denominator by the conjugate,
		-- the denominator will then ALWAYS be real, so we just divide

		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)

		local nz, ni = lhs.Real, lhs.Imaginary
		local dz, di
		if rhsType == "real" then
			dz = rhs :: number
		else
			local rhs = rhs :: Complex
			nz, ni = COMPLEX_MUL(nz, ni, rhs.Real, -rhs.Imaginary)
			-- shortuct: a conjugate is an expression that gives us a difference
			-- of two squares, so we can just use a^2 + b^2 for dz
			dz = rhs.Real^2 + rhs.Imaginary^2
		end

		return COMPLEX_NEW(nz / dz, ni / dz)
	end,
	
	__pow = function(lhs: Complex, rhs: number | Complex): Complex
		COMPLEX_TEST(lhs, false)
		local rhsType = COMPLEX_TEST(rhs, true)
		local rhs = if rhsType == "real" then COMPLEX_NEW(rhs::number) else rhs :: Complex
		
		return COMPLEX_exp(rhs * COMPLEX_log(lhs))
	end,

	__tostring = function(c: Complex): string
		if c.Real == 0 then
			if c.Imaginary == 0 then
				return "(0 + 0i)"
			else
				return `{c.Imaginary}i`
			end
		else
			if c.Imaginary == 0 then
				return tostring(c.Real)
			end

			local complexSign = if math.sign(c.Imaginary) == -1 then "-" else "+"
			local abs = math.abs(c.Imaginary)

			if abs == 1 then
				return `({c.Real} {complexSign} i)`
			end

			return `({c.Real} {complexSign} {abs}i)`
		end
	end,
}

local memoized: {[string]: Complex} = setmetatable({} :: {[string]: Complex}, {__mode = "v"})
local function complex_hash(a: number, bi: number): string
	return `{a} + {bi}i`
end

function COMPLEX_NEW(real: number?, imaginary: number?): Complex
	local hash = complex_hash(real or 0, imaginary or 0)
	local comp = memoized[hash]

	if not comp then	
		comp = setmetatable({
			Real = real or 0,
			Imaginary = imaginary or 0,
		}, COMPLEX_MT)

		memoized[hash] = comp
	end

	return comp
end

local function COMPLEX_conjugate(comp: Complex): Complex
	COMPLEX_TEST(comp, false)
	return COMPLEX_NEW(comp.Real, -comp.Imaginary)
end

local function COMPLEX_normalize(comp: Complex): Complex
	COMPLEX_TEST(comp, false)
	return COMPLEX_NEW(comp.Real ^ 2 + comp.Imaginary ^ 2)
end



local function COMPLEX_polar(comp: Complex): (number, number)
	-- TODO: should this return a Vector2 if we're in the Roblox namespace?
	return COMPLEX_abs(comp), COMPLEX_argument(comp)
end

local function COMPLEX_unpack(comp: Complex): (number, number)
	return comp.Real, comp.Imaginary
end

local function COMPLEX_isReal(comp: Complex): boolean
	return comp.Imaginary == 0
end

local function COMPLEX_sqrt(comp: Complex): Complex
	local r, theta = COMPLEX_polar(comp)
	local sr = math.sqrt(r)

	return COMPLEX_NEW(
		sr * math.cos(theta / 2),
		sr * math.sin(theta / 2)
	)
end

return {
	new = COMPLEX_NEW,

	zero = COMPLEX_NEW(0, 0),
	i = COMPLEX_NEW(0, 1),
	one = COMPLEX_NEW(1, 0),

	abs = COMPLEX_abs,
	argument = COMPLEX_argument,
	conjugate = COMPLEX_conjugate,
	normalize = COMPLEX_normalize,

	polar = COMPLEX_polar,
	unpack = COMPLEX_unpack,
	isReal = COMPLEX_isReal,

	exp = COMPLEX_exp,
	log = COMPLEX_log,
	sqrt = COMPLEX_sqrt	
}
1 Like

If the concept of complex number in mathematics is scam then AC power system wouldn’t be very efficient. Wireless technology wouldn’t be advanced as today. Our world would progress much slower.

And also tau is 2 times the pi, not divide by 2. But most of time we just use 2*pi

You don’t really need complex numbers to work with AC, it’s just a bit of a pain.