# Complex number class in Lua

This is my first attemp with Lua.

I decided to create this class, because it have lots of "tweaks" and "tricks".

I want to know if I did it as best practices Lua ways.

My Lua version is 5.4.1

require "math"

Complex = {
IMAGINARY_CHAR = "i"
}

function Complex:new(r, i)
o = {
r = r,
i = i
}

setmetatable(o, self)

self.__index = self

return o
end

function Complex:__newindex(r, i)
end

function Complex:__unm()
return Complex:new(
-self.r,
-self.i
)
end

if type(other) == "number" then
return Complex:new(
self.r + other,
self.i
)
else
return Complex:new(
self.r + other.r,
self.i + other.i
)
end
end

function Complex:__sub(other)
end

function Complex:__mul(other)
if type(other) == "number" then
return Complex:new(
self.r * other,
self.i * other
)
else
return Complex:new(
self.r * other.r - self.i * other.i,
self.i * other.r + self.r * other.i
)
end
end

function Complex:__eq(other)
return
self.r == other.r and
self.i == other.i
end

function Complex:__lt(other)
-- incorrect but for sorting
if self.r == other.r then
return self.i < other.i
else
return self.r < other.r
end
end

function Complex:tostring(i)
if type(i) ~= "string" then
i = "i"
end

if self.r == 0 and self.i == 0 then
return "( 0 )"
elseif self.i == 0 then
return "( " .. self.r .. " )"
--  elseif self.r == 0 then
--      return "( " .. i .. self.i .. " )"
else
return "( " .. self.r .. " + " .. i .. self.i .. " )"
end
end

function Complex:__tostring(i)
return self:tostring(self.IMAGINARY_CHAR)
end

function Complex:abs2()
return self.r * self.r + self.i * self.i
end

function Complex:abs()
return math.sqrt(self:abs2())
end

-- ===================================================================

Complex.IMAGINARY_CHAR = "j" -- Electrical notation

x = Complex:new(0, 10)
y = Complex:new(-5, 5)

print(x)
print(y)

print(x:abs())
print(y:abs())

print(x + y)
print(x - y)

print(x + 12)
print(x - 12)

print(x == y)
print(x ~= y)

print(x ~= x)
print(x ~= x)

print(x < y)
print(x > y)

print(x <= y)
print(x >= y)


Possible improvements:

• localise functions of the math library, and sometimes, self.r and self.i. It will reduce the number of table lookups, improving the performance, and will make the expressions simpler,
• define __call metamethod for Complex. It will allow you to replace Complex:new(x, y) with Complex(x, y),
• the 'constructor' should correctly handle cases when it is called with one parameter (Complex(5) = 5 + 0i). It can be achieved with a 'nullsafe operator' emulated as local a = user_input or value_if_null,
• in can also be mage more concise, using the fact that setmetatable returns the table,
• __newindex metamethod is not needed here, unless you want to set the absolute value or phase with code like c.abs = 1 or code.arg = pi. In addition, the arguments to __newindex are the table, the absent key and value set to it,
• you can emulate the ternary operator in Lua with and and or, which allows to write more concise code: local a = condition and on_success or on_failure,
• your arithmetic metamethods should handle the cases when the first argument (self) is a number,
• you can define conjugate method and __div metamethod for division,
• to square a table element, I'd rather recommend using ^ rather than multiplying by self. It will save table lookups,
• you can define methods for polar coordinates and __pow metamethod for Euler's and de Moivre's formulae,
• the __lt metamethod can be simplified using boolean operations,
• Complex:tostring() can be simplified using 'ternary' operators, also handling of negative imaginary parts can be improved,
• whether to put the i before or after the imaginary part and whether to surround a complex number with parentheses can be customisable,
• the expression tests can be automated somewhat with load() function.

The improved code:

local math = require 'math'
local pi, sin, cos, atan2 = math.pi, math.sin, math.cos, math.atan2
local sqrt, exp, ln = math.sqrt, math.exp, math.log
local floor, abs = math.floor, math.abs

Complex = {
IMAGINARY_CHAR = 'i'
}

function Complex:new(r, i)
self.__index = self
return setmetatable({r = r, i = i or 0}, self)
end

setmetatable(Complex, {
__call = function (tbl, r, i)
return Complex:new(r, i)
end}
)

function Complex:__unm()
return Complex(-self.r, -self.i)
end

