Entendiendo los operadores de Bitwise

Los operadores bitwise son aquellos operadores de apariencia extraña que pueden parecer difíciles de entender ... ¡pero ya no más! Este artículo fácil de seguir le ayudará a comprender qué son y cómo usarlos, con un par de ejemplos prácticos para mostrarle cuándo y por qué los necesita..


Introducción

Los operadores de bitwise son operadores (como +, *, &&, etc.) que operan en ints y uints a nivel binario. Esto significa que miran directamente a los dígitos binarios o bits de un entero. Todo esto suena aterrador, pero en realidad los operadores a nivel de bits son bastante fáciles de usar y también muy útiles!

Sin embargo, es importante que entiendas los números binarios y los números hexadecimales. Si no lo haces, por favor revisa este artículo, ¡realmente te ayudará! A continuación se muestra una pequeña aplicación que te permitirá probar los diferentes operadores bitwise..

No se preocupe si aún no entiende lo que está pasando, todo estará claro pronto ...


Reconociendo a los Operadores de Bitwise

Echemos un vistazo a los operadores bitwise que suministra AS3. Muchos otros idiomas son bastante similares (por ejemplo, JavaScript y Java tienen operadores prácticamente idénticos):

  • & (bitwise Y)
  • | (bit a bit)
  • ~ (en modo de bit no)
  • ^ (XOR a nivel de bit)
  • << (bitwise left shift)
  • >> (desplazamiento a la derecha en modo bit)
  • >>> (desplazamiento a la derecha en modo bit sin signo)
  • & = (asignación AND a nivel de bits)
  • | = (asignación OR en modo bit)
  • ^ = (asignación XOR a nivel de bits)
  • <<= (bitwise left shift and assignment)
  • >> = (desplazamiento a la derecha en modo bit y asignación)
  • >>> = (asignación y desplazamiento a la derecha en modo bit sin signo)

Hay algunas cosas que debería tomar de esto: primero, algunos operadores bitwise se parecen a los operadores que ha usado antes (& vs. &&, | vs. ||). Esto es porque ellos son algo similar.

En segundo lugar, la mayoría de los operadores de bitwise vienen con una formulario de asignación compuesto de ellos mismos. Esto es lo mismo que cómo puedes usar + y + =, - y - =, etc..


El operador

Arriba primero: el operador bit a bit Y, &. Un rápido heads-up sin embargo: normalmente, ints y uints ocupa 4 bytes o 32 bits de espacio. Esto significa que cada En t o uint Se almacena como 32 dígitos binarios. Por el bien de este tutorial, a veces pretendemos que ints y uints solo ocupa 1 byte y solo tiene 8 dígitos binarios.

El operador & compara cada dígito binario de dos enteros y devuelve un nuevo entero, con un 1 donde ambos números tenían un 1 y un 0 en cualquier otro lugar. Un diagrama vale más que mil palabras, así que aquí hay uno para aclarar las cosas. Representa hacer 37 y 23, que es igual a 5.

Observe cómo se comparan cada dígito binario de 37 y 23, y el resultado tiene un 1 donde ambos, 37 y 23 tuvieron un 1, y el resultado tiene un 0 de lo contrario.

Una forma común de pensar en dígitos binarios es como cierto o falso. Es decir, 1 es equivalente a cierto y 0 es equivalente a falso. Esto hace que el operador tenga más sentido..

Cuando comparamos dos booleanos, normalmente hacemos boolean1 && boolean2. Esa expresión solo es verdadera si ambas boolean1 y boolean2 son verdaderas. Del mismo modo, integer1 & integer2 es equivalente, ya que el operador & solo genera un 1 cuando ambos dígitos binarios de nuestros dos enteros son 1.

Aquí hay una tabla que representa esa idea:

