Números complexos em Lua II
Este artigo foi publicado por mim originalmente em Reflexões de Monte Gasppa e Giulia C.. Esta é uma atualização para Lua 5.1.
Lua é uma poderosa linguagem de programação procedimental, 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, classe é um molde para a criação de objetos similares, chamados instâncias em relação à classe. Lua trabalha com um conceito ligeiramente diferente: metatabelas e metamétodos.
Em Lua tudo são tabelas, cujos elementos são quaisquer objetos de primeira ordem, inclusive funções (chamadas métodos ou metamétodos em classes) e outras tabelas.
Alguns métodos especiais, chamados metamétodos aritméticos, são usados para reagir a operações aritméticas. Há ainda os metamétodos comparativos – que reagem a operações de comparação – e os metamétodos de string.
Os metamétodos aritméticos, comparativos e de string são:
__add()
(adição), __sub()
(subtração), __mul()
(multiplicação), __div()
(divisão), __pow()
(potência), __unm()
(inversão de sinal), __tostring()
(conversão para string), __concat()
(concatenaçã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 {}
return setmetatable(o, self)
end
Números complexos são formados por duas partes, uma real (ℜ) e outra imagem (ℑ), então criamos nossa metatabela assim.
A sintaxe
function cmt:new(o)
é um açúcar sintático para function cmt.new(self, o)
e é usada para métodos.O comando
setmetatable(o, self)
define que a metatabela de o será self
(que representa mt
) e o elemento __index
informa de onde a tabela deve retirar os valores padrão (para elementos não definidos), e será a própria metatabela.No entanto queremos ter alguma flexibilidade ao criar um número complexo:
- e não for passado argumento, queremos que a função retorne
j
; - 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:
function new(...)
if #{...} == 0 then
-- nenhum argumento retorna j
return cmt:new { real = 0, img = 1 }
elseif #{...} == 1 and type(select(1, ...)) == "number" then
-- um argumento: numero real
return select(1, ...)
elseif #{...} == 1 and getmetatable(select(1, ...)) == cmt then
-- um argumento: numero complexo
return cmt:new { real = select(1, ...).real, img = select(1, ...).img }
elseif #{...} == 2 and
type(select(1, ...)) == "number" and type(select(2, ...)) == "number" then
-- dois argumentos reais
if select(2, ...) == 0 then
return select(1, ...)
else
return cmt:new { real = select(1, ...), img = select(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: 0×80725e0
E queremos que, na verdade retorne algo do tipo:
lua> print(numero)
3 + 2j
Para tanto é preciso 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 "j"
elseif b == -1 and a == 0 then
return "-j"
elseif b == 1 and a ~= 0 then
return a .. " + j"
elseif b == -1 and a ~= 0 then
return a .. " - j"
elseif b ~= 0 and a == 0 then
return b .. "j"
elseif b > 0 and a ~= 0 then
return a .. " + " .. b .. "j"
elseif b < 0 and a ~= 0 then
return a .. " - " .. (-b) .. "j"
else
error "unexpected (a + bj) combination"
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 operação matemática é multiplicar a fração resultante por uma expressão equivalente a 1, como .
Fazendo esta continha simpática, obtemos real e imagem .
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 além de adição com 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 inversão
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: ¹converter números reais em complexos quando tentamos extrair a raiz de um número negativo e ²converter 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:
function sqrt(v)
if type(v) ~= "number" then
error "value must be a number"
end
if v >= 0 then
return v ^ .5
else
return new(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:
function iscomplex(v)
return getmetatable(v) == cmt
end
j = new()
Finalizando
Para transformar isso tudo num pacote, salve todos os códigos num arquivo (pode ser
complex.lua
) e coloque na primeira linha do arquivo:module("complex", package.seeall)
Vamos agora testar! Acesse o diretório onde está o arquivo
complex.lua
e execute o interpretador lua51
. Execute os seguintes comandos e veja se funciona:lua> require “complex”
lua> for c = -4, 7 do print(c, complex.j ^ c) end
-4 1
-3 j
-2 -1
-1 -j
0 1
1 j
2 -1
3 -j
4 1
5 j
6 -1
7 -j
lua> a = complex.sqrt(-9) + 2
lua> b = complex.new(3, 2)
lua> print(a, b)
2 + 3j 3 + 2j
lua> print(a + b)
5 + 5j
lua> print(a * b)
13j
lua> print(a / 2)
1 + 1.5j
lua> print(b ^ 2)
5 + 12j
Se tudo sair direitinho, parabéns! Acabou de fazer seu primeiro módulo de números complexos.
[]’s
PS: A primeira versão deste artigo foi escrita para Lua 5.0 e publicada aqui. Esta versão foi publicada pela primeira vez no Wordpress.com, no entanto tive problemas com a ferramenta deles.