Binario, hexadecimal y complemento a dos
Con dos símbolos alcanza para representar cualquier número, y con un truco de rotación alcanza para representar los negativos sin que el circuito de suma tenga que enterarse.
Todo lo que hace una computadora termina siendo una secuencia de bits. Lo interesante no es que los números se escriban en base dos —eso es mecánico— sino cómo se representan los negativos, que es una decisión de diseño con consecuencias que se ven en cualquier lenguaje.
Qué es una base, en realidad
Una base es sólo cuántos símbolos hay antes de necesitar otra posición. En binario, cada posición vale una potencia de dos, y un número de bits representa valores de a .
El hexadecimal existe por comodidad humana: cada dígito hexadecimal equivale exactamente a cuatro bits, así que convertir es agrupar de a cuatro, sin cuentas. Por eso los volcados de memoria, los colores y las direcciones se escriben en hexa: es binario legible.
Antes de seguir, predecí
Las tres formas de escribir un negativo
Para los negativos hubo varias opciones. Signo y magnitud reserva un bit para el signo: fácil de leer y con dos ceros distintos, el positivo y el negativo. Complemento a uno invierte todos los bits: también tiene dos ceros.
Los dos ceros no son una curiosidad estética: obligan al circuito a casos especiales en cada comparación. La opción que ganó, el complemento a dos, es invertir los bits y sumar uno, y no tiene ese problema.
El 5 en cuatro bits: 0101. El bit más a la izquierda en cero dice que es positivo.
Por qué ganó complemento a dos
La ventaja del complemento a dos es que la resta desaparece como operación. Sumar un número negativo representado así da el resultado correcto usando el mismo sumador que para los positivos, descartando el acarreo que se sale.
Es aritmética modular: los valores se comportan como un anillo donde pasar del máximo vuelve al mínimo. El hardware no distingue con signo de sin signo al sumar; la diferencia aparece recién al comparar y al desplazar a la derecha. Un solo circuito para las dos cosas: por eso ganó.
Hay un negativo más que positivos
El rango de bits en complemento a dos va de a : hay un negativo más que positivos, porque el cero ocupa un lugar del lado positivo.
Eso produce una rareza real: el mínimo no tiene opuesto representable. Negar el entero más chico de 32 bits devuelve el mismo número, y el valor absoluto de ese valor es negativo. Es la fuente de una familia entera de bugs y de vulnerabilidades, y no es un caso hipotético.
El desbordamiento y cómo se detecta
El desbordamiento ocurre cuando el resultado no entra en los bits disponibles. En complemento a dos se detecta con una regla simple: sumar dos positivos y obtener un negativo, o dos negativos y obtener un positivo.
Qué pasa después depende del lenguaje. En C con enteros con signo es comportamiento indefinido, y el compilador optimiza asumiendo que no ocurre. En Java y Go se envuelve silenciosamente. En Rust aborta en desarrollo y envuelve en producción. En Python no pasa nada porque los enteros crecen sin límite. La misma operación, cuatro resultados distintos.
Dos costumbres que salen de esto
De acá salen dos costumbres útiles. Primera: el chequeo de desbordamiento se hace antes de la operación, no después, porque después el valor ya se perdió.
Segunda: el corrimiento a la derecha tiene dos variantes. La aritmética conserva el signo copiando el bit más alto; la lógica rellena con ceros. Usar la equivocada sobre un número negativo da un resultado absurdo, y es el motivo por el que dividir por dos con un corrimiento no es equivalente a dividir por dos para los negativos.
Lo que se ve desde un lenguaje
// Desplazar es multiplicar o dividir por potencias de dos
5 << 1; // 10
5 >> 1; // 2, y trunca hacia abajo
// El desbordamiento silencioso, en enteros de 32 bits
const max = 2 ** 31 - 1; // 2147483647
(max | 0) + 1 | 0; // -2147483648: dio la vuelta
// Las máscaras: banderas empaquetadas en un entero
const LEER = 0b100, ESCRIBIR = 0b010, EJECUTAR = 0b001;
const permisos = LEER | ESCRIBIR; // 0b110
(permisos & ESCRIBIR) !== 0; // true: ¿tiene el permiso?
const sinEscritura = permisos & ~ESCRIBIR; // 0b100: apagar un bitLas tres partes son la misma representación vista desde arriba. El desplazamiento funciona porque cada posición vale una potencia de dos; el desbordamiento ocurre porque después del mayor positivo viene el patrón de bits que en complemento a dos significa el menor negativo; y las máscaras existen porque un entero es un conjunto de bits independientes.
Por qué complemento a dos ganó
| Signo y magnitud | Complemento a uno | Complemento a dos | |
|---|---|---|---|
| Representación del cero | dos: +0 y −0 | dos | una sola |
| Sumar negativos | circuito aparte | con acarreo de vuelta | el mismo sumador |
| Comparar | casos especiales | casos especiales | directo |
| Rango con n bits | −(2ⁿ⁻¹−1) a 2ⁿ⁻¹−1 | lo mismo | −2ⁿ⁻¹ a 2ⁿ⁻¹−1 |
Cierre
Binario para la máquina, hexadecimal para leerlo. El complemento a dos convierte la resta en suma y elimina el cero doble, a cambio de un rango asimétrico donde el mínimo no tiene opuesto. El desbordamiento se detecta por el cambio de signo y cada lenguaje decide distinto qué hacer con él.
Autoevaluación
¿Lo entendiste?
Práctica