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));
~
, &
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.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
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).
C:
/*
1110 0010
^
|
Este
*/
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
Por tanto, si tenemos dos variables de cuatro bits cada una, tal que
Y realizamos la operación
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).
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.Por tanto, si tenemos dos variables de cuatro bits cada una, tal que
C:
u8 x = 13; //0000 1101
u8 y = 7; //0000 1101
C:
u8 z = x & y; // 0000 1101 & 0000 1101
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).
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
De nuevo, con las variables de antes
Obtendremos el siguiente resultado
0 | 0
.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
Este es el operador XOR, o Exclusive OR. En resumen, devuelve 1 siempre que los bits a comparar sean distintos:
Y para los ejemplos anteriores
Tenemos este resultado.
Y para los ejemplos anteriores
C:
u8 x = 13; //0000 1101
u8 y = 7; //0000 1101
u8 z = x & y; // 0000 1101 ^ 0000 1101
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:
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));
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
Al tener que moverlo 6 veces hacia la izquierda, se nos quedaría como
C:
(0x10000 << 6) = 00000000 01000000 00000000 00000000
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
-
541 bytes Visitas: 202
Última edición: