Ir para o conteúdo

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

Exemplos das operaçõs bitwise AND, OR e XOR

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.