\$\begingroup\$
\$\endgroup\$
1
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)
-
\$\begingroup\$ For a C library for Lua that uses native C complex numbers, see web.tecgraf.puc-rio.br/~lhf/ftp/lua/#lcomplex \$\endgroup\$lhf– lhf2022年11月12日 17:46:34 +00:00Commented Nov 12, 2022 at 17:46
1 Answer 1
\$\begingroup\$
\$\endgroup\$
2
Possible improvements:
- localise functions of the
math
library, and sometimes,self.r
andself.i
. It will reduce the number of table lookups, improving the performance, and will make the expressions simpler, - define
__call
metamethod forComplex
. It will allow you to replaceComplex:new(x, y)
withComplex(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 aslocal 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 likec.abs = 1
orcode.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
andor
, 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
-
\$\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\$Nick– Nick2020年12月04日 09:39:47 +00:00Commented Dec 4, 2020 at 9:39 -
1\$\begingroup\$ I think that
__sub
will be faster asreturn Complex(self.r - subtrahend.r, self.i - subtrahend.i)
thanreturn 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\$Alexander Mashin– Alexander Mashin2020年12月05日 07:58:58 +00:00Commented Dec 5, 2020 at 7:58
lang-lua