Números complexos em Lua
Lua é uma poderosa linguagem de programação procedural, orientada a objetos, de tipagem dinâmica, baseada em tabelas associativas e semântica extensível, projetada e implementada no Tecgraf, Grupo de Computação Gráfica da PUC-Rio, em 1993.
Foi projetada para ser uma linguagem de extensão, para que fosse possível que os usuários reconfigurassem aplicações sem necessidade de recompilação das aplicações.
Atualmente é a linguagem de script mais usada para programação de jogos (em segundo lugar está Python).
Uma curiosidade de Lua é não haver um módulo de suporte a números complexos. Vamos então implementar um.
Metatabelas
Em orientação a objetos, o «tipo» de objeto é chamado classe. Lua trabalha com um conceito ligeiramente diferente: metatabelas e metamétodos.
Em Lua, os tipos definidos pelo usuário são sempre tabelas, cujos elementos podem ser quaisquer objetos de primeira ordem, inclusive funções (métodos) e outras tabelas.
Ainda é possível associar uma metatabela a uma tabela: esta metatabela define como a tabela deve comportar-se. Então a metatabela funciona de forma semelhante a classe em outras linguagens.
Os métodos da metatabela definem como a tabela reagirá às operações matemáticas, às comparações e até como responderá quando for solicitado o valor de um elemento elemento que não possui (podemos chamar isso de valores default).
Para as operações matemáticas temos os métodos
__add()
(adição), __sub()
(subtração), __mul()
(multiplicação), __div()
(divisão) e __pow()
(potência). Ainda há os métodos __unm()
(inversão de sinal), __tostring()
(conversão para string) e __concat()
(concatenação).As operações comparativas são
__eq()
(igualdade), __lt()
(menor que) e __le()
(menor ou igual). Alguém pode perguntar «e quanto a ‘maior que’ e ‘maior ou igual’?». A resposta é simples, se a > b
então b < a
, capicci? A mesma idéia é para diferença (a ~= b
é o mesmo que not a == b
).Está confuso até aqui? Vai clarear assim que começarmos a criar nossa metatabela. Vamos então à criação de nossa metatabela: primeiro usaremos o construtor de tabela (
{}
) para criar a metatabela com alguns valores default e em seguida criaremos o construtor próprio para números complexos:local cmt = { real = 0, img = 1 }
cmt.__index = cmt
function cmt:new(o)
o = o or {}
setmetatable(o, self)
return o
end
Números complexos são formados por duas partes, uma real e uma imagem, então criamos nossa metatabela assim.
A sintaxe
cmt:new(o)
é um açúcar sintático para cmt.new(self, o)
e é usada para métodos.O comando
setmetatable(o, self)
define que a metatabela de o
será self
e o elemento __index
informa de onde a tabela deve retirar os valores default (para elementos não definidos), e será a própria metatabela.No entanto queremos ter alguma flexibilidade ao criar um número complexo:
- Se não for passado argumento, queremos que a função retorne i;
- Se for passado um número (real), queremos retornar ele mesmo;
- Se for passado um número complexo, queremos retornar uma cópia dele;
- Se forem passados dois números (reais), queremos que o primeiro seja a parte real e que o segundo seja a imagem (se a imagem for zero, retorne somente a parte real).
Assim sendo, podemos criar a seguinte função:
local function new(...)
if arg.n == 0 then
-- nenhum argumento retorna i
return cmt:new { real = 0, img = 1 }
elseif arg.n == 1 and type(arg[1]) == “number” then
-- um argumento: numero real
return arg[1]
elseif arg.n == 1 and getmetatable(arg[1]) == cmt then
-- um argumento: numero complexo
return cmt:new { real = arg[1].real, img = arg[1].img }
elseif arg.n == 2 and
type(arg[1]) == “number” and type(arg[2]) == “number” then
-- dois argumentos reais
if arg[2] == 0 then
return arg[1]
else
return cmt:new { real = arg[1], img = arg[2] }
end
else
error “parse error”
end
end
Mostrando nosso número complexo
O primeiro método que definiremos será para exibir nosso número complexo.
Sem este método, o comando abaixo retornaria assim:
lua> print(numero)
table: 0x80725e0
E queremos que, na verdade retorne algo do tipo:
lua> print(numero)
3 + 2i
Então precisamos definir o método
__tostring()
. Mas não será tão fácil assim!Imagine só: precisamos definir pelo menos oito casos diferentes:
- imagem = 0
- real = 0, imagem = 1
- real = 0, imagem = -1
- real ≠ 0, imagem = 1
- real ≠ 0, imagem = -1
- real = 0, imagem ≠ 0
- real ≠ 0, imagem > 0 e imagem ≠ 1
- real ≠ 0, imagem < 0 e imagem ≠ -1
Vamos então!
function cmt:__tostring()
local a, b = self.real, self.img
if b == 0 then
return tostring(a)
elseif b == 1 and a == 0 then
return “i”
elseif b == -1 and a == 0 then
return “-i”
elseif b == 1 and a ~= 0 then
return a .. “ + i”
elseif b == -1 and a ~= 0 then
return a .. “ - i”
elseif b ~= 0 and a == 0 then
return b .. “i”
elseif b > 0 and a ~= 0 then
return a .. “ + “ .. b .. “i”
elseif b < 0 and a ~= 0 then
return a .. “ - “ .. (-b) .. “i”
else
error “unexpected (a + bi) combination: contact author”
end
end
Uma funçãozinha útil
Para definir alguns parâmetros importantes precisamos definir o que acontece com o número quando tentamos invertê-lo (
(a + bi)-1
).A operação matemática é multiplicar a fração resultante por uma expressão equivalente a 1, como
(a – bi) / (a – bi)
.Fazendo esta continha simpática, obtemos real
a / (a2 + b2)
e imagem -b / (a2 + b2)
.Vamos criar nossa função:
local function inv(v)
if getmetatable(v) ~= cmt then
return v ^ (-1)
else
local a, b, q = v.real, v.img
q = a ^ 2 + b ^ 2
return new(a / q, -b / q)
end
end
Nesta função, a primeira coisa que fizemos foi verificar se o argumento é um número complexo. Se não for, a função retorna um dividido pelo argumento. Se for um número complexo, realiza o cálculo citado.
Repare na linha:
local a, b, q = v.real, v.img
Neste comando,
a
, b
e q
são definidos como variáveis locais. a
recebe v.real
, b
recebe v.img
e q
recebe nil
.Inversão de sinais
Se temos um número complexo
b
, queremos que -b
retorne um número complexo com os sinais do real e da imagem invertidos:function cmt:__unm()
return new(-self.real, -self.img)
end
Igualdade
Vamos verificar igualdade. Quando Lua verifica
a == b
, sendo a
e b
tabelas, na verdade está verificando se a
e b
são exatamente a mesma tabela, não se seus elementos são iguais. O quer queremos quando comparamos dois números complexos é se representam o mesmo valor, ou seja, se seus reais são iguais e se suas imagens também são.Precisamos então criar um método para tratar isso:
function cmt:__eq(v)
if getmetatable(v) == cmt then
return self.real == v.real and self.img == v.img
else
return self.img == 0 and v == self.real
end
end
Operações binárias
Agora definiremos as operações binárias, ou seja, que necessitam de dois operandos: adição (
+
), subtração (-
), multiplicação (*
), divisão (/
), potência (^
) e concatenação (..
).Em todos os casos verificaremos se o segundo elemento da operação é também um número complexo ou não.
- Adição
function cmt:__add(v)
local a1, b1, a2, b2, a3, b3 = self.real, self.img
if getmetatable(v) ~= cmt then
a2, b2 = v, 0
else
a2, b2 = v.real, v.img
end
a3, b3 = a1 + a2, b1 + b2
return new(a3, b3)
end
Na declaração das variáveis locais,
a1
recebe self.real
e b1
self.img
. Todas as demais variáveis recebem nil
.- Subtração (nada mais do que soma com o sinal invertido)
function cmt:__sub(v)
return self + (-v)
end
- Multiplicação (muito semelhante à adição)
function cmt:__mut(v)
local a1, b1, a2, b2, a3, b3 = self.real, self.img
if getmetatable(v) ~= cmt then
a2, b2 = v, 0
else
a2, b2 = v.real, v.img
end
a3, b3 = a1 * a2 – b1 * 2, a1 * b2 + a2 * b1
return new(a3, b3)
end
- Divisão (divisão é a multiplicação onde o segundo termo é invertido)
function cmt:__div(v)
return self * inv(v)
end
- Potência (aqui trataremos apenas expoentes inteiros, que não passam de multiplicações sucessivas)
function cmt:__pow(v)
if v == 0 then
-- expoente 0 retorna 1
return 1
elseif v == 1 then
-- expoente 1 retorna uma cópia de si mesmo
return new(self)
elseif v < 0 then
-- expoente negativo retorna inversao
return inv(self ^ (-v))
else
local aux, cont = new(self)
for cont = 2, v do
aux = self * aux
end
return aux
end
end
Repare a recursividade na linha:
return inv(self ^ (-v))
A potência chama novamente
__pow()
para self
, mas desta vez com argumento -v
.- Concatenação (fácil: retorna a contenação das strings!)
function cmt:__concat(v)
return tostring(self) .. tostring(v)
end
Vamos tornar isso tudo útil?
Até agora está tudo muito bonito, tratando números complexos e tudo mais... mas números complexos só são úteis se pudermos fazer duas coisas: 1converter números reais em complexos quando tentamos extrair a raiz de um número negativo e 2converter números complexos para reais por meio das operações básicas.
Bem, a segunda coisa nosso módulo já faz, falta a primeira! Para tanto, vamos criar uma função de raiz quadrada segura, que retorne um número complexo quando a base for negativa:
local function sqrt(v)
if type(v) ~= “number” then
error “value must be a number”
end
if v >= 0 then
return v ^ .5
else
return (0, (-v) ^ .5)
end
end
Também será útil termos uma função que retorne verdadeiro ou falso para verificar se um valor é um número complexo:
local function iscomplex(v)
return getmetatable(v) == cmt
end
Finalizando
Até aqui, tudo o que fizemos foi privado (
local
). Vamos então criar uma tabela pública onde colocaremos somente as partes que nos interessam:complex = {
iscomplex = iscomplex,
new = new,
sqrt = sqrt,
i = new()
}
Vamos agora testar! Salve este arquivo como
complex.lua
no diretório atual e execute o interpretador lua
. Execute os seguintes comandos e veja se funciona:lua> require “complex”
lua> for c = -4, 7 do print(c, complex.i ^ c) end
-4 1
-3 i
-2 -1
-1 -i
0 1
1 i
2 -1
3 -i
4 1
5 i
6 -1
7 -i
lua> a = complex.sqrt(-9) + 2
lua> b = complex.new(3, 2)
lua> print(a, b)
2 + 3i 3 + 2i
lua> print(a + b)
5 + 5i
lua> print(a * b)
13i
lua> print(a / 2)
1 + 1.5i
lua> print(b ^ 2)
5 + 12i
Se tudo sair direitinho, parabéns! Acabou de fazer seu primeiro módulo de números complexos.
[]'s