BitMasks e operações bitwise
São usadas desde pequenas aplicações até motores tri-dimensionais. Sempre que existe necessidade de pequenas definições, género interruptor (as chamadas flags), muito provavelmente (senão com toda a certeza), são usadas bitmasks. Elas permitem a passagem de uma série de argumentos booleanos em apenas um. Mas como?
O problema
Vamos imaginar que temos entre mãos uma aplicação que tem como objectivo registar sintomas médicos comuns para fins estatísticos. Para além dos dados da pessoa, existem 8 sintomas que devem ser registados, marcando se determinado paciente o manifesta, ou não. Temos então de registar e armazenar os seguintes dados: Nome, Sexo, Idade, Febre?, Cansaço constante?, Dor muscular?, Enxaquecas?, Dormências?, Insónias?, Náuseas? e Prisão de ventre? Existem inúmeras abordagens a este tipo de problema.
A solução comum
No lado da aplicação
Declaração
Dim Nome As String
Dim Sexo As String
Dim Idade As Integer
Dim Febre As Boolean
Dim Cansaco As Boolean
Dim DorMuscular As Boolean
Dim Enxaquecas As Boolean
Dim Dormencias As Boolean
Dim Insonias As Boolean
Dim Nauseas As Boolean
Dim PrisaoVentre As Boolean
Armazenamento
Public Sub ArmazenarRegisto(Nome As String,Sexo As String, Idade As Integer, Febre As Boolean, Cansaco As Boolean, DorMuscular As Boolean, Enxaquecas As Boolean, Dormencias As Boolean, Insonias As Boolean, Nauseas As Boolean, PrisaoVentre As Boolean)
'Instruções de armazenamento
End Sub
Adição de atributo
DorMuscular=True
Enxaquecas=True
Dormencias=True
PrisaoVentre=True
Remoção de atributo
DorMuscular=False
Enxaquecas=False
Dormencias=False
PrisaoVentre=False
Teste
If DorMuscular=True Then
If DorMuscular=False Then
No lado da base de dados
PessID int
Nome nvarchar(100)
Sexo varchar(1)
Idade smallint
Febre bit
Cansaco bit
DorMuscular bit
Enxaquecas bit
Dormencias bit
Insonias bit
Nauseas bit
PrisaoVentre bit
Operações "bitwise"
Deixemos agora um pouco de parte o problema, que ainda temos de resolver de uma maneira mais eficaz, para nos focarmos naquilo que é no fundo a solução. Operações bitwise são, resumidamente, operações que trabalham directamente os bits individuais de um ou mais padrões de bits. Como é sabido, os bits podem assumir apenas um de dois valores. Ou estão ligados, ou não estão. Um padrão de bits como 00110 significa literalmente desligado, desligado, ligado, ligado, desligado
Entender os operadores
Existem quatro operadores: NOT
, OR
, XOR
e AND
.
NOT (negação)
O operador NOT
inverte todos os bits. Os que estavam ligados passam a desligados e vice-versa. Este operador não é regularmente usado em máscaras de bits.
NOT 001100
= 110011
OR (adição)
O operador OR
soma dois padrões de bits. A lógica é tão simples como a frase: "1 OU 2 incluí tanto o 1 como o 2 nas possibilidades"
0110010
OR 1001000
= 1111010
XOR (subtracção)
O operador XOR
(exclusive OR) produz o efeito inverso ao OR
. É utilizado para subtraír um padrão de bits de outro, devolvendo 0 sempre que existam dois bits iguais entre as partes de comparação.
1111010
XOR 1001000
= 0110010
AND (intersecção)
O operador AND
é utilizado para testar o padrão. É desta forma que nos é possível saber se determinado padrão de bits foi adicionado a outro. Se um padrão não fizer parte de outro, o AND
devolve todos os bits do padrão desligados.
1111010 1111010
AND 1001000 AND 0000100
= 1001000 = 0000000
Esquema exemplo
Aplicar operações "bitwise"
A noção de operações bitwise é muito importante, pois as máscaras de bits assentam inteiramente na sua filosofia.
Máscara de bits?
Vamos encarar uma máscara de bits como uma representação decimal de um padrão de bits. Partindo do conceito binário, é correcto assumir que a melhor maneira de garantir que as máscaras são únicas, é através de potências de dois.
Assim, 1 traduz-se em 1; 2 traduz-se em 10; 4 traduz-se em 100; 8 traduz-se em 1000; ... Então, somando um 4 e um 8, ficamos com 1100; 4 e 2, ficamos com 110; ...
Se aplicarmos os operadores referidos anteriormente, nos bits que as máscaras representam, é nos possível determinar, por exemplo, que uma soma de máscaras que totalize 12 nunca pode representar nenhuma máscara diferente do 8 e do 4. De facto, não existem dois conjuntos de máscaras diferentes que possam resultar no mesmo número:
1+2 = 3
1+4 = 5
2+1 = 3 = 1+2
2+4 = 6
1+8 = 9
2+8 = 10
4+8 = 12
... = ..
É esta particularidade que possibilita o uso de bitmasks. Recorremos a operações bitwise com a representação binária do número, pois facilita em muito a tarefa de determinar o que somou com o que, ou o que subtraíu de onde, bem como a verificação da existência de um elemento.
Ideia: Um excelente exemplo da utilização de bitmasks, e demonstração da sua utilidade, são as MessageBoxes com todos os seus estilos e conjuntos de botões
A solução óptima
Com a noção de bitmask assente, é fácil de imaginar uma solução bem melhor do que a anteriormente proposta.
No lado da aplicação
Declaração
Enum Sintomas
Febre = 1
Cansaco = 2
DorMuscular = 4
Enxaquecas = 8
Dormencias = 16
Insonias = 32
Nauseas = 64
PrisaoVentre = 128
End Enum
Nome As String
Sexo As String
Idade As Integer
Armazenamento
Public Sub ArmazenarRegisto(Nome As String,Sexo As String, Idade As Integer, Sintomas As Integer)
'Instruções de armazenamento
End Sub
Adição de atributo
Atributos=4+8+16+128
Ou
Atributos As Sintomas
Atributos = (Atributos Or DorMuscular)
Atributos = (Atributos Or Enxaquecas)
Atributos = (Atributos Or Dormencias)
Atributos = (Atributos Or PrisaoVentre)
Remoção de atributo
Atributos=4-8-16-128
Ou
Atributos As Sintomas
Atributos = (Atributos Xor DorMuscular)
Atributos = (Atributos Xor Enxaquecas)
Atributos = (Atributos Xor Dormencias)
Atributos = (Atributos Xor PrisaoVentre)
Teste
Atributos As Sintomas
If CBool(Atributos And DorMuscular) Then
If Not CBool(Atributos And DorMuscular) Then
No lado da base de dados
PessID int
Nome nvarchar(100)
Sexo varchar(1)
Idade smallint
Atributos int
Importante: Se os dados forem consultados por outras aplicações, convém desdobrar a máscara de bits para uma estructura semelhante à apresentada na primeira solução, pois só assim se garante que é possível executar queries que façam sentido
Conclusão
O uso de bitmasks não só pode diminuir o número de variáveis que terá de declarar e controlar como também diminui as assinaturas dos métodos, contribuíndo para um código mais limpo, legível e mais fácil de manter.