Un pequeño y agradable uso del operador & es verificar si un número es par o impar. Para los enteros, simplemente podemos verificar el bit más a la derecha (también llamado el bit menos significativo) para determinar si el entero es impar o par. Esto se debe a que al convertir a la base 10, el bit más a la derecha representa 20 o 1. Cuando el bit más a la derecha es 1, sabemos que nuestro número es impar ya que estamos agregando 1 a un grupo de poderes de dos que siempre serán parejos. Cuando el bit más a la derecha es 0, sabemos que nuestro número será par, ya que simplemente consiste en sumar un montón de números pares.

Aquí hay un ejemplo:

 var randInt: int = int (Math.random () * 1000); if (randInt & 1) trace ("número impar");  else trace ("número par"); 

En mi computadora, este método era un 66% más rápido que usar randInt% 2 para verificar los números pares e impares. Eso es un gran aumento de rendimiento!


El | Operador

El siguiente es el operador OR a nivel de bits, |. Como habrás adivinado, el | operador es para el || operador como operador & es para el operador &&. El | el operador compara cada dígito binario entre dos enteros y devuelve un 1 si ya sea de ellos son 1. Una vez más, esto es similar a la || operador con booleanos.

Veamos el mismo ejemplo que antes, excepto que ahora usamos el | operador en lugar del operador &. Ahora estamos haciendo 37 | 23 que es igual a 55:


Banderas: Un uso del & y | Los operadores

Podemos aprovechar el & y | operadores que nos permiten pasar múltiples opciones a una función en un solo En t.

Echemos un vistazo a una posible situación. Estamos construyendo una clase de ventana emergente. En la parte inferior de la misma, podemos tener un botón Sí, No, Aceptar o Cancelar o cualquier combinación de estos: ¿cómo debemos hacer esto? Aquí está la manera difícil:

 public class PopupWindow extiende Sprite // Variables, Constructor, etc… public static void showPopup (yesButton: Boolean, noButton: Boolean, okayButton: Boolean, cancelButton: Boolean) if (yesButton) // add YES button if (noButton ) // no agregar el botón // y así sucesivamente para el resto de los botones

¿Es esto horrible? No. Pero es malo, si eres un programador, tener que buscar el orden de los argumentos cada vez que llamas a la función. También es molesto, por ejemplo, si solo desea mostrar el botón Cancelar, debe configurar todos los demás Booleanos a falso.

Usemos lo que aprendimos sobre y y | para hacer una mejor solución:

 la clase pública PopupWindow extiende Sprite public static const YES: int = 1; constante estática pública NO: int = 2; const estática pública OKAY: int = 4; public static const CANCEL: int = 8; público static void showPopup (buttons: int) if (buttons & YES) // agregar el botón YES if (buttons & NO) // agregar el botón NO

¿Cómo llamaría un programador a la función para que se muestren el botón Sí, el botón No y el botón Cancelar? Me gusta esto:

 PopupWindow.show (PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL);

¿Que esta pasando? Es importante tener en cuenta que nuestras constantes en el segundo ejemplo son todas las potencias de dos. Entonces, si observamos sus formas binarias, notaremos que todos tienen un dígito igual a 1 y el resto igual a 0. De hecho, cada uno tiene un dígito diferente igual a 1. Esto significa que no importa cómo combinemos Con |, cada combinación nos dará un número único. Mirándolo de una manera diferente, nuestro resultado | La declaración será un número binario con un 1 donde nuestras opciones tenían un 1.

Para nuestro ejemplo actual tenemos PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL que es equivalente a 1 | 2 | 8 el cual reescrito en binario es 00000001 | 00000010 | 00001000 lo que nos da un resultado de 00001011.

Ahora en nuestra mostrar ventanas emergentes() función, usamos y para comprobar qué opciones se pasaron. Por ejemplo, cuando comprobamos botones y SÍ, todos los bits en SÍ son iguales a 0, excepto el más a la derecha. Por lo tanto, básicamente estamos verificando si el bit más a la derecha en los botones es un 1 o no. Si esto es, botones y SÍ no será igual a 0 y cualquier cosa en la sentencia if será ejecutada. Por el contrario, si el bit más a la derecha en los botones es 0, botones y SÍ será igual a 0, y la sentencia if no se ejecutará.


El operador ~

El operador NO bit a bit es ligeramente diferente a los dos que hemos visto hasta ahora. En lugar de tomar un entero a cada lado, toma un entero solo después de él. Esto es como el! Operador, y, como es lógico, hace algo similar. De hecho, igual que! lanza un booleano desde cierto a falso o viceversa, el operador ~ invierte cada dígito binario en un entero: de 0 a 1 y de 1 a 0:

Un ejemplo rápido. Digamos que tenemos el número entero 37, o 00100101. ~ 37 es entonces 11011010. ¿Cuál es el valor base 10 de esto? Bien…


Complemento de dos, uint vs. En t, y más!

Ahora comienza la diversión! Vamos a echar un vistazo más de cerca a los números binarios en una computadora. Comencemos con el uint. Como se mencionó anteriormente, un uint Normalmente tiene 4 bytes o 32 bits de longitud, lo que significa que tiene 32 dígitos binarios. Esto es fácil de entender: para obtener el valor de base 10, simplemente convertimos el número a base 10 regularmente. Siempre obtendremos un número positivo.

Pero ¿qué tal el En t? También usa 32 bits, pero ¿cómo almacena los números negativos? Si supuso que el primer dígito se usa para almacenar el cartel, está en el camino correcto. Echemos un vistazo a la complemento de dos Sistema de almacenamiento de números binarios. Si bien no vamos a entrar en todos los detalles aquí, se utiliza un sistema de complemento a dos porque facilita la aritmética binaria.

Para encontrar el complemento de dos de un número binario, simplemente volteamos todos los bits (es decir, hacemos lo que hace el operador ~) y agregamos uno al resultado. Probemos esto una vez:

Luego definimos nuestro resultado como el valor -37. ¿Por qué hacer este proceso complicado y no solo voltear el primer bit y llamar a eso -37?

Bueno, tomemos una expresión simple. 37 + -37. Todos sabemos que esto debería ser igual a 0, y cuando agregamos el 37 a su complemento de dos, eso es lo que obtenemos:

Tenga en cuenta que dado que nuestros enteros solo tienen ocho dígitos binarios, el 1 en nuestro resultado se elimina, y terminamos con 0, como deberíamos.

Para recapitular, para encontrar el negativo de un número, simplemente tomamos el complemento de sus dos. Podemos hacer esto invirtiendo todos los bits y agregando uno.

¿Quieres probar esto tú mismo? Añadir traza (~ 37 + 1); a un archivo AS3, luego compilarlo y ejecutarlo. Verás que se imprime -37, como debería ser.

También hay un pequeño atajo para hacer esto a mano: a partir de la derecha, trabaje hacia la izquierda hasta que alcance un 1. Voltee todos los bits a la izquierda de este primero 1.

Cuando estamos viendo un número binario firmado (en otras palabras, uno que puede ser negativo, un En t No un uint), podemos mirar el dígito de la izquierda para indicar si es negativo o positivo. Si es un 0, entonces el número es positivo y podemos convertir a la base 10 simplemente calculando su valor base 10. Si el bit de más a la izquierda es un 1, entonces el número es negativo, entonces tomamos el complemento de los dos del número para obtener su valor positivo y luego simplemente agregamos un signo negativo.

Por ejemplo, si tenemos 11110010, sabemos que es un número negativo. Podemos encontrar el complemento de dos al pasar todos los dígitos a la izquierda del extremo derecho 1, lo que nos da 00001110. Esto es igual a 13, por lo que sabemos que 11110010 es igual a -13.


El operador

Regresamos a los operadores bitwise, y el siguiente es el operador XOR bitwise. No hay un operador booleano equivalente a este.

El operador ^ es similar al de & y | operadores en que se necesita una En t o uint a ambos lados. Cuando está calculando el número resultante, nuevamente compara los dígitos binarios de estos números. Si uno u otro es un 1, insertará un 1 en el resultado; de lo contrario, insertará un 0. Aquí es donde el nombre XOR, o "exclusivo o" proviene de.

