terça-feira, 13 de março de 2007

Números complexos em Lua

Lua 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:
  1. imagem = 0
  2. real = 0, imagem = 1
  3. real = 0, imagem = -1
  4. real ≠ 0, imagem = 1
  5. real ≠ 0, imagem = -1
  6. real = 0, imagem ≠ 0
  7. real ≠ 0, imagem > 0 e imagem ≠ 1
  8. 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 (1 / (a + bj)).

A operação matemática é multiplicar a fração resultante por uma expressão equivalente a 1, como (a - bj) / (a - bj).

Fazendo esta continha simpática, obtemos real a / (a² + b²) e imagem -b / (a² + b²).

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.
blog comments powered by Disqus