Operación y uso bitwise

Considere este código:

x = 1 # 0001 x << 2 # Shift left 2 bits: 0100 # Result: 4 x | 2 # Bitwise OR: 0011 # Result: 3 x & 1 # Bitwise AND: 0001 # Result: 1 

Puedo entender los operadores aritméticos en Python (y otros idiomas), pero nunca entendí muy bien los operadores ‘a nivel de bit’. En el ejemplo anterior (de un libro de Python), entiendo el cambio a la izquierda pero no los otros dos.

Además, ¿para qué se utilizan realmente los operadores de bitwise? Apreciaría algunos ejemplos.

Los operadores bitwise son operadores que trabajan con valores de múltiples bits, pero conceptualmente un bit a la vez.

  • AND es 1 solo si sus dos entradas son 1, de lo contrario es 0.
  • OR es 1 si una o sus dos entradas son 1, de lo contrario es 0.
  • XOR es 1 solo si exactamente una de sus entradas es 1, de lo contrario es 0.
  • NOT es 1 solo si su entrada es 0, de lo contrario es 0.

Estos a menudo se pueden mostrar mejor como tablas de verdad. Las posibilidades de entrada están en la parte superior e izquierda, el bit resultante es uno de los cuatro valores (dos en el caso de NO, ya que solo tiene una entrada) que se muestran en la intersección de las entradas.

 AND | 0 1 OR | 0 1 XOR | 0 1 NOT | 0 1 ----+----- ---+---- ----+---- ----+---- 0 | 0 0 0 | 0 1 0 | 0 1 | 1 0 1 | 0 1 1 | 1 1 1 | 1 0 

Un ejemplo es si solo desea los 4 bits más bajos de un número entero, Y Y con 15 (binario 1111), entonces:

  201: 1100 1001 AND 15: 0000 1111 ------------------ IS 9 0000 1001 

Los bits cero en 15 en ese caso actúan efectivamente como un filtro, forzando que los bits en el resultado también sean cero.

Además, >> y << menudo se incluyen como operadores bit a bit, y "desplazan" un valor, respectivamente, a la derecha y a la izquierda en un cierto número de bits, eliminando los bits que rodan del extremo al que se está desplazando y alimentando Cero bits en el otro extremo.

Así por ejemplo:

 1001 0101 >> 2 gives 0010 0101 1111 1111 << 4 gives 1111 0000 

Tenga en cuenta que el desplazamiento a la izquierda en Python es inusual ya que no utiliza un ancho fijo donde se descartan los bits, mientras que muchos idiomas usan un ancho fijo basado en el tipo de datos, Python simplemente expande el ancho para satisfacer los bits adicionales. Para obtener el comportamiento de descarte en Python, puede seguir un desplazamiento a la izquierda con un bit a bit and como en un valor de 8 bits desplazando a la izquierda cuatro bits:

 bits8 = (bits8 << 4) & 255 

Teniendo esto en cuenta, otro ejemplo de operadores bitwise es que si tiene dos valores de 4 bits que desea empaquetar en uno de 8 bits, puede usar los tres operadores ( left-shift , and y or ):

 packed_val = ((val1 & 15) << 4) | (val2 & 15) 
  • La operación & 15 se asegurará de que ambos valores solo tengan los 4 bits más bajos.
  • El << 4 es un desplazamiento a la izquierda de 4 bits para mover val1 a los 4 bits superiores de un valor de 8 bits.
  • El | simplemente combina estos dos juntos.

Si val1 es 7 y val2 es 4:

  val1 val2 ==== ==== & 15 (and) xxxx-0111 xxxx-0100 & 15 << 4 (left) 0111-0000 | | | +-------+-------+ | | (or) 0111-0100 

Un uso típico:

| se utiliza para establecer un cierto bit a 1

& se usa para probar o borrar un poco

  • Establezca un bit (donde n es el número de bit y 0 es el bit menos significativo):

    unsigned char a |= (1 << n);

  • Borrar un poco:

    unsigned char b &= ~(1 << n);

  • Alternar un poco:

    unsigned char c ^= (1 << n);

  • Prueba un poco:

    unsigned char e = d & (1 << n);