Echemos un vistazo a nuestro ejemplo habitual:

El operador ^ tiene usos, es especialmente bueno para alternar dígitos binarios, pero no cubriremos ninguna aplicación práctica en este artículo..


los << Operator

Ahora estamos en los operadores de cambio de bits, específicamente el operador de desplazamiento a la izquierda de modo de bits aquí.

Estos funcionan un poco diferente que antes. En lugar de comparar dos enteros como &, | y ^ did, estos operadores cambian un entero. En el lado izquierdo del operador se encuentra el número entero que se está cambiando, y a la derecha, cuánto hay que desplazar. Así, por ejemplo, 37 << 3 is shifting the number 37 to the left by 3 places. Of course, we're working with the binary representation of 37.

Veamos este ejemplo (recuerde, solo vamos a pretender que los enteros solo tienen 8 bits en lugar de 32). Aquí tenemos el número 37 sentado en su bonito bloque de memoria de 8 bits de ancho.

Muy bien, vamos a deslizar todos los dígitos hacia la izquierda por 3, como 37 << 3 haría:

Pero ahora tenemos un pequeño problema: ¿qué hacemos con los 3 bits abiertos de memoria desde donde movimos los dígitos??

¡Por supuesto! Cualquier lugar vacío es reemplazado por 0s. Terminamos con 00101000. Y eso es todo lo que hay a la izquierda. Tenga en cuenta que Flash siempre piensa que el resultado de un desplazamiento a la izquierda es un En t, No un uint. Así que si necesitas un uint por alguna razón, tendrás que lanzarlo a un uint Me gusta esto: uint (37 << 3). Este casting no cambia realmente ninguna de las informaciones binarias, solo la forma en que Flash las interpreta (la cosa del complemento de las dos).

Una característica interesante del cambio de bits de la izquierda es que es lo mismo que multiplicar un número por dos para obtener el cambio en la potencia de montaje. Asi que, 37 << 3 == 37 * Math.pow(2,3) == 37 * 8. Si puedes usar el turno de la izquierda en lugar de Math.pow, Verás un gran aumento de rendimiento..

Es posible que haya notado que el número binario con el que terminamos no era igual a 37 * 8. Esto se debe a nuestro uso de solo 8 bits de memoria para enteros; Si lo intentas en ActionScript, obtendrás el resultado correcto. O inténtalo con la demo en la parte superior de la página.!


El >> operador

Ahora que entendemos el cambio de bits de la izquierda, el siguiente, el cambio de bits de la derecha, será fácil. Todo se desliza hacia la derecha la cantidad que especificamos. La única pequeña diferencia es con lo que se llenan los bits vacíos..

Si estamos empezando con un número negativo (un número binario donde el bit más a la izquierda es un 1), todos los espacios vacíos se llenan con un 1. Si estamos empezando con un número positivo (donde el bit más a la izquierda, o el más significativo) bit, es un 0), entonces todos los espacios vacíos se llenan con un 0. Una vez más, todo esto se remonta al complemento de dos.

Si bien esto suena complicado, básicamente solo conserva el signo del número con el que comenzamos. Asi que -8 >> 2 == -2 mientras 8 >> 2 == 2. Yo recomendaría probarlos en papel usted mismo.

Ya que >> es lo opuesto a <<, it's not surprising that shifting a number to the right is the same as dividing it by 2 to the power of shiftAmount. You may have noticed this from the example above. Again, if you can use this to avoid calling Math.pow, obtendrá un aumento significativo del rendimiento.


El operador >>>

Nuestro operador final a nivel de bits es el desplazamiento a la derecha sin signo a nivel de bits. Esto es muy similar al desplazamiento a la derecha regular en modo bit, excepto que todos los bits vacíos de la izquierda se rellenan con 0s. Esto significa que el resultado de este operador es siempre un número entero positivo y siempre trata el número entero que se está desplazando como un número entero sin signo. No veremos un ejemplo de esto en esta sección, pero veremos su uso muy pronto..


