Trilhas
/
Python
Python
/
Programa
/
Operadores bit a bit
Op

Operadores bit a bit em Python

{one: "1 exercício", many: "%{count} de exercícios", other: "%{count} exercícios"}

Sobre Operadores bit a bit

Lá no nível do hardware, os transistores só podem estar ligados ou desligados: dois estados que tradicionalmente representamos com 1 e 0. Esses são os binary digits, abreviados como bits. Entender bits e binary é especialmente importante para quem programa sistemas e trabalha com linguagens de baixo nível.

No entanto, durante a maior parte da história da computação, a prioridade da programação foi encontrar formas cada vez mais sofisticadas de abstrair essa realidade binária. Em Python (e em muitas outras linguagens de programação de alto nível), trabalhamos com int, float, string e outros tipos definidos, incluindo até formatos de áudio e vídeo. Deixamos os detalhes internos do Python cuidarem de (eventualmente) traduzir tudo para bits.

Ainda assim, usar operadores bit a bit e operações bit a bit pode, às vezes, trazer vantagens significativas de velocidade e eficiência de memória, mesmo em uma linguagem de alto nível como Python.

Inserindo e exibindo números binários

Como era de esperar, o Python interage com o usuário usando números decimais, mas quem programa pode mudar esse padrão. Na verdade, o Python aceita sem problemas um int nos formatos binary, hexadecimal ou octal, e realiza operações matemáticas entre eles tranquilamente. Para mais detalhes, você pode revisar o conceito binary-octal-hexadecimal.

Números binários são inseridos com o prefixo 0b, assim como 0x pode ser usado para hexadecimal (números hex são uma forma concisa de representar grupos de 4 bits) e oct pode ser usado para números octais.

Há várias formas de converter inteiros em strings binárias, que variam conforme incluam ou não o prefixo 0b e conforme suportem ou não preenchimento à esquerda com zeros:

# Binary entry.
>>> 0b10111
23

# Converting an int display to binary string, with prefix.
>>> bin(23)  
'0b10111'

>>> number = 23

# Binary without prefix, padded to 8 digits.
>>> format(number, '08b')  
'00010111'

# Same format, but using an f-string.
>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex" 
'23 in decimal is 00010111 in binary and 17 in hex'

Bitwise Logic

No conceito bools, discutimos os operadores lógicos and, or e not usados com valores Boolean (True e False). As mesmas regras de lógica se aplicam ao trabalhar com bits.

No entanto, os equivalentes bit a bit dos operadores lógicos & (and), | (or), ~ (not) e ^ (XOR) são aplicados a cada bit de uma representação binária, tratando 1 como True ("ligado") e 0 como False ("desligado"). Um exemplo com o & bit a bit pode deixar isso mais claro:

>>> x = 0b01100110
>>> y = 0b00101010

>>> format(x & y, '08b')
'00100010'

Somente as posições com um 1 em ambos os números de entrada são definidas como 1 na saída.

O & bit a bit costuma ser usado para isolar bits individuais em um conjunto compactado de valores True/False, como configurações que o usuário ajusta em um aplicativo. Isso permite que o valor de bits individuais controle a lógica do programa:

>>> number = 0b0110
>>> number & 0b0001 > 0
False

>>> number & 0b0010 > 0
True

Para o | bit a bit (or), define-se um 1 na saída se houver um 1 em qualquer uma das entradas:

>>> x = 0b01100110
>>> y = 0b00101010

>>> format(x | y, '08b')
'01101110'

Com o operador ^ para o ou exclusivo bit a bit (xor), define-se um 1 se ele aparecer em uma ou outra das entradas, mas não em ambas. Esse símbolo pode parecer familiar do conceito sets, onde ele é usado para a diferença simétrica de set, que é a mesma coisa que xor aplicado a conjuntos. Se o xor ^ parecer estranho, saiba que essa é de longe a operação mais comum em criptografia.

>>> x = 0b01100110
>>> y = 0b00101010

>>> format(x ^ y, '08b')
'01001100'

Por fim, há o operador ~ (o caractere til), que é um not bit a bit que recebe uma única entrada e inverte todos os bits, o que pode não ser o resultado que você esperava! Cada 1 na representação vira 0, e vice-versa. Veja a seção abaixo para mais detalhes.

Números negativos e representação binária

Na representação decimal, distinguimos números positivos e negativos usando um sinal + ou - à esquerda dos dígitos. Usar esses símbolos no nível binário se mostrou ineficiente para a computação digital e trouxe o problema de que +0 não é o mesmo que -0.

Em vez de usar - e +, todos os computadores modernos usam a representação de two's complement para números negativos, até o nível do chip de silício. Isso significa que todos os bits são invertidos e um número é interpretado como negativo se o bit mais à esquerda (também chamado de "bit mais significativo", ou MSB) for 1. Números positivos têm MSB igual a 0. Essa representação tem a vantagem de ter apenas uma versão do zero, de modo que quem programa não precisa lidar com -0 e +0.

Essa forma de representar números negativos e positivos cria uma complicação para o Python: não há conceitos de inteiros de tamanho finito como int32 ou int64 internamente na linguagem principal. No Python 'moderno', os ints têm tamanho ilimitado (limitado apenas pela capacidade do hardware), e um número negativo ou com bits invertidos tem uma quantidade (teoricamente) infinita de 1s à esquerda, assim como um número positivo tem 0s ilimitados.

Isso torna difícil dar um exemplo útil do bitwise not:

>>> x = 0b01100110
>>> format(x, '08b')
'01100110'

# This is a negative binary (not twos-complement display).
>>> format(~x, '08b')
'-1100111'  

 # Decimal representation.
>>> x
102

# Using the Bitwise not, with an unintuitive result.
>>> ~x
-103

Isto não é o 0b10011001 que veríamos em linguagens com inteiros de tamanho fixo.

O operador ~ só funciona como esperado com tipos de byte ou inteiro sem sinal, ou com tipos de inteiro de tamanho fixo. Esses tipos numéricos são suportados em pacotes de terceiros como NumPy, pandas e sympy, mas não no Python principal.

Na prática, quem programa em Python costuma usar &, |, ^ e os operadores de deslocamento descritos abaixo somente com números positivos. Operações bit a bit com números negativos são bem menos comuns. Uma técnica é somar 2**32 (or 1 << 32) a um valor negativo para tornar um int sem sinal, mas isso fica difícil de gerenciar. Outra estratégia é trabalhar com o módulo ctypes e usar tipos de inteiro no estilo C, mas isso é igualmente complicado.

Shift operators

O operador de deslocamento à esquerda x << y move todos os bits de x em y posições para a esquerda, preenchendo as novas lacunas com zeros. Repare que isso é aritmeticamente idêntico a multiplicar um número por (2**y).

O operador de deslocamento à direita x >> y faz o oposto. Isso é aritmeticamente idêntico à divisão inteira x // (2**y).

Tenha em mente a seção anterior sobre números negativos e as armadilhas de deslocá-los em Python.

>>> x = 8
>>> format(x, '08b')
'00001000'

# A left bit shift. 
>>> x << 2  
32

>>> format(x << 2, '08b')
'00100000'

# A right bit shift. 
>>> format(x >> 2, '08b')
'00000010'
Editar via GitHub O link abre em uma nova janela ou aba