Tomemos como ejemplo el caso de su lista:

x | 2 x | 2 se utiliza para establecer el bit 1 de x a 1

x & 1 se usa para probar si el bit 0 de x es 1 o 0

¿Para qué se utilizan realmente los operadores bitwise? Apreciaría algunos ejemplos.

Uno de los usos más comunes de las operaciones bitwise es para analizar los colores hexadecimales.

Por ejemplo, aquí hay una función de Python que acepta una cadena como #FF09BE y devuelve una tupla de sus valores de rojo, verde y azul.

 def hexToRgb(value): # Convert string to hexadecimal number (base 16) num = (int(value.lstrip("#"), 16)) # Shift 16 bits to the right, and then binary AND to obtain 8 bits representing red r = ((num >> 16) & 0xFF) # Shift 8 bits to the right, and then binary AND to obtain 8 bits representing green g = ((num >> 8) & 0xFF) # Simply binary AND to obtain 8 bits representing blue b = (num & 0xFF) return (r, g, b) 

Sé que hay maneras más eficientes de lograr esto, pero creo que este es un ejemplo realmente conciso que ilustra tanto los cambios como las operaciones booleanas a nivel de bits.

Creo que la segunda parte de la pregunta:

Además, ¿para qué se utilizan realmente los operadores de bitwise? Apreciaría algunos ejemplos.

Ha sido abordado sólo parcialmente. Estos son mis dos centavos en ese asunto.

Las operaciones bitwise en los lenguajes de progtwigción juegan un papel fundamental cuando se trata de una gran cantidad de aplicaciones. Casi toda la computación de bajo nivel debe realizarse utilizando este tipo de operaciones.

En todas las aplicaciones que necesitan enviar datos entre dos nodos, tales como:

  • Red de computadoras;

  • Aplicaciones de telecomunicaciones (celulares, comunicaciones satelitales, etc).

En la capa de nivel inferior de la comunicación, los datos generalmente se envían en lo que se denomina marcos . Las ttwigs son solo cadenas de bytes que se envían a través de un canal físico. Estos marcos generalmente contienen los datos reales más algunos otros campos (codificados en bytes) que forman parte de lo que se llama el encabezado . El encabezado generalmente contiene bytes que codifican cierta información relacionada con el estado de la comunicación (por ejemplo, con indicadores (bits)), contadores de ttwig, códigos de detección de corrección y error, etc. Para obtener los datos transmitidos en una ttwig y para construir el Marcos para enviar datos, necesitará para operaciones seguras a nivel de bits.

En general, cuando se trata de ese tipo de aplicaciones, hay una API disponible para que no tenga que lidiar con todos esos detalles. Por ejemplo, todos los lenguajes de progtwigción modernos proporcionan bibliotecas para conexiones de socket, por lo que en realidad no es necesario crear los marcos de comunicación TCP / IP. Pero piense en la buena gente que programó esas API para usted, tenían que lidiar con la construcción de marcos con seguridad; el uso de todo tipo de operaciones bitwise para ir y venir de la comunicación de bajo nivel al nivel superior.

Como ejemplo concreto, imagine que alguien le proporciona un archivo que contiene datos sin procesar que fueron capturados directamente por el hardware de telecomunicaciones. En este caso, para encontrar los marcos, necesitará leer los bytes en bruto en el archivo e intentar encontrar algún tipo de palabras de sincronización, escaneando los datos bit a bit. Después de identificar las palabras de sincronización, deberá obtener los marcos reales, y CAMBIARLOS si es necesario (y eso es solo el comienzo de la historia) para obtener los datos reales que se están transmitiendo.