return type(self) == 'number'   and Complex(self + other.r, other.i)
or type(other) == 'number'  and Complex(self.r + other, self.i)
or Complex(self.r + other.r, self.i + other.i)
end

function Complex:__sub(other)
return self + (-other);
end

function Complex:__mul(other)
return type(self) == 'number'   and Complex(self * other.r, self * other.i)
or type(other) == 'number'  and Complex(self.r * other, self.i * other)
or Complex(self.r * other.r - self.i * other.i, self.i * other.r + self.r * other.i)
end

function Complex:conjugate()
return Complex(self.r, -self.i)
end

function Complex:__div(denominator)
-- https://www.mesacc.edu/~scotz47781/mat120/notes/complex/dividing/dividing_complex.html
local conjugate = type(denominator) == 'number' and Complex(denominator) or denominator:conjugate()
local new_numerator, new_denominator = self * conjugate, denominator * conjugate
-- new_denominator is real.
return Complex(new_numerator.r / new_denominator.r, new_numerator.i / new_denominator.r)
end

function Complex:abs2()
return self.r ^ 2 + self.i ^ 2
end

function Complex:abs()
return sqrt(self:abs2())
end

function Complex:polar(abs, arg)
return Complex(abs * cos(arg), abs * sin(arg))
end

function Complex:arg()
return atan2(self.i, self.r)
end

function Complex:exp()
local abs = exp(self.r)
return Complex(abs * cos(self.i), abs * sin(self.i))
end

function Complex:__pow(power)
-- Euler:
local x = type(self) == 'number' and self or self.i == 0 and self.r > 0 and self.r or nil
if x then
return (power * ln(x)):exp()
else
-- de Moivre:
local n = type(power) == 'number' and power or power.i == 0 and power.r or nil
if n and floor(n) == n then
local abs, arg = self:abs(), self:arg()
return Complex:polar(abs ^ n, n * arg)
end
end
end

function Complex:__eq(other)
return
self.r == other.r and
self.i == other.i
end

function Complex:__lt(other)
-- incorrect but for sorting:
return self.r < other.r or self.r == other.r and self.i < other.i
end

function Complex:tostring(i, prefix, parentheses)
local im = type(i) == 'string' and i or 'i'
local r, i = self.r, self.i

local str = ((r ~= 0 or i == 0) and r or '')
.. (r ~= 0 and i > 0 and ' + ' or '')
.. (i < 0 and ' - ' or '')
.. (i ~= 0 and (prefix and im .. abs(i) or abs(i) .. im) or '')
if parentheses then
str = '(' .. str .. ')'
end
return str
end

function Complex:__tostring(i)
return self:tostring(self.IMAGINARY_CHAR, self.PREFIX, self.PARENTHESES)
end

-- ===================================================================

Complex.IMAGINARY_CHAR = 'j' -- electrical notation.
Complex.PREFIX = true
Complex.PARENTHESES = true

local context = {
Complex = Complex,
x = Complex(0, 10),
y = Complex(-5, 5),
z = Complex(5),
pi = pi
}

local cases = {
'x', 'y', 'z', 'x:abs()',
'x + y', 'x - y', 'x + 12', 'x - 12', '12 + x', '12 - x',
'x * y', 'y * 12', '2 * x',
'y:conjugate()', 'x / y', 'y / y', 'x / 5', '5 / x', '5 / x * x',
'y:abs()', 'y:arg() / pi * 180', 'Complex:polar(2, pi / 4)', 'y:exp()', 'x ^ 2', '2 ^ x',
'x == y', 'x ~= y', 'x < y', 'x > y', 'x <= y', 'x >= y'
}
for _, expr in ipairs(cases) do
print(expr, assert(load('return ' .. expr, nil, 't', context))())
end
$$$$

• Thanks a lot. Did not know ternary operations returns underline types. Do you think _sub shall be implemented in this way or just copy paste _add - second will be faster? Isn't ^ slow operation?
– Nick
Dec 4, 2020 at 9:39
• I think that __sub will be faster as return Complex(self.r - subtrahend.r, self.i - subtrahend.i) than return self + (-subtrahend)`: one function call fewer. I tested squaring on my PC: under Lua 5.1 and 5.2 squaring by multiplication was slightly faster. Under Lua 5.3, power was about 40% faster. Haven't got Lua 5.4. Dec 5, 2020 at 7:58