Registrarse

[Otros] Operadores Bit a Bit - Uso y Funcionalidades

Kaktus

Miembro insignia
Miembro insignia


Si habéis estado trasteando lo suficiente con cualquier proyecto de decompilación, habréis visto líneas como esta

C:
structPtr->palettes = ~1 & ~(0x10000 << IndexOfSpritePaletteTag(0));
Y probablemente, no sepáis que hacen exactamente los carácteres ~, & o <<

Así que explicaré siendo lo más claro posible que hacen todos los operadores bit a bit, e intentaré no dar nada por hecho.

En primer lugar, es fundamental entender que en C hay distintos tipos de operadores, por ejemplo, los operadores aritméticos 5 + 4, x * 2, x/2. operadores de asignación x = 5, i += 2, n /= 2. Entre muchos otros, si tenéis curiosidad, os dejo aquí una imagen con todos ellos.

1614876662067.png

Bien, pues si miramos la imagen, veremos que uno de los tipos de operadores son los bit a bit (o bitwise operator en inglés). Estos operadores harán operaciones bit a bit, aunque paséis un valor en decimal. En el juego, se suelen usar mucho para almacenar "conjuntos de posibilidades" en muy poco espacio.

Me explico, imaginad que tenemos un sistema de misiones secundarias en el juego, y en total, tenemos 30 misiones secundarias. Y tal y como funciona el sistema de misiones secundarias, podemos tener varias de estas misiones a la vez activas, y además, no siguen un orden, es decir, que aunque tú no hayas completado la primera misión secundaria que te ofrece el juego, puedes activar y completar la dos, y la tres, y la treinta.

¡Eso es! Creamos treinta variables booleanas, y cada variable estará asignada a si la misión se ha activado o no, ¿no...? Bueno, esa sería la respuesta correcta si no existieran los bitwise operator (bueno, en realidad también es válido, pero no es optimo, y es más difícil trabajar con 30 nombres de variables distinas). En su lugar, podríamos tomar un u32 (que si no sabéis lo que es, es un entero con 32 bits), y que cada bit, representara lo que iba a representar ese bool.

Para que lo veáis de forma más gráfica:

C:
bool8 Mision1 = FALSE;
bool8 Mision2 = FALSE;
//26 líneas más tarde...
bool8 Mision29 = FALSE;
bool8 Mision30 = FALSE;
C:
u32 misionesActivas = 0
/*
Teniendo en cuenta que hemos creado un unsigned de 32 bits,
se podría decir que si estuviera representado en bits, tendríamos
00000000 00000000 00000000 00000000
Así que, si quisieramos indicar que hemos activado la misión 1,
podríamos dejar todos estos bits, así
00000000 00000000 00000000 00000001

Y si quisieramos activar la 1 y la 2, así
00000000 00000000 00000000 00000011

Y si quisieramos la 4 y la 2, pero la 1 y la 3 no
00000000 00000000 00000000 00001010

Y así sucesivamente
*/

ATENCIÓN: Esta explicación está enfocada a los tipos de datos unsigned (u8, u16, u32...), visto que esta teoría no aplica para los signed (s8, s16, s32...), si quieres saber porqué, puedes leer el siguiente spoiler.

Esto se debe a que los signed, usan el bit más significativo, es decir

C:
/*
1110 0010
^
|
Este
*/
Para indicar si el número es positivo (bit a 0) o negativo (bit a 1), y estas operaciones pierden todo el sentido, visto que están enfocadas para ser aplicadas sobre números binarios sin formato (hay varias formas de representar los números con signo en binario).

Y ahora, la explicación de cada uno de los operadores con sus respectivos ejemplos.

NOTA: Para hacer más fácil de entender la explicación, a partir de ahora haré los ejemplos con solo 4 bits.

Este operador devolverá 1, sólo si hacemos la operación 1 & 1, en cualquier otro caso (0 & 1, 1 & 0 o 0 & 0) el resultado de la operación será 0.

1614880087315.png


Por tanto, si tenemos dos variables de cuatro bits cada una, tal que

C:
u8 x = 13; //0000 1101
u8 y = 7; //0000 1101
Y realizamos la operación

C:
u8 z = x & y; // 0000 1101 & 0000 1101
El primer bit de x, se operará con el bit de y, y el resultado será el primer bit de z. El segundo bit de x se operará con el segundo de y, y el resultado será el segundo bit de z.

