Tutorial de Programación en C para Linux Parte 15 – Complemento a 2 y Números negativos
Se trata de los números negativos. Sí, aunque mencionamos brevemente las variables con signo frente a las variables sin signo en uno de nuestros tutoriales iniciales, en realidad no hablamos de cómo se almacenan los números negativos en la memoria.
Pues bien, eso es exactamente lo que se tratará en este tutorial. Así que, sin más preámbulos, empecemos con la discusión.
Complemento a 2
Antes de empezar con la explicación sobre la representación de los números negativos en la memoria, es importante que conozcamos el concepto de complemento a 1 y a 2, que son operaciones de nivel binario.
Pongamos un ejemplo muy sencillo. Supongamos que tienes un entero de 4 bytes «a» con valor decimal 15. Así es como se representa en la memoria en forma binaria:
00000000 00000000 00000000 00001111
Ahora, para calcular el complemento a uno, basta con invertir todos los bits. Así que la siguiente es la representación del complemento a 1 de 15:
11111111 11111111 11111111 11110000
Ahora, si añades 1 a la representación binaria anterior, obtienes el complemento a 2.
11111111 11111111 11111111 11110001
Así que la representación anterior es el complemento a dos de 15.
Números negativos
Algunos de vosotros estaréis pensando ¿por qué hemos hablado del complemento a 1 y a 2? Pues bien, la respuesta está en que la representación binaria de un número negativo se calcula mediante el complemento a dos.
¿Te cuesta creerlo? Aquí tienes la prueba:
El complemento a 2 que hemos calculado en el apartado anterior puede representarse en forma hexadecimal como 0xFFFFFFF1. Ahora, veamos cuál es este valor en forma decimal mediante un programa en C
Aquí tienes el código:
#include <stdio.h>
int main()
{
int a = 0xFFFFFFF1;
printf(«a = %d», a);
return 0;
}
Y la siguiente es la salida:
a = -15
¿Te lo crees ahora? Empezamos con un número «15», calculamos su complemento a 2, y cuando convertimos de nuevo el valor del complemento a 2 en decimal, descubrimos que es -15.
Sigamos, ahora vamos a retocar ligeramente el código para asegurarnos de que la llamada a printf lee el valor de la variable ‘a’ como un entero sin signo.
#include <stdio.h>
int main()
{
int a = 0xFFFFFFF1;
printf(«a = %u», a);
return 0;
}
Aquí está ahora la salida:
a = 4294967281
Uy, la salida ha cambiado, y ahora es un enorme valor positivo. Pero, ¿por qué ha ocurrido esto? ¿No es 0xFFFFFFF1 el complemento a 2 de 15 como vimos antes?
Sí, 0xFFFFFFF1 es el complemento a 2 de 15, pero si no lo miras desde esa perspectiva, también es un valor normal (4294967281). La diferencia radica en cómo se lee. Si se lee como un entero con signo (a través de %d en printf), verás la salida como -15, pero si se lee como un entero sin signo (a través de %u en printf), verás la salida como 4294967281.
Como regla general con las variables con signo (que manejan valores tanto negativos como positivos), ten en cuenta que la representación binaria de los números negativos siempre tiene «1» como bit más a la izquierda, mientras que en el caso de los números positivos el bit en cuestión siempre es 0.
Por último, ten en cuenta que también puedes invertir una representación en complemento a dos para obtener su homólogo positivo. Como ejemplo, tomemos de nuevo el valor 0xFFFFFFF1, que es la representación hexadecimal de -15. Se representa en forma binaria como:
11111111 11111111 11111111 11110001
Ahora, para obtener su contrapartida positiva, basta con volver a realizar un complemento a 2. Es decir, haz primero un complemento a 1:
00000000 00000000 00000000 00001110
Y luego suma 1
00000000 00000000 00000000 00001111
Ahora, si conviertes esto, obtendrás el valor 15 en forma decimal.
Conclusión
Espero que este tutorial te haya ayudado a comprender el concepto de números negativos en el contexto de cómo se representan en la memoria. Te sugiero que pruebes los ejemplos que hemos utilizado en este tutorial, y en caso de que encuentres algún problema, o tengas alguna duda o consulta, déjanos un comentario más abajo.