Sistemas de numeración y aritmética binaria
Adentro de la máquina todo son bits. Lo que cambia es cómo se los lee: el mismo byte puede ser un número sin signo, uno negativo, un carácter o una instrucción.
01Un número no es su escritura
Trece ovejas son trece ovejas, se escriban 13, XIII, 1101 o
D. El número es la cantidad; lo que cambia es el sistema con que se anota. Los sistemas que
usa la computación son posicionales: cada dígito pesa según el lugar que ocupa, y el peso de cada
lugar es una potencia de la base b.
Por ejemplo, 1101,12 = 1·23 + 1·22 + 0·21 + 1·20 + 1·2−1 = 8 + 4 + 1 + 0,5 = 13,5.
Un circuito que sólo tiene que distinguir dos estados, conduce o no conduce, es barato, rápido y muy inmune al ruido: una tensión de 4,3 V en lugar de 5 V sigue siendo un «1» sin discusión. Distinguir diez niveles en un mismo cable sería mucho más frágil. La base 2 no es una elección matemática sino eléctrica.
Los programadores leen binario agrupado. El hexadecimal (base 16, dígitos 0–9 y A–F) resume
cuatro bits en un dígito y el octal (base 8) resume tres. 1001 1100 se lee 9C
de un vistazo; por eso los volcados de memoria y las direcciones se escriben en hexadecimal.
02Un byte, muchas lecturas
Los mismos ocho bits significan cosas distintas según quién los lea. Tocá los bits: el patrón es uno solo, y las interpretaciones cambian a la vez. El bit de la izquierda (b7) es el de mayor peso.
Clic en cada bit para cambiarlo. En complemento a dos, el bit 7 pesa −128.
La lectura no está en los bits: está en el programa. En C la decide el tipo de la variable, y convertir de un tipo a otro muchas veces no toca ni un bit, sólo cambia la lectura.
03Cómo se convierte entre bases
De decimal a otra base: divisiones sucesivas
Se divide por la base y se guarda el resto; el cociente se vuelve a dividir, hasta llegar a cero. Los restos, leídos de abajo hacia arriba, son los dígitos.
156 ÷ 2 = 78 resto 0 · 78 ÷ 2 = 39 resto 0 · 39 ÷ 2 = 19 resto 1 · 19 ÷ 2 = 9 resto 1 · 9 ÷ 2 = 4 resto 1 · 4 ÷ 2 = 2 resto 0 · 2 ÷ 2 = 1 resto 0 · 1 ÷ 2 = 0 resto 1.
De abajo hacia arriba: 156 = 1001 11002 = 9C16 = 2348. Comprobación: 128 + 16 + 8 + 4 = 156.
La parte fraccionaria: multiplicaciones sucesivas
Se multiplica por la base; la parte entera que aparece es el dígito siguiente y se sigue con lo que queda después de la coma.
0,625 · 2 = 1,25 → 0,25 · 2 = 0,5 → 0,5 · 2 = 1,0. Termina: 0,625 = 0,1012.
0,1 · 2 = 0,2 → 0,4 → 0,8 → 1,6 → 1,2 → 0,4 → … y el 0,4 vuelve a aparecer. 0,1 = 0,000112, periódico.
Una fracción tiene desarrollo finito en base 2 sólo si su denominador es una potencia de 2. El 0,1
decimal no lo es, así que en la máquina se guarda redondeado. Por eso en casi cualquier lenguaje
0.1 + 0.2 == 0.3 es falso. Nunca se comparan números reales con ==: se compara
la diferencia contra una tolerancia.
Entre binario, octal y hexadecimal: agrupar
Como 16 = 24 y 8 = 23, no hace falta pasar por decimal: se agrupan los bits de a
cuatro o de a tres desde la coma y se reemplaza cada grupo. 1011 0110 1111 = B6F;
101 101 101 111 = 5557.
04Aritmética binaria
Se hace igual que en decimal, con tablas mucho más cortas. En la suma, 1 + 1 = 10: se escribe 0 y se lleva 1. La multiplicación se reduce a desplazar y sumar, porque cada dígito del multiplicador es 0 o 1: o no se suma nada o se suma el multiplicando corrido.
1011 (11) 1011
+ 0110 ( 6) × 101
------ ------
10001 (17) 1011 ← 1011 · 1
0000 ← 1011 · 0, corrido 1
1011 ← 1011 · 1, corrido 2
------
110111 (55 = 11 · 5)
Correr un número k lugares a la izquierda lo multiplica por 2k; a la derecha lo divide
por 2k descartando el resto. Los compiladores reemplazan x * 8 por
x << 3 cuando les conviene. Los sumadores que hacen esto en hardware están en
lógica combinacional.
05Números negativos: complemento a dos
La máquina no tiene un lugar para el signo menos: todo son bits. Hubo tres soluciones, y ganó la que permite usar el mismo sumador para números con y sin signo.
| Representación | −5 en 8 bits | Rango con 8 bits | Problema |
|---|---|---|---|
| Signo y magnitud | 1000 0101 | −127 … +127 | Dos ceros (+0 y −0) y un circuito de suma distinto para cada combinación de signos |
| Complemento a uno | 1111 1010 | −127 … +127 | También tiene dos ceros; la suma necesita sumar el acarreo final |
| Complemento a dos | 1111 1011 | −128 … +127 | Ninguno: un solo cero y la suma común funciona |
En complemento a dos el bit de mayor peso vale negativo. Con n bits:
Una manera de verlo sin fórmulas: los números de 8 bits forman un reloj de 256 posiciones. Restar 5
es lo mismo que avanzar 251, porque 251 + 5 = 256 da la vuelta y vuelve a cero. En complemento a dos, a la
posición 251 (1111 1011) se la llama −5. Por eso el sumador no necesita saber si hay signo: la
cuenta en el reloj es la misma.
Al pasar un número con signo a más bits se copia el bit de signo hacia la izquierda: −5 en 16 bits es
1111 1111 1111 1011, no 0000 0000 1111 1011 (que es +251).
06Acarreo y desborde: las banderas
El resultado de una suma de 8 bits siempre cabe en 8 bits más un acarreo. Lo que puede fallar es la interpretación, y la ALU avisa con banderas, sin saber cuál de las dos lecturas le interesa al programa:
- C (acarreo): el resultado sin signo no entró. 200 + 100 = 300 > 255.
- V (desborde): el resultado con signo no entró. Pasa sólo al sumar dos números del mismo signo y obtener uno del signo contrario: 100 + 100 da −56.
- N (negativo) copia el bit 7 y Z (cero) indica si el resultado es 0.
Escribí los operandos en decimal (−128 a 255). La ALU los guarda como 8 bits y calcula las banderas.
07Números reales: punto fijo y punto flotante
En punto fijo se decide de antemano cuántos bits van después de la coma. En formato Q8.8, por ejemplo, un entero de 16 bits representa el número dividido por 256. Es rápido, usa sólo la ALU entera y es lo habitual en microcontroladores sin unidad de punto flotante; a cambio, el rango es chico.
El punto flotante hace lo mismo que la notación científica: guarda signo, exponente y mantisa. El estándar IEEE 754 de precisión simple usa 32 bits:
6,25 = 110,012 = 1,10012 · 22. Signo 1; exponente 2 + 127 = 129 =
1000 0001; fracción 1001 0000 ….
1 10000001 10010000000000000000000 = 0xC0C80000.
Con 24 bits de mantisa la precisión relativa es 2−23 ≈ 1,2·10−7: unas siete cifras
decimales. Sumar 1 a 100 000 000 en float no cambia nada, porque el 1 cae por debajo de la última
cifra que se guarda. Para cálculos de ingeniería se usa double, de 64 bits y unas 16 cifras.
08Códigos: cuando los bits no son cantidades
| Código | Qué representa | Ejemplo |
|---|---|---|
| BCD | Cada dígito decimal en 4 bits. Lo usan los relojes, los displays y los instrumentos | 59 = 0101 1001 |
| ASCII | Caracteres en 7 bits: letras, dígitos, signos y controles | 'A' = 65 = 0x41; '0' = 48 |
| UTF-8 | Todo Unicode en 1 a 4 bytes; los primeros 128 coinciden con ASCII | 'ñ' = 0xC3 0xB1 |
| Gray | Números consecutivos difieren en un solo bit. Evita lecturas falsas en encoders | 3 = 010, 4 = 110 |
El dígito '7' no es el número 7: vale 55. Para convertir un carácter dígito en su valor se resta
'0'. Los códigos que usan los conversores están en
conversores A/D y D/A.
09En el laboratorio
Escribir una función void binario(uint8_t x) que imprima los 8 bits con
(x >> i) & 1. Imprimir con ella 5, −5 guardado en un int8_t, y 251.
Explicar por qué los dos últimos coinciden.
Declarar uint8_t a = 200, b = 100; y guardar a + b en un uint8_t.
Predecir el resultado antes de imprimirlo (44). Repetir con int8_t y 100 + 100.
Sumar 0.1f diez veces en un float y comparar con 1.0f con ==. Imprimir con
%.10f. Repetir con double y proponer una comparación correcta con tolerancia.
10Errores frecuentes
- Leer los restos de arriba hacia abajo en las divisiones sucesivas: da el número espejado.
- Agrupar los bits desde la izquierda para pasar a hexadecimal. Se agrupa desde la coma.
- Olvidar el «+1» del complemento a dos, que es el complemento a uno.
- Confundir acarreo con desborde. Uno interesa sin signo y el otro con signo.
- Rellenar con ceros un número negativo al agrandarlo, en lugar de extender el signo.
- Comparar reales con ==.
11Autoevaluación
Pasar 45 a binario, octal y hexadecimal.
45 = 10 11012 = 558 = 2D16.
¿Qué valor tiene 1110 0110 sin signo y en complemento a dos?
Sin signo, 230. En complemento a dos, 230 − 256 = −26.
¿Cuál es el rango de un entero de 16 bits en complemento a dos?
De −32 768 a +32 767.
Al sumar 0111 0000 + 0101 0000, ¿qué banderas se encienden?
112 + 80 = 192 = 1100 0000. C = 0 (sin signo entra), V = 1 (dos positivos dieron
un negativo: −64), N = 1, Z = 0.
¿Por qué el 0,5 se guarda exacto en un float y el 0,2 no?
0,5 = 2−1 tiene desarrollo binario finito. 0,2 = 1/5 tiene un 5 en el denominador, que no es potencia de 2, y su desarrollo binario es periódico.
¿Qué número representa 0x41200000 en IEEE 754 simple?
Signo 0; exponente 1000 0010 = 130 → 23; fracción 0100…
= 0,25. x = 1,25 · 8 = 10.
¿Cómo se obtiene el valor numérico del carácter '8'?
Restándole '0': '8' − '0' = 56 − 48 = 8.
12Para ampliar
- Thomas L. Floyd. Fundamentos de sistemas digitales. 9.ª ed., Pearson Prentice Hall, 2006. El capítulo 2 cubre sistemas de numeración, complemento a dos, BCD y códigos con muchísimos ejercicios.
- M. Morris Mano. Diseño digital. 3.ª ed., Pearson Educación, 2003. Más formal: bases, complementos y códigos binarios como punto de partida del diseño lógico.
- Randal E. Bryant y David R. O'Hallaron. Computer Systems: A Programmer's Perspective. 3.ª ed., Pearson, 2016 (en inglés). El capítulo 2 es la mejor explicación de cómo representa C enteros y flotantes, con sus trampas.
- David Goldberg. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys, 23(1), 1991 (en inglés). El artículo clásico sobre por qué el punto flotante se porta como se porta.