Otra familia de aplicaciones de bajo nivel muy diferente es cuando necesita controlar el hardware utilizando algunos puertos (antiguos), como los puertos paralelos y los puertos serie. Estos puertos se controlan estableciendo algunos bytes, y cada bit de esos bytes tiene un significado específico, en términos de instrucciones, para ese puerto (consulte, por ejemplo, http://en.wikipedia.org/wiki/Parallel_port ). Si desea crear un software que haga algo con ese hardware, necesitará operaciones a nivel de bits para traducir las instrucciones que desea ejecutar a los bytes que comprende el puerto.

Por ejemplo, si tiene algunos botones físicos conectados al puerto paralelo para controlar algún otro dispositivo, esta es una línea de código que puede encontrar en la aplicación de software:

 read = ((read ^ 0x80) >> 4) & 0x0f; 

Espero que esto contribuya.

Espero que esto aclare esos dos:

 x | 2 0001 //x 0010 //2 0011 //result = 3 

 x & 1 0001 //x 0001 //1 0001 //result = 1 

Piense en 0 como falso y 1 como verdadero. Luego, a nivel de bits y (&) y o (|) funcionan igual que regular y y o excepto que hacen todos los bits en el valor a la vez. Por lo general, los verá usados ​​para las banderas si tiene 30 opciones que se pueden configurar (por ejemplo, como estilos de dibujo en una ventana) no quiere tener que pasar 30 valores booleanos separados para configurar o desarmar cada uno, así que use | para combinar las opciones en un solo valor y luego usa y para verificar si cada opción está configurada. Este estilo de paso de bandera es muy utilizado por OpenGL. Como cada bit es una bandera separada, obtiene valores de bandera en potencias de dos (también conocido como números que solo tienen un bit establecido) 1 (2 ^ 0) 2 (2 ^ 1) 4 (2 ^ 2) 8 (2 ^ 3) la potencia de dos le indica qué bit se establece si el indicador está activado.

También tenga en cuenta 2 = 10, por lo que x | 2 es 110 (6) no 111 (7) si ninguno de los bits se superpone (lo que es cierto en este caso) | Actúa como adición.

No lo vi mencionado anteriormente, pero también verá que algunas personas usan el desplazamiento hacia la izquierda y hacia la derecha para las operaciones aritméticas. Un desplazamiento a la izquierda por x equivale a multiplicar por 2 ^ x (siempre que no se desborde) y un desplazamiento a la derecha equivale a dividir por 2 ^ x.

Recientemente, he visto personas que utilizan x << 1 y x >> 1 para doblar y reducir a la mitad, aunque no estoy seguro de si solo están tratando de ser inteligentes o si realmente existe una clara ventaja sobre los operadores normales.

Este ejemplo le mostrará las operaciones para los cuatro valores de 2 bits:

 10 | 12 1010 #decimal 10 1100 #decimal 12 1110 #result = 14 

 10 & 12 1010 #decimal 10 1100 #decimal 12 1000 #result = 8 

Aquí hay un ejemplo de uso:

 x = raw_input('Enter a number:') print 'x is %s.' % ('even', 'odd')[x&1] 

Conjuntos

Los conjuntos se pueden combinar utilizando operaciones matemáticas.

  • El operador sindical | combina dos conjuntos para formar uno nuevo que contiene elementos en cualquiera de los dos.
  • El operador de intersección & obtiene elementos solo en ambos.
  • El operador de diferencia - obtiene elementos en el primer conjunto pero no en el segundo.
  • El operador de diferencia simétrica ^ obtiene elementos en cualquiera de los conjuntos, pero no en ambos.

Inténtalo tú mismo:

 first = {1, 2, 3, 4, 5, 6} second = {4, 5, 6, 7, 8, 9} print(first | second) print(first & second) print(first - second) print(second - first) print(first ^ second) 

Resultado:

 {1, 2, 3, 4, 5, 6, 7, 8, 9} {4, 5, 6} {1, 2, 3} {8, 9, 7} {1, 2, 3, 7, 8, 9} 

Otro caso de uso común es la manipulación / prueba de permisos de archivos. Consulte el módulo de estadísticas de Python: http://docs.python.org/library/stat.html .

Por ejemplo, para comparar los permisos de un archivo con un conjunto de permisos deseado, podría hacer algo como:

 import os import stat #Get the actual mode of a file mode = os.stat('file.txt').st_mode #File should be a regular file, readable and writable by its owner #Each permission value has a single 'on' bit. Use bitwise or to combine #them. desired_mode = stat.S_IFREG|stat.S_IRUSR|stat.S_IWUSR #check for exact match: mode == desired_mode #check for at least one bit matching: bool(mode & desired_mode) #check for at least one bit 'on' in one, and not in the other: bool(mode ^ desired_mode) #check that all bits from desired_mode are set in mode, but I don't care about # other bits. not bool((mode^desired_mode)&desired_mode) 

Los resultados son booleanos, porque solo me importa la verdad o la falsedad, pero sería un ejercicio valioso para imprimir los valores de bin () para cada uno.

Las representaciones de bits de enteros se utilizan a menudo en la computación científica para representar matrices de información verdadera / falsa porque una operación a nivel de bits es mucho más rápida que la iteración a través de una matriz de valores booleanos. (Los idiomas de nivel superior pueden usar la idea de una matriz de bits).

Un ejemplo agradable y bastante simple de esto es la solución general al juego de Nim. Eche un vistazo al código de Python en la página de Wikipedia . Hace un uso intensivo de bitwise exclusivo o, ^ .

Puede haber una mejor manera de encontrar dónde está un elemento de matriz entre dos valores, pero como muestra este ejemplo, el & trabaja aquí, mientras que no lo hace.

 import numpy as np a=np.array([1.2, 2.3, 3.4]) np.where((a>2) and (a<3)) #Result: Value Error np.where((a>2) & (a<3)) #Result: (array([1]),) 

No lo vi mencionado. Este ejemplo le mostrará la operación decimal (-) para valores de 2 bits: AB (solo si A contiene B)

esta operación es necesaria cuando tenemos un verbo en nuestro progtwig que representa bits. a veces necesitamos agregar bits (como arriba) y otras veces debemos eliminar bits (si el verbo contiene entonces)

 111 #decimal 7 - 100 #decimal 4 -------------- 011 #decimal 3 

con python: 7 y ~ 4 = 3 (elimine de 7 los bits que representan 4)

 001 #decimal 1 - 100 #decimal 4 -------------- 001 #decimal 1 

con python: 1 & ~ 4 = 1 (elimine de 1 los bits que representan 4; en este caso 1 no es ‘contiene’ 4).

Si bien la manipulación de bits de un entero es útil, a menudo para los protocolos de red, que pueden especificarse hasta el bit, se puede requerir la manipulación de secuencias de bytes más largas (que no se convierten fácilmente en un entero). En este caso, es útil emplear la biblioteca de cadenas de bits que permite realizar operaciones a nivel de bits en los datos; por ejemplo, se puede importar la cadena ‘ABCDEFGHIJKLMNOPQ’ como una cadena o como hexadecimal y desplazamiento de bits (o realizar otras operaciones bitwise):

 >>> import bitstring >>> bitstring.BitArray(bytes='ABCDEFGHIJKLMNOPQ') << 4 BitArray('0x142434445464748494a4b4c4d4e4f50510') >>> bitstring.BitArray(hex='0x4142434445464748494a4b4c4d4e4f5051') << 4 BitArray('0x142434445464748494a4b4c4d4e4f50510') 

los siguientes operadores bitwise: & , | , ^ y ~ devuelven valores (según su entrada) de la misma manera que las puertas lógicas afectan a las señales. Podrías usarlos para emular circuitos.

Para voltear bits (es decir, complemento / inversión de 1) puede hacer lo siguiente:

Dado que el valor ExORed con todos los resultados de 1s en inversión, para un ancho de bit dado, puede utilizar ExOR para invertirlos.

 In Binary a=1010 --> this is 0xA or decimal 10 then c = 1111 ^ a = 0101 --> this is 0xF or decimal 15 ----------------- In Python a=10 b=15 c = a ^ b --> 0101 print(bin(c)) # gives '0b101'