Usando operadores de Bitwise para trabajar con colores

Uno de los usos más prácticos de los operadores bitwise en Actionscript 3 es trabajar con colores, que normalmente se almacenan como uints.

El formato estándar para los colores es escribirlos en hexadecimal: 0xAARRGGBB: cada letra representa un dígito hexadecimal. Aquí, los dos primeros dígitos hexadecimales, que son equivalentes a los primeros ocho dígitos binarios, representan nuestro alfa, o transparencia. Los siguientes ocho bits representan la cantidad de rojo en nuestro color (por lo tanto, un número entero de 0 a 255), los siguientes ocho la cantidad de verde y los ocho últimos representan la cantidad de azul en nuestro color..

Sin operadores bitwise, es extremadamente difícil trabajar con colores en este formato, pero con ellos es fácil!

Desafío 1: Encuentra la cantidad de azul en un color: Usando el operador &, intente encontrar la cantidad de azul en un color arbitrario.

 función pública findBlueComponent (color: uint): uint // ¡Su código aquí! 

Necesitamos una forma de 'borrar' o enmascarar todos los demás datos en color y solo queda el componente azul a la izquierda. ¡Esto es fácil, en realidad! Si tomamos color y 0x000000FF - o, escrito más simplemente, color y 0xFF - Terminamos solo con el componente azul.

Como puede ver en la descripción anterior y en la descripción del operador &, cualquier dígito binario y 0 siempre será igual a 0, mientras que cualquier dígito binario y 1 mantendrá su valor. Entonces, si enmascaramos nuestro color con 0xFF, que solo tiene 1s donde se encuentra el componente azul de nuestro color, terminamos solo con el componente azul.

Reto 2: Encuentra la cantidad de rojo en un color: Utilizando dos operadores bitwise, intenta encontrar la cantidad de rojo en un color arbitrario.

 función pública findRedComponent (color: uint): uint // ¡Su código aquí! 

En realidad tenemos dos soluciones a este problema. Uno seria return (color & 0xFF0000) >> 16; y el otro seria retorno (color >> 16) & 0xFF;

Esto es muy similar al desafío 1, excepto que tenemos que cambiar nuestra respuesta en algún momento.

Reto 3: encontrar la transparencia de un color.: Utilizando solo un operador bit a bit, intente encontrar el alfa de un color (un entero de 0 a 255).

 función pública findAlphaComponent (color: uint): uint // ¡Su código aquí! 

Este es un toque más complicado. Tenemos que tener cuidado con el operador de turno derecho que elegimos. Porque estamos trabajando con los dígitos más a la izquierda de una uint, queremos usar el operador >>> Por lo tanto, nuestra respuesta simplemente es color de retorno >>> 24;.

Final Challenge: Crea un color a partir de sus componentes.: Utilizando la << and | operators, take the components of a color and merge them in to one uint.

 función pública createColor (a: uint, r: uint, g: uint, b: uint): uint // ¡Tu código aquí! 

Aquí, tenemos que cambiar cada componente a su posición correcta, y luego fusionarlos. Queremos que Flash lo trate como un entero sin signo, por lo que lo convertimos en un uint: devuelve uint ((a << 24) | (r << 16) | (g << 8) | b);


Operadores de compuestos

Puede que hayas notado que me he olvidado de explicar los operadores compuestos a modo de bits. Imagina que tenemos un entero x. Entonces, x = x y 0xFF es lo mismo que x & = 0xFF, x = x | 256 es lo mismo que x | = 256, Y así sucesivamente para el resto de los operadores compuestos..


Conclusión

¡Gracias por leer este artículo! Espero que ahora entienda a los operadores de bitwise y pueda utilizarlos en su código AS3 (¡o en muchos otros idiomas!). Como siempre, si tiene alguna pregunta o comentario, por favor déjelos a continuación..