1. Información general
Los operadores se utilizan en el lenguaje Java para operar con datos y variables.
En este tutorial, exploraremos los operadores bit a bit y cómo funcionan en Java.
2. Operadores bit a bit
Los operadores bit a bit funcionan con dígitos binarios o bits de valores de entrada. Podemos aplicarlos a los tipos de enteros: long, int, short, char y byte.
Antes de explorar los diferentes operadores bit a bit, primero comprendamos cómo funcionan.
Los operadores bit a bit trabajan en un equivalente binario de números decimales y realizan operaciones en ellos bit a bit según el operador dado:
- Primero, los operandos se convierten a su representación binaria
- A continuación, se aplica el operador a cada número binario y se calcula el resultado
- Finalmente, el resultado se convierte de nuevo a su representación decimal.
Entendamos con un ejemplo; tomemos dos enteros:
int value1 = 6; int value2 = 5;
A continuación, apliquemos un operador OR bit a bit en estos números:
int result = 6 | 5;
Para realizar esta operación, primero se calculará la representación binaria de estos números:
Binary number of value1 = 0110 Binary number of value2 = 0101
Luego, la operación se aplicará a cada bit. El resultado devuelve un nuevo número binario:
0110 0101 ----- 0111
Finalmente, el resultado 0111 se volverá a convertir a decimal, que es igual a 7 :
result : 7
Los operadores bit a bit se clasifican además como operadores lógicos bit a bit y operadores de desplazamiento bit a bit. Repasemos ahora cada tipo.
3. Operadores lógicos bit a bit
Los operadores lógicos bit a bit son AND (&), OR (|), XOR (^) y NOT (~).
3.1. O bit a bit (|)
El operador OR compara cada dígito binario de dos enteros y devuelve 1 si cualquiera de ellos es 1.
Esto es similar al || operador lógico usado con booleanos. Cuando se comparan dos valores booleanos, el resultado es verdadero si alguno de ellos es verdadero. De manera similar, la salida es 1 cuando cualquiera de ellos es 1.
Vimos un ejemplo de este operador en la sección anterior:
@Test public void givenTwoIntegers_whenOrOperator_thenNewDecimalNumber() value2; assertEquals(7, result);
Veamos la representación binaria de esta operación:
0110 0101 ----- 0111
Aquí, podemos ver que usar OR, 0 y 0 resultará en 0, mientras que cualquier combinación con al menos un 1 resultará en 1.
3.2. Y (&) bit a bit
El operador AND compara cada dígito binario de dos enteros y devuelve 1 si ambos son 1; de lo contrario, devuelve 0.
Esto es similar al operador && con valores booleanos . Cuando los valores de dos booleanos son verdaderos, el resultado de una operación && es verdadero.
Usemos el mismo ejemplo que el anterior, excepto que ahora usamos el operador & en lugar de | operador:
@Test public void givenTwoIntegers_whenAndOperator_thenNewDecimalNumber() { int value1 = 6; int value2 = 5; int result = value1 & value2; assertEquals(4, result); }
Veamos también la representación binaria de esta operación:
0110 0101 ----- 0100
0100 es 4 en decimal, por lo tanto, el resultado es:
result : 4
3.3. XOR bit a bit (^)
El operador XOR compara cada dígito binario de dos enteros y devuelve 1 si ambos bits comparados son diferentes. Esto significa que si los bits de ambos enteros son 1 o 0, el resultado será 0; de lo contrario, el resultado será 1:
@Test public void givenTwoIntegers_whenXorOperator_thenNewDecimalNumber() { int value1 = 6; int value2 = 5; int result = value1 ^ value2; assertEquals(3, result); }
Y la representación binaria:
0110 0101 ----- 0011
0011 es 3 en decimal, por lo tanto, el resultado es:
result : 3
3.4. COMPLEMENTO bit a bit (~)
El operador Bitwise Not o Complement simplemente significa la negación de cada bit del valor de entrada. Solo toma un entero y es equivalente a! operador.
Este operador cambia cada dígito binario del entero, lo que significa que todo 0 se convierte en 1 y todo 1 se convierte en 0. ¡El! El operador funciona de manera similar para valores booleanos : invierte los valores booleanos de verdadero a falso y viceversa.
Ahora entendamos con un ejemplo cómo encontrar el complemento de un número decimal.
Hagamos el complemento de value1 = 6:
@Test public void givenOneInteger_whenNotOperator_thenNewDecimalNumber() { int value1 = 6; int result = ~value1; assertEquals(-7, result); }
El valor en binario es:
value1 = 0000 0110
Al aplicar el operador de complemento, el resultado será:
0000 0110 -> 1111 1001
This is the one’s complement of the decimal number 6. And since the first (leftmost) bit is 1 in binary, it means that the sign is negative for the number that is stored.
Now, since the numbers are stored as 2’s complement, first we need to find its 2’s complement and then convert the resultant binary number into a decimal number:
1111 1001 -> 0000 0110 + 1 -> 0000 0111
Finally, 0000 0111 is 7 in decimal. Since the sign bit was 1 as mentioned above, therefore the resulting answer is:
result : -7
3.5. Bitwise Operator Table
Let's summarize the result of the operators we've seen to so far in a comparison table:
A B A|B A&B A^B ~A 0 0 0 0 0 1 1 0 1 0 1 0 0 1 1 0 1 1 1 1 1 1 0 0
4. Bitwise Shift Operators
Binary shift operators shift all the bits of the input value either to the left or right based on the shift operator.
Let's see the syntax for these operators:
value
The left side of the expression is the integer that is shifted, and the right side of the expression denotes the number of times that it has to be shifted.
Bitwise shift operators are further classified as bitwise left and bitwise right shift operators.
4.1. Signed Left Shift [<<]
The left shift operator shifts the bits to the left by the number of times specified by the right side of the operand. After the left shift, the empty space in the right is filled with 0.
Another important point to note is that shifting a number by one is equivalent to multiplying it by 2, or, in general, left shifting a number by n positions is equivalent to multiplication by 2^n.
Let's take the value 12 as the input value.
Now, we will move it by 2 places to the left (12 <<2) and see what will be the final result.
The binary equivalent of 12 is 00001100. After shifting to the left by 2 places, the result is 00110000, which is equivalent to 48 in decimal:
@Test public void givenOnePositiveInteger_whenLeftShiftOperator_thenNewDecimalNumber() { int value = 12; int leftShift = value << 2; assertEquals(48, leftShift); }
This works similarly for a negative value:
@Test public void givenOneNegativeInteger_whenLeftShiftOperator_thenNewDecimalNumber() { int value = -12; int leftShift = value << 2; assertEquals(-48, leftShift); }
4.2. Signed Right Shift [>>]
The right shift operator shifts all the bits to the right. The empty space in the left side is filled depending on the input number:
- When an input number is negative, where the leftmost bit is 1, then the empty spaces will be filled with 1
- When an input number is positive, where the leftmost bit is 0, then the empty spaces will be filled with 0
Let's continue the example using 12 as input.
Now, we will move it by 2 places to the right(12 >>2) and see what will be the final result.
The input number is positive, so after shifting to the right by 2 places, the result is 0011, which is 3 in decimal:
@Test public void givenOnePositiveInteger_whenSignedRightShiftOperator_thenNewDecimalNumber() { int value = 12; int rightShift = value >> 2; assertEquals(3, rightShift); }
Also, for a negative value:
@Test public void givenOneNegativeInteger_whenSignedRightShiftOperator_thenNewDecimalNumber() { int value = -12; int rightShift = value >> 2; assertEquals(-3, rightShift); }
4.3. Unsigned Right Shift [>>>]
This operator is very similar to the signed right shift operator. The only difference is that the empty spaces in the left are filled with 0 irrespective of whether the number is positive or negative. Therefore, the result will always be a positive integer.
Let's right shift the same value of 12:
@Test public void givenOnePositiveInteger_whenUnsignedRightShiftOperator_thenNewDecimalNumber() { int value = 12; int unsignedRightShift = value >>> 2; assertEquals(3, unsignedRightShift); }
And now, the negative value:
@Test public void givenOneNegativeInteger_whenUnsignedRightShiftOperator_thenNewDecimalNumber() { int value = -12; int unsignedRightShift = value >>> 2; assertEquals(1073741821, unsignedRightShift); }
5. Difference Between Bitwise and Logical Operators
There are a few differences between the bitwise operators we've discussed here and the more commonly known logical operators.
First, logical operators work on boolean expressions and return boolean values (either true or false), whereas bitwise operators work on binary digits of integer values (long, int, short, char, and byte) and return an integer.
Also, logical operators always evaluate the first boolean expression and, depending on its result and the operator used, may or may not evaluate the second. On the other hand, bitwise operators always evaluate both operands.
Finally, logical operators are used in making decisions based on multiple conditions, while bitwise operators work on bits and perform bit by bit operations.
6. Casos de uso
Algunos casos de uso potenciales de operadores bit a bit son:
- Pilas de comunicación donde los bits individuales en el encabezado adjunto a los datos significan información importante
- En sistemas embebidos para establecer / borrar / alternar solo un bit de un registro específico sin modificar los bits restantes
- Para cifrar datos por cuestiones de seguridad mediante el operador XOR
- En la compresión de datos mediante la conversión de datos de una representación a otra, para reducir la cantidad de espacio utilizado
7. Conclusión
En este tutorial, aprendimos sobre los tipos de operadores bit a bit y en qué se diferencian de los operadores lógicos. También vimos algunos casos de uso potenciales para ellos.
Todos los ejemplos de código de este artículo están disponibles en GitHub.