Avançar para o conteúdo
Imagem com logotipo, contendo link para a página inicial
  • United Stated of America flag, representing the option for the English language.
  • Bandeira do Brasil, simbolizando a opção pelo idioma Português do Brasil.

Aprenda Programação: Operações Bit-a-Bit (Bitwise Operations)

Exemplos de uso de operações bit-a-bit em quatro linguagens de programação: Python, Lua, GDScript e JavaScript.

Créditos para a imagem: Imagem criada pelo autor usando o programa Spectacle.

Pré-Requisitos

Na introdução sobre ambientes de desenvolvimento, indiquei Python, Lua e JavaScript como boas escolhas de linguagens de programação para iniciantes. Posteriormente, comentei sobre GDScript como opção para pessoas que tenham interesse em programar jogos digitais ou simulações. Para as atividades de introdução a programação, você precisará de, no mínimo, um ambiente de desenvolvimento configurado em uma das linguagens anteriores.

Caso queira experimentar programação sem configurar um ambiente, você pode usar um dos editores online que criei:

Contudo, eles não possuem todos os recursos dos interpretadores para as linguagens. Assim, cedo ou tarde, você precisará configurar um ambiente de desenvolvimento. Caso precise configurar um, confira os recursos a seguir.

Assim, se você tem um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE) ou a combinação de editor de texto com interpretador, você está pronto para começar. Os exemplos assumem que você saiba executar código em sua linguagem escolhida, como apresentado nas páginas de configuração.

Caso queira usar outra linguagem, a introdução provê links para configuração de ambientes para as linguagens C, C++, Java, LISP, Prolog e SQL (com SQLite). Em muitas linguagens, basta seguir os modelos da seção de experimentação para adaptar sintaxe, comandos e funções dos trechos de código. C e C++ são exceções, por requerem o uso de ponteiros para acesso à memória.

Um Byte, Oito Bits

O tipo de dados lógico representa dois possíveis valores: Verdadeiro (True) ou Falso (False). Um bit pode representar dois valores binários. Logo, um bit seria suficiente para armazenar uma variável to tipo lógico.

Contudo, a realidade nem sempre é simples como a teoria. É bastante comum que linguagens de programação definam uma variável do tipo lógico com um byte, ou até mesmo quatro ou oito bytes. Ou seja, 8, 32 ou 64 bits, quando apenas 1 bastaria.

Um desperdício que possui uma razão para existir: desempenho. Existe um conceito em arquiteturas de computadores chamado de alinhamento de dados. Todo dado possui um endereço em memória. O acesso a um dado tende a ser mais rápido quando existe um padrão fixo para acesso aos endereços. Alinhar endereços de um em um, quatro em quatro, ou oito em oito bytes é algo que faz sentido.

Um motivo é que todos os endereços serão múltiplos de oito (pois cada byte tem oito bits). Assumindo-se endereçamento com 1 byte, se um endereço começa em 0, o próximo começa em 8, depois 16, 24, 32, e assim por diante. Para encontrar o próximo valor, basta procurar pelo próximo múltiplo de oito. Se uma variável possui 4 bytes, basta avançar 32 unidades.

Caso se armazenasse bits individuais, o início de cada endereço poderia ter qualquer valor decimal. Se o primeiro valor armazenado tiver 1 bit em um endereço inciado em 0, o próximo valor estará no endereço 1. Se o segundo tiver 1 byte, o próximo valor estará no endereço 9. Mais um bit, endereço 10. A complexidade para identificação do endereço torna-se mais complexa e dependente do que está armazenado em um determinado momento.

Computadores e dispositivos modernos podem possuir grandes quantidades de memória. Caso uma máquina possua gigabytes de memória RAM, a troca de alguns bits por melhor desempenho ao custo memória pode compensar. Todavia, isso nem sempre ocorre.

Existem máquinas com quantidades limitadas de memória (como poucas centenas de bytes). Isso é comum, por exemplo, quando se trabalha com circuitos embarcados. Também existem programas nos quais desempenho pode ser essencial; em outros programas, a otimização de certas partes do código pode ser desejável. Mesmo em computadores e dispositivos modernos, a quantidade de memória cache é limitada (normalmente a algumas dezenas de quilobytes ou cerca de dois megabytes). Em arquiteturas modernas, localidade de referência é um conceito importante para computação de alto desempenho. Quanto mais próximos os dados, maiores as chances de acesso ao valor necessário em uma memória mais rápida (como cache).

Algumas linguagens de programação, especialmente linguagens de nível mais baixo, fornecem formas de acessar bits individuais de uma região de memória. Isso é comumente feito com o tipo inteiro. Pensando-se um número inteiro como um conjunto de bytes, pode imaginar que cada bit do número é uma "sub-variável" do tipo lógico. Ou seja, 1 byte pode armazenar 8 valores lógicos individuais; 4 bytes podem armazenar 32 valores lógicos individuais; 8 bytes podem armazenar 64 valores lógicos. Analogamente, um inteiro de 4 bytes podem armazenar 2 valores de 2 bytes ou 4 valores de 1 bytes cada um.

Em troca da complexidade de operação e manipulação de dados, pode-se usar todos os bits da memória.

Operações Bit-a-Bit

O que se pode fazer com um bit? A resposta consta em um tópico anterior: Operações Lógicas e Álgebra Booleana. De fato, portas lógicas pode definir computadores.

Existem quatro operações lógicas principais:

Desde a introdução das operações, as três primeiras foram comumente usadas em tópicos anteriores. A manipulação de bits possui quatro operações equivalentes. Para evitar confusão, costuma-se sufixá-las com o termo bit-a-bit. Da mesma forma, pode-se sufixar as quatro operações com tipos lógicos (booleanos) por lógico.

Operações bit-a-bit também possui uma operação própria, de deslocamento de bits. As próximas subseções detalham as seis operações bit-a-bit típicas.

Ou Bit-a-Bit (Bitwise Or)

Do tópico de operadores lógicos, o Ou Lógico possui a seguinte tabela verdade:

FalsoFalsoFalso
FalsoVerdadeiroVerdadeiro
VerdadeiroFalsoVerdadeiro
VerdadeiroVerdadeiroVerdadeiro
000
011
101
111

O ou bit-a-bit aplica a operação ou a cada um dos bits de uma variável. Em linguagens de programação, o operador é comumente representado por uma única barra vertical (|). Por exemplo, em uma variável de 1 byte (8 bits) considerada como inteiro sem sinal:

Variável / Bit76543210Valor Decimal
p0011110060
q0101101090
p | q01111110126

Na tabela anterior, faz-se:

  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • .

Com uma única operação, fez-se, pois, o equivalente oito operações lógicas. Para variáveis com mais bytes, o mesmo processo ocorre. Assim, para um inteiro com 4 bytes, faz-se o equivalente 32 operações lógicas em uma única operação. Todas as outras operações bit-a-bit funcionam deste mesmo modo.

É importante observar que coluna da tabela com valor decimal fornece o valor que seria escrito como inteiro. Entretanto, em operações bit-a-bit, o interesse é a representação binária do número inteiro. Operações bit-a-bit são, portanto, uma forma alternativa de como interpretar uma representação binária (mesmo que o tipo de dado corresponda a um tipo de outro nome).

E Bit-a-Bit (Bitwise And)

Do tópico de operadores lógicos, o E Lógico possui a seguinte tabela verdade:

FalsoFalsoFalso
FalsoVerdadeiroFalso
VerdadeiroFalsoFalso
VerdadeiroVerdadeiroVerdadeiro
000
010
100
111

O e bit-a-bit aplica a operação e a cada um dos bits de uma variável. Em linguagens de programação, o operador é comumente representado por um único e-comercial (&). Por exemplo, em uma variável de 1 byte (8 bits) considerada como inteiro sem sinal:

Variável / Bit76543210Valor Decimal
p0011110060
q0101101090
p | q0001100024

Na tabela anterior, faz-se:

  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • .

Não Bit-a-Bit (Bitwise Not) ou Complemento de Um (One's Complement)

Do tópico de operadores lógicos, o Não Lógico possui a seguinte tabela verdade:

FalsoVerdadeiro
VerdadeiroFalso
01
10

O não bit-a-bit aplica a operação não a cada um dos bits de uma variável. Em linguagens de programação, o operador é comumente representado por um til (~). Por exemplo, em uma variável de 1 byte (8 bits) considerada como inteiro sem sinal:

Variável / Bit76543210Valor Decimal
p0011110060
~p11000011195

Na tabela anterior, faz-se:

  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • .

Ou-Exclusivo (Xou) Bit-a-Bit (Bitwise Xor)

Do tópico de operadores lógicos, o Xou Lógico possui a seguinte tabela verdade:

FalsoFalsoFalso
FalsoVerdadeiroVerdadeiro
VerdadeiroFalsoVerdadeiro
VerdadeiroVerdadeiroFalso
000
011
101
110

O xou bit-a-bit aplica a operação xou a cada um dos bits de uma variável. Em linguagens de programação, o operador é comumente representado por um acento circunflexo (^). Por exemplo, em uma variável de 1 byte (8 bits) considerada como inteiro sem sinal:

Variável / Bit76543210Valor Decimal
p0011110060
q0101101090
p ^ q01100110102

Na tabela anterior, faz-se:

  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • ;
  • .

Deslocamento de Bits (Bit Shift)

Além das operações lógicas, linguagens de programação podem fornecer operadores para deslocamento de bits. O princípio é simples: mover todos os bits de um número para uma direção.

A rigor, existem três tipos de deslocamentos:

  1. Deslocamento aritmético;
  2. Deslocamento lógico;
  3. Deslocamento circular.

Linguagens de programação costumam fornecer operadores para o deslocamento aritmético. Portanto, eles serão comentados nas próximas subseções. Caso você queira aprender sobre os outros tipos, pode-se consultar esta entrada da Wikipedia (em inglês). A versão em Português é menos completa.

Deslocamento para a Esquerda (Left Shift)

O deslocamento para a esquerda move todos os bits armazenados em uma variável de uma quantidade de casas para a esquerda. No bit menos significativo (), introduz-se sempre o valor zero (0). Na prática, o deslocamento para a esquerda equivale a multiplicar um número por 2.

Em linguagens de programação, o deslocamento a esquerda costuma usar um operador representado por dois símbolos de menor em seqüência (<<). Após o operador, deve-se fornecer o número de casas para o deslocamento.

Por exemplo, em uma variável de 1 byte (8 bits) considerada como inteiro sem sinal:

Variável / Bit76543210Valor Decimal
p0011110060
p << 00011110060
p << 101111000120
p << 211110000240
p << 311100000224
p << 411000000192
p << 510000000128
p << 6000000000

Contudo, como demonstra a tabela, o resultado da operação como multiplicação é válido apenas caso:

  1. O dígito mais significativo (sem sinal) seja zero;
  2. A representação do número tenha quantidade arbitrária (ao invés de fixa) de dígitos binários.

Caso contrário, o valor resultante pode ser menor que o original. Isso ocorre em p << 2 e p << 3 no exemplo. Para se obter 480, seriam necessários 9 bits; para 960, 10 bits; e assim por diante.

Para números com sinal, o deslocamento pode modificar o sinal do número, dependendo da implementação. Por exemplo, para uma variável de 1 byte (8 bits) considerada como inteiro com sinal:

Variável / Bit76543210Valor Decimal
p0011110060
p << 00011110060
p << 101111000120
p << 211110000-16
p << 311100000-32
p << 411000000-64
p << 510000000-128
p << 6000000000

Isso ocorre porque, em complemento de dois, o dígito mais significativo (no caso, ) é usado para números negativos.

Deslocamento para a Direita (Right Shift)

O deslocamento para a direita move todos os bits armazenados uma quantidade de casas para a direita. Na prática, o deslocamento para a direita equivale a dividir um número por 2.

Em linguagens de programação, o deslocamento a direita costuma usar um operador representado por dois símbolos de maior em seqüência (>>). Após o operador, deve-se fornecer o número de casas para o deslocamento.

No bit mais significativo ( para 1 byte), o valor introduzido depende de implementação. Em implementações que não preservam o sinal do número, adiciona-se um zero; o número sempre será positivo.

Por exemplo, em uma variável de 1 byte (8 bits) considerada como inteiro sem sinal:

Variável / Bit76543210Valor Decimal
p0011110060
p >> 00011110060
p >> 10001111030
p >> 20000111115
p >> 3000001117
p >> 4000000113
p >> 5000000011
p >> 6000000000

Em implementações que preservam o sinal, copia-se o valor do bit de sinal e insere-se zero no segundo dígito mais significativo. Os outros dígitos são deslocados normalmente.

Variável / Bit76543210Valor Decimal
p11000100-60
p >> 011000100-60
p >> 111100010-30
p >> 211110001-15
p >> 311111000-8
p >> 411111100-4
p >> 511111110-2
p >> 611111111-1

Operações Bit-a-Bit em Linguagens de Programação

O suporte de operações bit-a-bit varia entre linguagens de programação. Linguagens de nível mais baixo costumam fornecer operadores. Linguagens de nível mais alto podem não oferecê-los.

JavaScript, Python, Lua e GDScript

JavaScript, Python e GDScript são linguagens que fornecem operadores bit-a-bit pré-definidos. Em Lua, a linguagem fornece operadores pré-definidos desde a versão 5.3 (documentação). A versão 5.1 de Lua não fornece operadores pré-definidos. A versão 5.2 oferece uma biblioteca pré-definida chamada bit32 (documentação).

let e_bit_a_bit = 60 & 90
let ou_bit_a_bit = 60 | 90
let nao_bit_a_bit = ~60
let xou_bit_a_bit = 60 ^ 90
let deslocamento_esquerda = 60 << 1
let deslocamento_direita = 60 >> 1
e_bit_a_bit = 60 & 90
ou_bit_a_bit = 60 | 90
nao_bit_a_bit = ~60
xou_bit_a_bit = 60 ^ 90
deslocamento_esquerda = 60 << 1
deslocamento_direita = 60 >> 1
-- Requer Lua 5.3 ou mais recente.
local e_bit_a_bit = 60 & 90
local ou_bit_a_bit = 60 | 90
local nao_bit_a_bit = ~60
-- Atenção! Lua usa ~ ao invés de ^.
local xou_bit_a_bit = 60 ~ 90
local deslocamento_esquerda = 60 << 1
local deslocamento_direita = 60 >> 1
extends Node

func _init():
    var e_bit_a_bit = 60 & 90
    var ou_bit_a_bit = 60 | 90
    var nao_bit_a_bit = ~60
    var xou_bit_a_bit = 60 ^ 90
    var deslocamento_esquerda = 60 << 1
    var deslocamento_direita = 60 >> 1

JavaScript, Python e GDScript também fornecem combinações dos operadores com a operação de atribuição. Os operadores são: &=, |=, ^=, <<= e >>=. Assim, por exemplo, x &= y é equivalente a x = x & y. Nenhuma das três linguagens define um operador ~= (para não bit-a-bit).

Quando se trabalha com operações bit-a-bit, tende a ser mais conveniente escrever valores em base binária ou hexadecimal. Quando disponível, a escrita em hexadecimal costuma usar o prefixo 0x, ao passo que escrita em binário costuma usar o prefixo 0b. Algumas linguagens de programação, como JavaScript, Python e GDScript, permitem a escrita de literais em ambas as bases. Lua permite a escrita em hexadecimal.

console.log(0b111100, 0x3c, 0x3C)
console.log(Number(60).toString(2), Number(60).toString(16))
print(0b111100, 0x3c, 0x3C)
print(bin(60), hex(60))
print(0x3c, 0x3C)
extends Node

func _init():
    printt(0b111100, 0x3c, 0x3C)

Algumas linguagens também fornecem subrotinas para conversão ou escrita de valores inteiros em base binária ou hexadecimal como uma cadeia de caracteres. JavaScript e Python são dois exemplos. Em JavaScript, Number.toString() (documentação) faz a conversão. Em Python, pode-se usar bin() (documentação) para conversão para binário e hex() (documentação) para hexadecimal.

Lua 5.1: Bibliotecas ou Criando-se a Própria Implementação

Lua 5.1 não fornece operadores bit-a-bit pré-definidos. Existem bibliotecas que fornecem implementações; esta página de lua-users.org fornece algumas sugestões.

Caso se use LuaJIT, a implementação fornece a biblioteca LuaBit pronta para uso. Também existe uma solicitação para adição de operadores para LuaJIT.

Outra possibilidade é criar a própria implementação. Uma forma simples (embora ineficiente) é converter um número inteiro para um vetor que armazene zeros e uns para representar os bits do números. Em seguida, para e, ou, não e xou, basta realizar a operação lógica correspondente para calcular o resultado para cada índice do vetor. Para deslocamento de bits, basta adicionar ou remover elementos do vetor.

Tente implementar uma solução como exercício para a prática. O próximo bloco de código apresenta uma possível implementação, baseada na explicação do parágrafo anterior.

function inteiro_para_binario(valor, numero_bits)
    local resultado = {}
    local sinal = 1
    if (valor >= 0) then
        valor = math.floor(valor)
    else
        valor = math.ceil(valor)
        sinal = -1
    end

    repeat
        table.insert(resultado, 1, valor % 2)

        valor = valor / 2
        if (valor >= 0) then
            valor = math.floor(valor)
        else
            valor = math.ceil(valor)
        end
    until (valor == 0)

    if (numero_bits ~= nil) then
        while (#resultado < numero_bits) do
            table.insert(resultado, 1, 0)
        end
    end

    return resultado, sinal
end

function binario_para_inteiro(vetor_binario, sinal)
    sinal = sinal or 1

    local resultado = 0
    for _, digito in ipairs(vetor_binario) do
        resultado = resultado * 2 + digito
    end

    return (sinal * resultado)
end

function escreva_binario(valor, numero_bits)
    local valor_binario, sinal = inteiro_para_binario(valor, numero_bits)
    if (sinal < 0) then
        io.write("-")
    end
    io.write("0b")
    for _, digito in ipairs(valor_binario) do
        io.write(digito)
    end
    io.write("\n")
end

function e_bit_a_bit(x, y, numero_bits)
    numero_bits = numero_bits or 32
    local x_binario = inteiro_para_binario(x, numero_bits)
    local y_binario = inteiro_para_binario(y, numero_bits)
    local resultado = {}
    for indice = 1, numero_bits do
        if ((x_binario[indice] == 1) and (y_binario[indice] == 1)) then
            table.insert(resultado, 1)
        else
            table.insert(resultado, 0)
        end
    end

    return resultado
end

function ou_bit_a_bit(x, y, numero_bits)
    numero_bits = numero_bits or 32
    local x_binario = inteiro_para_binario(x, numero_bits)
    local y_binario = inteiro_para_binario(y, numero_bits)
    local resultado = {}
    for indice = 1, numero_bits do
        if ((x_binario[indice] == 1) or (y_binario[indice] == 1)) then
            table.insert(resultado, 1)
        else
            table.insert(resultado, 0)
        end
    end

    return resultado
end

function nao_bit_a_bit(x, numero_bits)
    numero_bits = numero_bits or 32
    local x_binario = inteiro_para_binario(x, numero_bits)
    local resultado = {}
    for indice = 1, numero_bits do
        if (x_binario[indice] == 1) then
            table.insert(resultado, 0)
        else
            table.insert(resultado, 1)
        end
    end

    return resultado
end

function xou_bit_a_bit(x, y, numero_bits)
    numero_bits = numero_bits or 32
    local x_binario = inteiro_para_binario(x, numero_bits)
    local y_binario = inteiro_para_binario(y, numero_bits)
    local resultado = {}
    for indice = 1, numero_bits do
        if (x_binario[indice] ~= y_binario[indice]) then
            table.insert(resultado, 1)
        else
            table.insert(resultado, 0)
        end
    end

    return resultado
end

function deslocamento_esquerda(x, numero_bits)
    local resultado, sinal = inteiro_para_binario(x)
    for i = 1, numero_bits do
        table.insert(resultado, 0)
    end

    return resultado, sinal
end

function deslocamento_direita(x, numero_bits)
    local resultado, sinal = inteiro_para_binario(x)
    for i = 1, numero_bits do
        table.remove(resultado, #resultado)
        table.insert(resultado, 1, 0)
    end

    return resultado, sinal
end

No exemplo, o dígito mais significativo é armazenado no primeiro índice do vetor (índice 1 em Lua); o menos significativo é armazenado na última posição. A solução armazena o valor do sinal do número separadamente; compara-se valores como números inteiros sem sinal. Alternativamente, poder-se-ia criar a representação de bit de sinal ou complemento de dois em inteiro_para_binario(). Nesse caso, também seria necessário atualizar binario_para_inteiro() e deslocamento_direita().

Mostrar/Ocultar Exemplos de Uso

for i = -32, 32 do
    local valor_binario, sinal = inteiro_para_binario(i)
    print(binario_para_inteiro(valor_binario, sinal))
    escreva_binario(i, 8)
end

print("E bit-a-bit")
escreva_binario(0x6, 8)
escreva_binario(0xF, 8)
escreva_binario(binario_para_inteiro(e_bit_a_bit(0x6, 0xF, 8)), 8)
print("Ou bit-a-bit")
escreva_binario(0x6, 8)
escreva_binario(0xF, 8)
escreva_binario(binario_para_inteiro(ou_bit_a_bit(0x6, 0xF, 8)), 8)
print("Não bit-a-bit")
escreva_binario(0x6, 8)
escreva_binario(binario_para_inteiro(nao_bit_a_bit(0x6, 8)), 8)
print("---")
escreva_binario(0xF, 8)
escreva_binario(binario_para_inteiro(nao_bit_a_bit(0xF, 8)), 8)
print("Xou bit-a-bit")
escreva_binario(0x6, 8)
escreva_binario(0xF, 8)
escreva_binario(binario_para_inteiro(xou_bit_a_bit(0x6, 0xF, 8)), 8)
print("< 4")
escreva_binario(0x6, 8)
escreva_binario(binario_para_inteiro(deslocamento_esquerda(0x6, 4)), 8)
print("---")
escreva_binario(0xF, 8)
escreva_binario(binario_para_inteiro(deslocamento_esquerda(0xF, 4)), 8)
print("> 2")
escreva_binario(0x6, 8)
escreva_binario(binario_para_inteiro(deslocamento_direita(0x6, 2)), 8)
print("---")
escreva_binario(0xF, 8)
escreva_binario(binario_para_inteiro(deslocamento_direita(0xF, 2)), 8)

Exemplos

Sabendo-se operações lógicas, operações bit-a-bit não possuem muitos segredos. Por outro lado, elas possuem aplicações interessantes (embora de baixo nível). As próximas subseções apresentam usos típicos de operações bit-a-bit.

Flags e Múltiplas Propriedades em uma Única Variável

Uma flag (bandeira, em tradução livre para Português) é um valor que indica a presença (ou ausência) de uma propriedade. O uso de flags não é inédito para este tópico. Por exemplo, elas foram usadas em Entrada em Linha de Comando para estabelecer parâmetros binários.

Operações bit-a-bit permitem estender o uso de flags. Ao invés de uma variável por propriedade, pode-se empregar uma única variável para múltiplas propriedades.

const PRETO    = 0      // 0
const VERMELHO = 1 << 0 // 1
const VERDE    = 1 << 1 // 2
const AZUL     = 1 << 2 // 4

// Combinação de propriedades usando ou bit-a-bit.
let vermelho = VERMELHO
let amarelo = VERMELHO | VERDE
let magenta = VERMELHO | AZUL
let ciano = VERDE | AZUL
let branco = VERMELHO | VERDE | AZUL

// Verificação de ativação de propriedade com e bit-a-bit.
console.log("Magenta possui vermelho?", ((magenta & VERMELHO) === VERMELHO))
console.log("Branco possui vermelho e verde?",
            ((branco & (VERMELHO | VERDE)) === (VERMELHO | VERDE)))

// Criação de cor em modelo aditivo.
let cor = PRETO
cor |= VERMELHO
cor |= VERDE
// Remoção de propriedade usando e bit-a-bit e não bit-a-bit.
cor &= ~VERDE
console.log(cor === VERMELHO)

// Inversão de bits usando xou bit-a-bit.
let cor_complementar = cor ^ branco
console.log(cor_complementar === ciano)
PRETO    = 0      # 0
VERMELHO = 1 << 0 # 1
VERDE    = 1 << 1 # 2
AZUL     = 1 << 2 # 4

# Combinação de propriedades usando ou bit-a-bit.
vermelho = VERMELHO
amarelo = VERMELHO | VERDE
magenta = VERMELHO | AZUL
ciano = VERDE | AZUL
branco = VERMELHO | VERDE | AZUL

# Verificação de ativação de propriedade com e bit-a-bit.
print("Magenta possui vermelho?", ((magenta & VERMELHO) == VERMELHO))
print("Branco possui vermelho e verde?",
      ((branco & (VERMELHO | VERDE)) == (VERMELHO | VERDE)))

# Criação de cor em modelo aditivo.
cor = PRETO
cor |= VERMELHO
cor |= VERDE
# Remoção de propriedade usando e bit-a-bit e não bit-a-bit.
cor &= ~VERDE
print(cor == VERMELHO)

# Inversão de bits usando xou bit-a-bit.
cor_complementar = cor ^ branco
print(cor_complementar == ciano)
local PRETO    <const> = 0      -- 0
local VERMELHO <const> = 1 << 0 -- 1
local VERDE    <const> = 1 << 1 -- 2
local AZUL     <const> = 1 << 2 -- 4

-- Combinação de propriedades usando ou bit-a-bit.
local vermelho = VERMELHO
local amarelo = VERMELHO | VERDE
local magenta = VERMELHO | AZUL
local ciano = VERDE | AZUL
local branco = VERMELHO | VERDE | AZUL

-- Verificação de ativação de propriedade com e bit-a-bit.
print("Magenta possui vermelho?", ((magenta & VERMELHO) == VERMELHO))
print("Branco possui vermelho e verde?",
      ((branco & (VERMELHO | VERDE)) == (VERMELHO | VERDE)))

-- Criação de cor em modelo aditivo.
local cor = PRETO
cor = cor | VERMELHO
cor = cor | VERDE
-- Remoção de propriedade usando e bit-a-bit e não bit-a-bit.
cor = cor & (~VERDE)
print(cor == VERMELHO)

-- Inversão de bits usando xou bit-a-bit.
local cor_complementar = cor ~ branco
print(cor_complementar == ciano)
extends Node

const PRETO    = 0      # 0
const VERMELHO = 1 << 0 # 1
const VERDE    = 1 << 1 # 2
const AZUL     = 1 << 2 # 4

func _init():
    # Combinação de propriedades usando ou bit-a-bit.
    var vermelho = VERMELHO
    var amarelo = VERMELHO | VERDE
    var magenta = VERMELHO | AZUL
    var ciano = VERDE | AZUL
    var branco = VERMELHO | VERDE | AZUL

    # Verificação de ativação de propriedade com e bit-a-bit.
    printt("Magenta possui vermelho?", ((magenta & VERMELHO) == VERMELHO))
    printt("Branco possui vermelho e verde?",
           ((branco & (VERMELHO | VERDE)) == (VERMELHO | VERDE)))

    # Criação de cor em modelo aditivo.
    var cor = PRETO
    cor |= VERMELHO
    cor |= VERDE
    # Remoção de propriedade usando e bit-a-bit e não bit-a-bit.
    cor &= ~VERDE
    print(cor == VERMELHO)

    # Inversão de bits usando xou bit-a-bit.
    var cor_complementar = cor ^ branco
    print(cor_complementar == ciano)

Os exemplos anteriores ilustram as operações mais comuns com flags:

  • Verificação de bits: uso de e bit-a-bit para verificar bits de uma flag está ativo;
  • Ativação de bits: uso de ou bit-a-bit para ativar bits uma flag;
  • Desativação de bits: uso de e bit-a-bit e não bit-a-bit para desativar bits de uma flag;
  • Inversão de de bits: uso de xou bit-a-bit para inverter bits ativos e inativos de uma flag.

O uso de bits no plural deve-se a possibilidade de combinar propriedades para operar com múltiplos bits em uma única operação.

A criação de propriedades usando deslocamento de bits para a esquerda garante que não exista conflito entre os bits escolhidos. Cada deslocamento gera uma potência diferente de 2, representando um bit específico da variável.

CorVermelho? (+1)Verde? (+2)Azul? (+4)Valor
PretoNãoNãoNão0
VermelhoSimNãoNão1
VerdeNãoSimNão2
AzulNãoNãoSim4
AmareloSimSimNão3
MagentaSimNãoSim5
CianoNãoNãoNão6
BrancoSimSimSim7

Como cada propriedade está em um bit de uma potência de 2 diferente, as combinações de cada propriedade com operações bit-a-bit resulta em valores únicos.

Máscaras de Bits (Bit Masks) para Extração de Valores

Para a verificação de bits, o valor usado para a extração de parte dos bits da variável analisada é chamado de máscara de bits (bit mask). O intuito da máscara é manter o valor dos bits de interesse e remover os demais bits (isto é, torná-los zero).

A área de Redes de Computadores provê uma aplicação clássica de máscaras de bits: roteamento entre domínios sem classes (mais conhecido pela sigla CIDR, do termo inglês Classless Inter-Domain Routing). Para identificar classes, usa-se uma máscara em um endereço IPv4 (Internet Protocol Version 4). Um endereço IPv4 possui 4 bytes (32 bits). Para facilitar a leitura, o endereço possui o formato w.x.y.z, sendo w, x, y e z números entre 0 a 255 (os valores que podem ser escritos com um byte sem sinal).

Antes de considerar CIDR, convém ilustrar o conceito de máscaras com um endereço IP. Por exemplo, um endereço IPv4 hipotético poderia ser 127.123.45.67. Um possível forma de armazenar os valores seria usar um vetor como [127, 123, 45, 67]. Entretanto, também é possível armazenar o endereço em um único valor inteiro de 32 bits. Para extrair cada parte do valor, usar-se-ia uma máscara e deslocamento de bits.

Para ilustrar a abordagem, o primeiro passo é escrever o endereço IP com um número inteiro. Uma forma simples de fazer isso é escrever o valor em hexadecimal. Para isso, converte-se cada número w, x, y e z do IP em um valor hexadecimal correspondente.

// 127.123.45.67
// endereço    = [127, 123, 45, 67]
// hexadecimal =   7F   7B  2D  43
let ipv4 = 0x7F7B2D43
console.log(ipv4)
# 127.123.45.67
# endereço    = [127, 123, 45, 67]
# hexadecimal =   7F   7B  2D  43
ipv4 = 0x7F7B2D43
print(ipv4)
-- 127.123.45.67
-- endereço    = {127, 123, 45, 67}
-- hexadecimal =   7F   7B  2D  43
local ipv4 = 0x7F7B2D43
print(ipv4)
extends Node

func _ready():
    # 127.123.45.67
    # endereço    = [127, 123, 45, 67]
    # hexadecimal =   7F   7B  2D  43
    var ipv4 = 0x7F7B2D43
    print(ipv4)

Com operações bit-a-bit, pode-se obter o mesmo valor 0x7F7B2D43 (2138778947) de uma forma diferente. Poder-se-ia usar deslocamento de bits para a esquerda para colocar cada parte do endereço nos bits corretos de ipv4.

// Bits:        31:24  23:16  15:8  7:0
let endereco = [  127,   123,   45,  67]
let ipv4 = endereco[0] << 24
ipv4 += endereco[1] << 16
ipv4 += endereco[2] << 8
ipv4 += endereco[3]
console.log(ipv4)
console.log(ipv4.toString(16))
# Bits:     31:24  23:16  15:8  7:0
endereco = [  127,   123,   45,  67]
ipv4  = endereco[0] << 24
ipv4 += endereco[1] << 16
ipv4 += endereco[2] << 8
ipv4 += endereco[3]
print(ipv4)
print(hex(ipv4))
-- Bits:          31:24  23:16  15:8  7:0
local endereco = {  127,   123,   45,  67}
local ipv4 = endereco[1] << 24
ipv4 = ipv4 + (endereco[2] << 16)
ipv4 = ipv4 + (endereco[3] << 8)
ipv4 = ipv4 + (endereco[4])
print(ipv4)
extends Node

func _ready():
    # Bits:         31:24  23:16  15:8  7:0
    var endereco = [  127,   123,   45,  67]
    var ipv4 = endereco[0] << 24
    ipv4 += endereco[1] << 16
    ipv4 += endereco[2] << 8
    ipv4 += endereco[3]
    print(ipv4)

O valor de ipv4 são iguais nas duas implementações. Para extrair o valor de cada parte do endereço IPv4, usa-se uma flag. Para se obter o valor da flag, usa-se o valor 1 nos bits que se deseja preservar e 0 nos que não são de interesse.

Assim, para extrair um valor de 1 byte armazenado em um valor de 4 bytes, deve-se criar a máscara. O valor máximo de um inteiro sem sinal de em 1 byte é 255. Embora prática habitual em redes, trabalhar com números decimais em máscaras pode ser confuso. O valor 255 em hexadecimal corresponde a 0xFF. Em binário, 0xFF corresponde a 0b11111111. Assim, é mais simples perceber o funcionamento da máscara: 0 & 1 == 0, assim como 1 & 1 == 1. Ou seja, x & 1 == x, caso x tenha um bit. A operação bit-a-bit repete esse processo para os quatro bits definidos.

Para extrair cada parte do endereço, monta-se a máscara correspondente às posições dos valores de interesse:

  • A última parte do endereço (z) está nos dígitos menos significativos. No caso, pode-se usar a máscara com valor 255 ou 0xFF;
  • A penúltima (y) está nos oito bits seguintes, ou seja, no segundo byte. Isso significa que o objetivo é ignorar os oito primeiros bits menos significativos e obter o valor dos próximos oito. Para isso, pode-se usar 0xFF00;
  • A segunda parte (x) está no segundo byte mais significativo. Deve-se, pois, ignorar os dois primeiros bytes menos significativos. Isso pode ser feito usando-se a máscara 0xFF0000;
  • A primeira parte (w) está nos dígitos mais significativos. O objetivo agora é ignorar os três bytes menos significativos. Ou seja, a máscara é 0xFF000000.

Para alinhar todos os valores de máscara, pode-se usar zeros à esquerda. O próximo exemplo alinha o valor de ipv4 com os valores das máscaras, para facilitar a identificação das partes extraídas.

let ipv4 =     0x7F7B2D43
let w = ipv4 & 0xFF000000
let x = ipv4 & 0x00FF0000
let y = ipv4 & 0x0000FF00
let z = ipv4 & 0x000000FF
console.log(w, x, y, z)
w >>= 24
x >>= 16
y >>= 8
z >>= 0
console.log(w, x, y, z)
ipv4 =     0x7F7B2D43
w = ipv4 & 0xFF000000
x = ipv4 & 0x00FF0000
y = ipv4 & 0x0000FF00
z = ipv4 & 0x000000FF
print(w, x, y, z)
w >>= 24
x >>= 16
y >>= 8
z >>= 0
print(w, x, y, z)
local ipv4 =     0x7F7B2D43
local w = ipv4 & 0xFF000000
local x = ipv4 & 0x00FF0000
local y = ipv4 & 0x0000FF00
local z = ipv4 & 0x000000FF
print(w, x, y, z)
w = w >> 24
x = x >> 16
y = y >> 8
z = z >> 0
print(w, x, y, z)
extends Node

func _ready():
    var ipv4 =     0x7F7B2D43
    var w = ipv4 & 0xFF000000
    var x = ipv4 & 0x00FF0000
    var y = ipv4 & 0x0000FF00
    var z = ipv4 & 0x000000FF
    printt(w, x, y, z)
    w >>= 24
    x >>= 16
    y >>= 8
    z >>= 0
    printt(w, x, y, z)

O uso de e bit-a-bit permite remover dígitos remanescentes que sejam indesejados. Ele é, portanto, um exemplo de aplicação de máscara para a extração do valor.

No código, deve-se notar que é preciso deslocar os valores para a direita para se obter os valores esperados. O número de casas é o mesmo valor usado para a criação do número; apenas inverte-se a direção do deslocamento. Isso motiva uma forma alternativa para a escrita do exemplo. Ao invés de modificar a máscara, pode-se realizar primeiro o deslocamento de bits para a direita para que todos os valores estejam no primeiro byte. Em seguida, aplica-se a máscara 0xFF para a extração do valor.

let ipv4 = 0x7F7B2D43
let endereco = []
endereco.push(ipv4 >> 24)
endereco.push(ipv4 >> 16 & 0xFF)
endereco.push(ipv4 >> 8  & 0xFF)
endereco.push(ipv4       & 0xFF)
console.log(endereco)
ipv4 = 0x7F7B2D43
endereco = []
endereco.append(ipv4 >> 24)
endereco.append(ipv4 >> 16 & 0xFF)
endereco.append(ipv4 >> 8  & 0xFF)
endereco.append(ipv4       & 0xFF)
print(endereco)
local ipv4 = 0x7F7B2D43
local endereco = {}
table.insert(endereco, ipv4 >> 24)
table.insert(endereco, ipv4 >> 16 & 0xFF)
table.insert(endereco, ipv4 >> 8  & 0xFF)
table.insert(endereco, ipv4       & 0xFF)
print(endereco[1] .. "." .. endereco[2] .. "." .. endereco[3] .. "." .. endereco[4])
extends Node

func _ready():
    var ipv4 = 0x7F7B2D43
    var endereco = []
    endereco.append(ipv4 >> 24)
    endereco.append(ipv4 >> 16 & 0xFF)
    endereco.append(ipv4 >> 8  & 0xFF)
    endereco.append(ipv4       & 0xFF)
    print(endereco)

Para um exemplo diferente, armazenou-se os valores em um vetor. Após a extração, endereco armazenará os valores [127, 123, 45, 67]. Isso ocorre porque as operações realizadas são o inverso das usadas para criar o valor. Quando se aplicou as operações inversas, extraiu-se quatro valores de um byte armazenados no valor de quatro bytes.

Calculadora CIDR

Para um segundo exemplo, pode-se retomar o conceito de CIDR e gerar máscaras para classes. O endereço localhost usado em Bibliotecas é um exemplo de rede privada. No contexto de redes, rede privada significa uma rede que não seja pública, isto é, uma rede interna que não é parte da Internet. O valor padrão de localhost é o endereço 127.0.0.1.

localhost pertence ao intervalo (range) de endereço 127.0.0.0/8, conhecido como rede privada loopback. No endereço, /8 significa que os primeiro 8 bits do endereço representam o endereço de rede. Os 24 bits restantes são usados para a identificação do host (algo como hospedeiro; a máquina ou dispositivo identificada).

A máscara para extrair os bits da rede um endereço de rede no formato w.x.y.z/8 é 255.0.0.0, conhecida como máscara de sub-rede (subnet mask). O valor é obtido adicionado-se 8 dígitos 1 em seqüência: 0b11111111 e convertendo o valor de binário para decimal. O 255 preserva o valor de w; cada um dos 0 remove os valores de x, y e z. Analogamente, a máscara para extrair os bits do host de um endereço no mesmo formato corresponde a 0.255.255.255, conhecida como wildcard mask (algo como máscara curinga, embora use-se o termo em inglês).

Assim, todo endereço de rede privada loopback inicia-se com o valor 127, e tem o formato 127.x.y.z. O mesmo princípio aplica-se para os demais intervalos. Por exemplo, 192.168.0.0/16 utiliza 16 bits para o endereço de rede e 16 bits para host. Se você já configurou um roteador em uma rede doméstica, o início do endereço possivelmente será familiar. No caso, a máscara de sub-rede 255.255.0.0 (novamente, pensar no valor hexadecimal ajuda a entender o valor). A máscara para hosts é 0.0.255.255.

Dos dois exemplos, pode-se notar que a máscara wildcard é a negação bit-a-bit da máscara de sub-rede.

Caso os parágrafos anteriores sejam confusos, o ideal é criar um programa para automatizar o processo. O próximo exemplo ilustra o processo para IPv4.

// 127.123.45.67
let endereco = [127, 123, 45, 67]
// 1--30.
let bits_cidr = 8

let mascara_subrede = [0, 0, 0, 0]
let bits_restantes = bits_cidr
let indice = 0
while (bits_restantes > 0) {
    let bits_considerados = Math.min(bits_restantes, 8)
    let mascara = 256 - Math.pow(2, 8 - bits_considerados)
    mascara_subrede[indice] = Math.floor(mascara)

    bits_restantes -= 8
    ++indice
}

let mascara_wildcard = [0, 0, 0, 0]
for (indice = 0; indice < 4; ++indice) {
    mascara_wildcard[indice] = ~mascara_subrede[indice] & 255
}

let endereco_rede = [0, 0, 0, 0]
let endereco_host = [0, 0, 0, 0]
for (indice = 0; indice < 4; ++indice) {
    endereco_rede[indice] = endereco[indice] & mascara_subrede[indice]
    endereco_host[indice] = endereco[indice] & mascara_wildcard[indice]
}

console.log(endereco_rede, "/", bits_cidr)
console.log("Máscara de sub-rede:", mascara_subrede)
console.log("Máscara wildcard:", mascara_wildcard)
console.log("Endereço de rede:", endereco_rede)
console.log("Endereço do host na rede:", endereco_host)
# 127.123.45.67
endereco = [127, 123, 45, 67]
# 1--30.
bits_cidr = 8

mascara_subrede = [0, 0, 0, 0]
bits_restantes = bits_cidr
indice = 0
while (bits_restantes > 0):
    bits_considerados = min(bits_restantes, 8)
    mascara = 256 - 2 ** (8 - bits_considerados)
    mascara_subrede[indice] = int(mascara)

    bits_restantes -= 8
    indice += 1

mascara_wildcard = [0, 0, 0, 0]
for indice in range(4):
    mascara_wildcard[indice] = ~mascara_subrede[indice] & 255

endereco_rede = [0, 0, 0, 0]
endereco_host = [0, 0, 0, 0]
for indice in range(4):
    endereco_rede[indice] = endereco[indice] & mascara_subrede[indice]
    endereco_host[indice] = endereco[indice] & mascara_wildcard[indice]

print(endereco_rede, "/", bits_cidr)
print("Máscara de sub-rede:", mascara_subrede)
print("Máscara wildcard:", mascara_wildcard)
print("Endereço de rede:", endereco_rede)
print("Endereço do host na rede:", endereco_host)
-- 127.123.45.67
local endereco = {127, 123, 45, 67}
-- 1--30.
local bits_cidr = 8

local mascara_subrede = {0, 0, 0, 0}
local bits_restantes = bits_cidr
local indice = 1
while (bits_restantes > 0) do
    local bits_considerados = math.min(bits_restantes, 8)
    local mascara = 256 - 2 ^ (8 - bits_considerados)
    mascara_subrede[indice] = math.floor(mascara)

    bits_restantes = bits_restantes - 8
    indice = indice + 1
end

local mascara_wildcard = {0, 0, 0, 0}
for indice = 1, 4 do
    mascara_wildcard[indice] = ~mascara_subrede[indice] & 255
end

local endereco_rede = {0, 0, 0, 0}
local endereco_host = {0, 0, 0, 0}
for indice = 1, 4 do
    endereco_rede[indice] = endereco[indice] & mascara_subrede[indice]
    endereco_host[indice] = endereco[indice] & mascara_wildcard[indice]
end

print(endereco_rede[1] .. "." ..
      endereco_rede[2] .. "." ..
      endereco_rede[3] .. "." ..
      endereco_rede[4], "/", bits_cidr)
print("Máscara de sub-rede:", mascara_subrede[1] .. "." ..
                              mascara_subrede[2] .. "." ..
                              mascara_subrede[3] .. "." ..
                              mascara_subrede[4])
print("Máscara wildcard:", mascara_wildcard[1] .. "." ..
                           mascara_wildcard[2] .. "." ..
                           mascara_wildcard[3] .. "." ..
                           mascara_wildcard[4])
print("Endereço de rede:", endereco_rede[1] .. "." ..
                           endereco_rede[2] .. "." ..
                           endereco_rede[3] .. "." ..
                           endereco_rede[4])
print("Endereço do host na rede:", endereco_host[1] .. "." ..
                                   endereco_host[2] .. "." ..
                                   endereco_host[3] .. "." ..
                                   endereco_host[4])
extends Node

func _init():
    # 127.123.45.67
    var endereco = [127, 123, 45, 67]
    # 1--30.
    var bits_cidr = 8

    var mascara_subrede = [0, 0, 0, 0]
    var bits_restantes = bits_cidr
    var indice = 0
    while (bits_restantes > 0):
        var bits_considerados = min(bits_restantes, 8)
        var mascara = 256 - pow(2, 8 - bits_considerados)
        mascara_subrede[indice] = int(mascara)

        bits_restantes -= 8
        indice += 1

    var mascara_wildcard = [0, 0, 0, 0]
    for indice_mascara in range(4):
        mascara_wildcard[indice_mascara] = ~mascara_subrede[indice_mascara] & 255

    var endereco_rede = [0, 0, 0, 0]
    var endereco_host = [0, 0, 0, 0]
    for indice_mascara in range(4):
        endereco_rede[indice_mascara] = endereco[indice_mascara] & mascara_subrede[indice_mascara]
        endereco_host[indice_mascara] = endereco[indice_mascara] & mascara_wildcard[indice_mascara]

    printt(endereco_rede, "/", bits_cidr)
    printt("Máscara de sub-rede:", mascara_subrede)
    printt("Máscara wildcard:", mascara_wildcard)
    printt("Endereço de rede:", endereco_rede)
    printt("Endereço do host na rede:", endereco_host)

No caso específico de redes, existem ferramentas para cálculos de endereço CIDR. Com alguns refinamentos, o exemplo anterior também poderia tornar-se sua própria ferramenta.

No exemplo, a geração de mascara_subrede poderia usar deslocamento de bits ao invés de potenciação. A solução também poderia usar um único valor inteiro de 32 bits para cada endereço e máscara, ao invés de um vetor com quatro números inteiros. Nesse caso, a escrita do IP seria mais simples em formato hexadecimal, como apresentado no início desta seção.

Conversão de Minúsculas e Maiúsculas em ASCII

Em Tipos de Dados, comentou-se sobre a tabela ASCII. Uma inspeção cuidadosa das letras maiúsculas de A até Z, e das minúsculas de a até z revela um padrão. A subtração do par correspondente de minúscula e maiúscula sempre resulta no valor 32. Por sinal, 32 é potência de 2 (). Suspeito.

Não é coincidência. A escolha foi feita por design, para facilitar conversões de caixa. Para converter uma letra maiúscula para minúscula em ASCII, basta somar 32 ao valor decimal do caractere maiúsculo. Para a conversão contrária de minúscula para maiúscula, basta subtrair 32 do valor decimal do caractere minúsculo.

Ao invés de uma soma, também é possível fazer uma simples operação bit-a-bit. Caso o valor do bit 5 seja 1, a letra será minúscula. Caso ele seja 0, a letra será maiúscula.

O valor decimal 32 corresponde a 0x20 em hexadecimal.

let mensagem = "Ola, Meu Nome e Franco!"
let maiusculas = ""
for (let caractere of mensagem) {
    let caractere_ascii = caractere.charCodeAt(0)
    if ((caractere_ascii >= "a".charCodeAt(0)) && (caractere_ascii <= "z".charCodeAt(0))) {
        caractere_ascii &= ~0x20
    }

    maiusculas += String.fromCharCode(caractere_ascii)
}

let minusculas = ""
for (let caractere of mensagem) {
    let caractere_ascii = caractere.charCodeAt(0)
    if ((caractere_ascii >= "A".charCodeAt(0)) && (caractere_ascii <= "Z".charCodeAt(0))) {
        caractere_ascii |= 0x20
    }

    minusculas += String.fromCharCode(caractere_ascii)
}

console.log(maiusculas)
console.log(minusculas)
mensagem = "Ola, Meu Nome e Franco!"
maiusculas = ""
for caractere in mensagem:
    caractere_ascii = ord(caractere)
    if ((caractere_ascii >= ord("a")) and (caractere_ascii <= ord("z"))):
        caractere_ascii &= ~0x20

    maiusculas += chr(caractere_ascii)

minusculas = ""
for caractere in mensagem:
    caractere_ascii = ord(caractere)
    if ((caractere_ascii >= ord("A")) and (caractere_ascii <= ord("Z"))):
        caractere_ascii |= 0x20

    minusculas += chr(caractere_ascii)

print(maiusculas)
print(minusculas)
local mensagem = "Ola, Meu Nome e Franco!"
local maiusculas = ""
for caractere in string.gmatch(mensagem, ".") do
    local caractere_ascii = string.byte(caractere)
    if ((caractere_ascii >= string.byte("a")) and (caractere_ascii <= string.byte("z"))) then
        caractere_ascii = caractere_ascii & ~0x20
    end

    maiusculas = maiusculas .. string.char(caractere_ascii)
end

local minusculas = ""
for caractere in string.gmatch(mensagem, ".") do
    local caractere_ascii = string.byte(caractere)
    if ((caractere_ascii >= string.byte("A")) and (caractere_ascii <= string.byte("Z"))) then
        caractere_ascii = caractere_ascii | 0x20
    end

    minusculas = minusculas .. string.char(caractere_ascii)
end

print(maiusculas)
print(minusculas)
extends Node

func _init():
    var mensagem = "Ola, Meu Nome e Franco!"
    var maiusculas = ""
    for caractere in mensagem:
        var caractere_ascii = ord(caractere)
        if ((caractere_ascii >= "a".to_ascii()[0]) and (caractere_ascii <= "z".to_ascii()[0])):
            caractere_ascii &= ~0x20

        maiusculas += PoolByteArray([caractere_ascii]).get_string_from_ascii()

    var minusculas = ""
    for caractere in mensagem:
        var caractere_ascii = ord(caractere)
        if ((caractere_ascii >= "A".to_ascii()[0]) and (caractere_ascii <= "Z".to_ascii()[0])):
            caractere_ascii |= 0x20

        minusculas += PoolByteArray([caractere_ascii]).get_string_from_ascii()

    print(maiusculas)
    print(minusculas)

As funções para conversão de um caractere de uma cadeia de caracteres para um inteiro codificado em ASCII foram usadas previamente. Por exemplo, em Arquivos e Serialização (Marshalling) para a criação de um arquivo de som em formato WAVE. Desta vez, usou-se também a função contrária, para conversão de um inteiro codificado em ASCII para uma cadeia de caracteres. Em JavaScript, usou-se String.fromCharCode() (documentação). Em Python, usou-se chr() (documentação). Em Lua, usou-se string.byte() (documentação). Em GDScript, usou-se get_string_from_ascii() (documentação).

Para praticar, é possível melhorar o exemplo. Algumas possibilidades incluem:

  1. Extraindo-se o número mágico 0x20 para uma constante;
  2. Calculando-se os valores inteiros para "a", "z", "A" e "Z" e armazenando-o em uma constante.

Para verificar se um caractere é maiúsculo ou minúsculo, também é possível verificar se o bit com o valor 32 possui valor 1.

Novos Itens para Seu Inventário

Ferramentas:

  • Calculadoras para redes de computadores.

Habilidades:

  • Uso de operações bit-a-bit.

Conceitos:

  • Operações bit-a-bit:
    • Ou bit-a-bit;
    • E bit-a-bit;
    • Não bit-a-bit;
    • Xou bit-a-bit;
    • Deslocamento de bits para a esquerda;
    • Deslocamento de bits para a direita.
  • Flags;
  • Máscaras de bits (bit masks).

Recursos de programação:

  • Operações bit-a-bit.

Pratique

  1. Os sistemas de cores RGB e RGBA (mencionados previamente em Arquivos e Serialização (Marshalling)) podem definir cores como inteiros de 4 bytes. Em RGB, as cores possuem 256 valores de vermelho, verde e azul. Em RGBA, as cores possuem 256 valores de vermelho, verde, azul e alfa (transparência).

    Com operações bit-a-bit, você pode criar cores com valores inteiros. Duas ordens populares para representação de cores no número inteiro são:

    1. ARGB32: alfa, vermelho, verde, azul;
    2. RGBA32: vermelho, verde, azul, alfa.

    Para mais informações, pode-se consultar a Wikipedia.

  2. Utilize um e bit-a-bit para determinar se um número inteiro é par ou ímpar. Dica: basta verificar um único bit.

  3. É possível implementar um algoritmo de troca usando xou bit-a-bit que não requer o uso de uma variável temporária. Caso os números sejam diferentes, isso pode ser feito com uma seqüência de atribuições e três operações xou. Tente descobrir como.

  4. No exercício anterior, os números devem ser diferentes porque x ^ x sempre resulta zero. De fato, essa é uma forma rápida de zerar o valor de uma variável em algumas implementações da linguagem assembly. Crie uma subrotina zera_com_xou() para testar a técnica.

  5. Utilize uma operação bit-a-bit para verificar se um caractere em ASCII é uma letra maiúscula. Tente também verificar se a letra é minúscula com uma variação bit-a-bit da primeira solução.

Próximos Passos

Operações bit-a-bit marcam o final dos tópicos introdutórios de Aprenda a Programar. Como mencionado em Arquivos e Serialização (Marshalling), o planejamento original seria terminar em arquivos. Este tópico, Entrada em Linha de Comando e Bibliotecas ilustram recursos adicionais de linguagens de programação.

O autor acredita que, neste momento, o material cobre os principais recursos fornecidos por qualquer linguagem de programação. Em linguagens de baixo nível, existem duas omissões: ponteiros e alocação dinâmica de memória. Esses tópicos requerem o uso de uma linguagem como a linguagem C ou a linguagem C++. Eles são importantes para melhorar as habilidades de programação, embora abstraídos em linguagens de programação de alto nível.

Além de ponteiros, técnicas para depuração e testes também merecem tópicos próprios. Os dois temas foram abordados ao longo de alguns tópicos. Assim, em um primeiro momento, talvez o autor crie um tópico como placeholder com links para menções anteriores.

Assim, os tópicos listados serão abordados em algum momento no futuro, assim como outros paradigmas de programação. Em particular, Programação Orientada a Objetos e Programação Funcional são bons paradigmas para a continuidade dos estudos.

Entretanto, o plano atual é iniciar um material mais interativo e focado em simulações. O material iniciado em Aprenda Programação: Introdução e terminado neste tópico é teórico. Convém, pois, explorar algo mais prático. Ao invés de restringir-se apenas a um terminal, é hora de explorar recursos multimídia.

  1. Introdução;
  2. Ponto de entrada e estrutura de programa;
  3. Saída (para console ou terminal);
  4. Tipos de dados;
  5. Variáveis e constantes;
  6. Entrada (para console ou terminal);
  7. Aritmética e Matemática básica;
  8. Operações relacionais e comparações;
  9. Operações lógicas e Álgebra Booleana;
  10. Estruturas de condição (ou condicionais ou de seleção);
  11. Subrotinas: funções e procedimentos;
  12. Estruturas de repetição (ou laços ou loops);
  13. Vetores (arrays), cadeias de caracteres (strings), coleções (collections) e estruturas de dados;
  14. Registros (structs ou records);
  15. Arquivos e serialização (serialization ou marshalling);
  16. Bibliotecas;
  17. Entrada em linha de comando;
  18. Operações bit-a-bit (bitwise operations);
  19. Testes e depuração.
  • Informática
  • Programação
  • Iniciante
  • Pensamento Computacional
  • Aprenda a Programar
  • Python
  • Lua
  • Javascript
  • Godot
  • Gdscript