Si lo colocamos como hacíamos las sumas en primaria, se ve bastante más claro (cada operación y resultado irá de un color distinto).

1614881361335.png
En este caso, el operador es similar al || que conocemos, es decir, es un OR bit a bit, por lo que el único caso en el que el resultado será 0, es cuando hagamos 0 | 0.

1614881790937.png


De nuevo, con las variables de antes

C:
u8 x = 13; //0000 1101
u8 y = 7; //0000 1101

u8 z = x & y; // 0000 1101 & 0000 1101
Obtendremos el siguiente resultado

1614881881615.png
Este es el operador XOR, o Exclusive OR. En resumen, devuelve 1 siempre que los bits a comparar sean distintos:

1614883602949.png


Y para los ejemplos anteriores

C:
u8 x = 13; //0000 1101
u8 y = 7; //0000 1101

u8 z = x & y; // 0000 1101 ^ 0000 1101
Tenemos este resultado.

1614883685787.png
En este caso tenemos el operador Shift Left, con este lo que haremos, será mover todos los bits de la variable x, y veces hacia la izquierda (los bits que entran nuevos son siempre cero). Por ejemplo

C:
u8 x = 3; //0000 0011
u8 y = x << 1; // 0000 0110 = 6
u8 z = y << 2; // 0001 1000 = 24

/*Y los más avistados os habréis dado
cuenta que si tenemos x << y, es lo mismo
que hacer x*(2^y) en decimal (siempre y
cuando no se pierdan bits a 1 por la
izquierda, porque se estaría perdiendo
información)

Otro ejemplo:*/

x = 13; // 0000 1101
y = x << 5; // 1010 0000
Ahora el Right Left, sip, exáctamente lo mismo que el anterior, pero ahora los movemos a la izquierda, y la formula dado x >> y sería x/(2^y)

C:
u8 x = 12; //0000 1100
u8 y = x >> 1; // 0000 0110 = 6
u8 z = y >> 2; // 0000 0001 = 1
Venga va, que este último es el más facilito. Operador negación bit a bit. Como dice el nombre, niega todas los bits.
Ejemplitosss:
C:
u8 x = 122; //0111 1010
u8 y = ~x; //1000 0101

u8 a = 71; // 0100 0111
u8 b = ~a; // 1011 1000

//Easy, ¿eh?

Para reforzar esto que acabamos de leer, vamos a intentar descifrar ese código tan raro que había al principio, y que pensábamos que nunca entenderíamos.

C:
structPtr->palettes = ~1 & ~(0x10000 << IndexOfSpritePaletteTag(0));
Aclarar que palettes de la estructura structPtr es un u32, por lo que trabajaremos con 32 bits, y que IndexOfSpritePaletteTag(u16 tag) devuelve un u8, aunque si te metes en la función, se puede comprobar que el valor máximo en decimal que puede devolver es 15.

Vamos a ir paso a paso, y como siempre, empezando por los paréntesis, tenemos que hay que desplazar la cantidad que devuelva IndexOfSpritePaletteTag los bits de 0x10000. Digamos que devuelve 6, pues tendríamos

C:
//0x1000 = 00000000 00000001 00000000 00000000
(Lo pongo en u32 porque es el tipo de dato con el que se guardará).

Al tener que moverlo 6 veces hacia la izquierda, se nos quedaría como
C:
(0x10000 << 6) = 00000000 01000000 00000000 00000000
Bien, ya tenemos el valor de dentro del paréntesis, ahora hay que negar todos sus bits, por lo que 00000000 01000000 00000000 00000000 pasaría a ser 11111111 10111111 11111111 11111111

Y por el otro lado, tenemos ~1, que diréis, bueno, para negar un 1, pues directamente pongo 0, pero NO estamos negando el 1 como un único bit, si no negando el 1 como si fuera un u32 (porque ese es el tipo de dato de palette, que es lo que va antes del igual), es decir, que en realidad estamos negando 00000000 00000000 00000000 00000001, lo que se quedaría como 11111111 11111111 11111111 11111110

Bien, ahora sí, ya podemos hacer el AND bit a bit, que si estáis un poquito espabilados, sabréis que el resultado serían todos los bits de ~(0x10000 << IndexOfSpritePaletteTag(0)) excepto el último, que será 0 haya lo que haya. ¿Por qué? Porque todos los bits de ~1 están a 0, menos el menos significativo (el que está más a la derecha), es decir, que el resultado final sería

C:
//Imaginando que IndexOfSpritePaletteTag(u16 tag) devolviera 6
structPtr->palettes = ~1 & ~(0x10000 << IndexOfSpritePaletteTag(0)); //  11111111 10111111 11111111 11111110

Y por último, volveremos al ejemplo de las misiones secundarias, para que veáis lo útil que pueden ser estos operadores.

Imaginad que estamos en nuestra interfaz, y queremos recorrer los 30 estados de las misiones, para cargar por pantalla las que estén activadas.

C:
if(Mision1)
    LoadMission(1);
if(Mision2)
    LoadMission(2);
if(Mision3)
    LoadMission(3);
if(Mision4)
    LoadMission(4);
if(Mision5)
    LoadMission(5);
if(Mision6)
    LoadMission(6);
if(Mision7)
    LoadMission(7);
if(Mision8)
    LoadMission(8);
if(Mision9)
    LoadMission(9);
if(Mision10)
    LoadMission(10);
if(Mision11)
    LoadMission(11);
if(Mision12)
    LoadMission(12);
if(Mision13)
    LoadMission(13);
if(Mision14)
    LoadMission(14);
if(Mision15)
    LoadMission(15);
if(Mision16)
    LoadMission(16);
if(Mision17)
    LoadMission(17);
if(Mision18)
    LoadMission(18);
if(Mision19)
    LoadMission(19);
if(Mision20)
    LoadMission(20);
if(Mision21)
    LoadMission(21);
if(Mision22)
    LoadMission(22);
if(Mision23)
    LoadMission(23);
if(Mision24)
    LoadMission(24);
if(Mision25)
    LoadMission(25);
if(Mision26)
    LoadMission(26);
if(Mision27)
    LoadMission(27);
if(Mision28)
    LoadMission(28);
if(Mision29)
    LoadMission(29);
if(Mision30)
    LoadMission(30);
C:
u8 i = 1;
//Siempre que sea mayor que 0, se sigue ejecutando
//Es decir, siempre que tenga algún bit a 1 :D
while(misionesActivas)
{
    if(1 & misionesActivas)
        LoadMission(i);
    misionesActivas = misionesActivas >> 1;
    i += 1;
}

//Así de fácil y bonito :'D

 

Adjuntos

Última edición:

KevinXDE

Usuario mítico
Estuve toqueteando hace tiempo las personalidades de los Pokémon y usaban algunos de esos operadores, me quedé un poco loco y tuve que descifrarlo por mi cuenta xD, ahora entiendo bastante mejor como funcionan. Trabajar con números de bits tan grandes la verdad es que puede ser muy lioso si encima no conoces estos operadores, pero bueno, con la práctica se pueden llegar a hacer cosas la mar de útiles gracias a ellos
 

Kaktus

Miembro insignia
Miembro insignia
Estuve toqueteando hace tiempo las personalidades de los Pokémon y usaban algunos de esos operadores, me quedé un poco loco y tuve que descifrarlo por mi cuenta xD, ahora entiendo bastante mejor como funcionan. Trabajar con números de bits tan grandes la verdad es que puede ser muy lioso si encima no conoces estos operadores, pero bueno, con la práctica se pueden llegar a hacer cosas la mar de útiles gracias a ellos
Precisamente por eso es importante traer al foro documentación de este tipo. Son conceptos teóricos muy importantes que van a ahorrar horas de deducciones e investigación, además, tenemos la información resumida y enfocada al decomp, y no dependemos de que lo que digan por X foro de por ahí se pueda aplicar a decomp o no :p
 

Caco5400

Héroe de WaH
Men este tuto me vino de perlas.
Sinceramente nunca le preste demasiada atención a este tipo de operadores ya que no le encontraba utilidad. Pero con estos acabo de ver que pueden ser muy útiles para hacer cosillas guapas en decomp , ahorrando mucho espacio.
¡Sigue así cruck!
 

Dark_Tyranitar

Desarrollador Web
tablas de verdad vengan a mi!!!!!!!!!!!!!!!!!!!!!!!!!! me gusta aprender C con Pokémon, estan muy bien explicadas en tu post, directo a marcadores :p
 
Arriba