6
\$\begingroup\$

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
function Complex:__add(other)
 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)
 return self:__add(- 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)
asked Dec 3, 2020 at 12:12
\$\endgroup\$
1

1 Answer 1

7
\$\begingroup\$

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:

complex.lua:

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 (type (r) == 'table' and r or {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
function complex:__add (other)
 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 complex (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 r_str = (r ~= 0 or i == 0) and r or ''
 local i_str = i ~= 0 and (prefix and im .. abs (i) or (abs (i) == 1 and '' or abs (i)) .. im) or ''
 local space = (r ~= 0 and i ~= 0) and ' ' or ''
 local sign = r ~= 0 and i > 0 and '+' or i < 0 and '-' or ''
 
 local str = r_str .. space .. sign .. space .. i_str
 if parentheses then
 str = '(' .. str .. ')'
 end
 return str
end
function complex:__tostring ()
 return self:tostring (self.IMAGINARY_CHAR, self.PREFIX, self.PARENTHESES)
end
return complex

test.lua:

local load = _VERSION == 'Lua 5.1' and function (chunk, _, __, context)
 for key, value in pairs (context) do
 _G [key] = value
 end
 return loadstring (chunk)
end or load
local complex = require 'complex'
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 = math.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 ^ y', '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

For further improvements, see my repository.

answered Dec 4, 2020 at 6:32
\$\endgroup\$
2
  • \$\begingroup\$ 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? \$\endgroup\$ Commented Dec 4, 2020 at 9:39
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ Commented Dec 5, 2020 at 7:58

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.