Operadores de bits

  1. Calcular las siguientes operaciones:

    1. 11001010 & 10100101,

    2. 11001010 | 10100101, y

    3. 11001010 ^ 10100101.

  2. Escribir las representaciones octales y hexadecimales de los números binarios del ejercicio anterior.

  3. Determinar los resultados octales de las siguientes operaciones, suponiendo que los números no tienen signo (los números están expresados en sistema octal):

    1. 0157 trasladado a izquierda una posición de bit,

    2. 0701 trasladado a izquierda dos posiciones de bit,

    3. 0673 trasladado a derecha dos posiciones de bit,

    4. 067 trasladado a derecha tres posiciones de bit.

  4. Una máscara es un patrón binario en una palabra. Las máscaras se utilizan de acuerdo con las siguientes operaciones: and, or y not. Escribir un programa que utilice una máscara para disponer 1's en posiciones que se deseen, para los bits de una palabra.

  5. Escribir un fragmento de código/procedimiento que convierta un número entero sin signo (32 bits) a formato Big Endian, sobre un arreglo de 4 bytes. Imprimir por stdout las representaciones origen y destino.

    Ejemplo: el número 271590900 será codificado como:

    X[0] = 0x10
    X[1] = 0x30
    X[2] = 0x25
    X[3] = 0xF4
    
  6. Escribir un fragmento de código/procedimiento que convierta un número entero en representación Big Endian de cuatro bytes a formato decimal. Imprimir por stdout las representaciones origen y destino.

    Ejemplo: el número |0x10|0x30|0x25|0xF4| será decodificado como: 271590900.

    ¿Qué aplicaciones tiene la codificación/decodificación Big Endian en Electrónica & Telecomunicaciones?

  7. Escribir un programa que utilice una máscara para invertir los bits de determinadas posiciones.

Operadores de bits y funciones

  1. Para un aplicativo a ejecutarse sobre una plataforma de 32 bits que contendrá una interfaz gráfica de usuario (GUI), se requiere disponer de funciones para el tratamiento de colores de los elementos gráficos de la interfaz. El color de cualquier elemento gráfico se almacenará en una variable de tipo int sin signo de 32 bits, de acuerdo a la convención RGB (ejemplo: un color con representación RGB = 0xFFCCAA se corresponde con 0xFF, 0xCC y 0xAA para las componentes rojo, verde y azul, respectivamente). Se pide:

    1. Escribir una función denominada get_rojo() que reciba como argumento el color en representación RGB sobre una variable de tipo uint32_t, que devuelva por el nombre la componente de color rojo como un uint8_t. Dar un ejemplo de invocación de la función.

    2. Idem a. para una función denominada get_verde() que devuelva la componente verde. Dar un ejemplo de invocación de la función.

    3. Idem a) para una función denominada get_azul() que devuelva la componente Azul. Dar un ejemplo de invocación de la función.

    4. Escribir una función llamada get_componentesRGB() que reciba como argumentos el color en notación RGB sobre una variable de tipo uint32_t, y devuelva a través de la interfaz las 3 componentes R, G, y B simultáneamente sobre variables de tipo uint8_t. Dar un ejemplo de invocación de esta función.

    5. Escribir los prototipos de las funciones desarrolladas en los cuatro puntos anteriores.

    NOTA: Se debe utilizar operadores de bits y máscaras para la resolución de este ejercicio, y documentar todas y cada una de las funciones desarrolladas.

  2. Un microcontrolador posee el siguiente registro para el manejo de datos a través de un puerto serie que puede conectarse a una PC:

    | bit 7 |   6   |   5   |   4   |   3   |   2   |   1   | bit 0 |
    +-------+-------+-------+-------+-------+-------+-------+-------+
    | RXCIE | TXCIE | UDRIE |  RXEN |  TXEN |  CHR9 |  RXB8 |  TXB8 |
    +-------+-------+-------+-------+-------+-------+-------+-------+
    

    RXCIE

    Cuando este bit es 1 indica que se ha terminado de recibir un dato por el puerto serie

    TXCIE

    Cuando este bit es 1 indica que se ha terminado de enviar un dato por el puerto serie

    UDRIE

    Cuando este bit es 1 habilita las interrupciones de hardware durante el envío y recepción de datos por el puerto serie

    RXEN

    Cuando este bit es 1 habilita al puerto serie a recibir datos, si vale 0 RXCIE no tiene funcionalidad

    TXEN

    Cuando este bit es 1 habilita al puerto serie a enviar datos, si vale 0 TXCIE no tiene funcionalidad

    CHR9

    Cuando este bit es 1 el dato tiene 9 bits de longitud más 1 bit de inicio y otro de fin. Cuando vale 0, la longitud del dato es de 8 bits más 1 de inicio y otro de fin

    RXB8

    Si CHR9 es 1, este bit contiene el noveno bit del dato a ser recibido

    TXB8

    Si CHR9 es 1, este bit contiene el noveno bit del dato a ser transmitido

    Se pide:

    1. Escriba el código de una función denominada transmision_completa() que indique, a partir del contenido del byte de control (bit TXCIE), y mediante la devolución de un valor por el nombre, si se ha completado o no, la transmisión de un símbolo por el puerto serie. El prototipo de la función pedida es:

      bool transmision_completa(uint8_t);
      
    1. Dar un ejemplo de invocación de la función.

    2. Modifique el código de la función pedida en el punto a) para que la misma indique, mediante la devolución de un valor por la interfaz, si se ha completado o no la transmisión. Dar un ejemplo de invocación de esta función. Debe utilizarse máscaras y operadores de bits para leer cada bit del registro de datos.

  3. Un sintetizador de una radio digital posee un generador de frecuencia controlado por el siguiente registro:

    | bit 7 |   6   |   5   |   4   |   3   |   2   |   1   | bit 0 |
    +-------+-------+-------+-------+-------+-------+-------+-------+
    |  AFT  | BNDAM |   E   |   D   |   C   |   B   |   A   |  SYN  |
    +-------+-------+-------+-------+-------+-------+-------+-------+
    

    AFT

    Control de Sintonía Fina Automática (1 = ON , 0 = OFF).

    BAND

    Control de selección de banda AM (1 = AM , 0 = FM).

    E, D, C, B, A

    Factor de división de la frecuencia de reloj (ver tabla).

    SYN

    Control de activación del sintetizador (1 = ON, 0 = OFF).

    E

    D

    C

    B

    A

    Factor de división (FDIV)

    0

    0

    0

    0

    0

    1

    0

    0

    0

    0

    1

    2

    0

    0

    0

    1

    0

    3

    0

    0

    0

    1

    1

    4

    ...

    ...

    ...

    ...

    ...

    ...

    1

    1

    1

    1

    0

    31

    1

    1

    1

    1

    1

    32

    Se pide:

    1. Escriba el código de una función denominada get_synthesizer_divider() que devuelva por su nombre el factor de división a partir de los bits A, B, C, D y E contenidos en el byte de control. El prototipo de la función pedida es:

      int get_synthesizer_divider(uint8_t);
      
    2. Dar un ejemplo de invocación de la función.

    3. Escriba el código de una función denominada es_am() que reciba como parámetro el registro de control, y retorne por si está seleccionada la banda de AM. Dar un ejemplo de invocación.

    NOTA: Debe utilizarse máscaras y operadores de bits para operar con los bits del registro de datos.

  4. Para un subsistema de comunicaciones basado en un microcontrolador, que forma parte de un equipo de medición, se debe programar el registro de control de periféricos (SPCR), el cual tiene la siguiente estructura:

    | bit 7 |   6   |   5   |   4   |   3   |   2   |   1   | bit 0 |
    +-------+-------+-------+-------+-------+-------+-------+-------+
    |  SPIE |  SPE  |   -   |   -   |  CPOL |  SPR1 |  SPR0 |   -   | SPCR
    +-------+-------+-------+-------+-------+-------+-------+-------+
    

    en donde:

    SPIE

    control de interrupciones (1: interrupciones habilitadas, 0: deshabilitadas)

    SPE

    encendido del periférico (1: encendido, 0: apagado)

    CPOL

    control de polaridad (1: clock activo por nivel bajo, 0: clock activo por nivel alto)

    SPR1 & SPR0

    factor de división del reloj interno, de acuerdo a la siguiente tabla:

    SPR1

    SPR0

    Factor de división

    Frecuencia de clock

    0

    0

    2

    1MHz

    0

    1

    4

    500kHz

    1

    0

    16

    125kHz

    1

    1

    32

    62.5kHz

    Se pide:

    1. Escribir dos funciones que devuelvan el estado de los bits SPIE y CPOL respectivamente, y que respondan a los siguientes prototipos:

      bool getSPIE(uint8_t);
      bool getCPOL(uint8_t);
      
    2. Escribir una función getCOMControl() que devuelva simultáneamente por la interfaz el estado de los bits SPIE, y CPOL, y del factor de división del clock interno (esta función no debe devolver nada por el nombre). Escribir su prototipo.

    3. Escribir una función cuyo prototipo sea: setCPOL(uint8_t *control, bool on); que permita colocar el bit CPOL en 1 ó 0, dependiendo si el valor del argumento on es true o false, respectivamente.

    4. Dar un ejemplo de invocación de cada una de las funciones desarrolladas en los puntos anteriores.

    NOTA: Se deben utilizar operadores de bits y máscaras para la resolución de este ejercicio.

  5. Para un programa que hará uso del puerto paralelo de una PC se necesita disponer de funciones que permitan colocar a nivel lógico 1 ó 0 un bit determinado dentro de un byte a ser presentado al puerto paralelo. Se pide:

    1. Escribir una función llamada :ansi:set(), que obedezca al siguiente prototipo:

      uint8_t set(uint8_t datos, short linea);
      

      que reciba un byte sobre la variable datos y el número de línea que será forzada a nivel lógico 1, devolviendo por el nombre el resultado de la operación.

      Ejemplo: si en datos se recibe 1000 0001 y linea = 3, se debe retornar el byte 1000 1001.

    2. Dar un ejemplo de invocación de la función.

    3. Idem a) pero para una función llamada clear() que coloque a nivel lógico 0 la línea indicada, y devuelva por el nombre el resultado de la operación. Ejemplo: Si en datos se recibe 0111 1100 y línea = 3, se debe devolver el byte 0111 0100.

    4. Dar un ejemplo de invocación de la función.

    ACLARACION: La línea 0 se corresponde con el bit menos significativo (LSB) del byte, y la linea 7 con el bit más significativo (MSB) del byte.

  6. La arquitectura de procesadores x86 tiene un conjunto de instrucciones llamado Bit Manipulation Instructions sets (BMI) que implementa operaciones de bits. Implementar en C algunas de ellas:

    1. int lzcnt(uint32_t n); (leading zeros count), cuenta la cantidad de ceros que hay a partir del MSB hasta encontrar el primer uno. Ejemplo: Si se recibe 0010 0000 debe devolver el valor 26 (no olvidar que n es de 32 bits y el ejemplo muestra sólo el byte más liviano).

    2. int popcnt(uint32_t n); (population count), cuenta la cantidad total de unos. Ejemplo: Si se recibe 1100 1010 debe devolver 4.

    3. uint32_t bextr(uint32_t a, uint16_t b); (bit field extract) el byte más liviano de b es un inicio y el más pesado una longitud, extrae longitud bits de a a partir de esa posición. Ejemplo: a = 1001 0011 1011 1101, b = 0000 1000 0000 0100, en este caso el inicio es 0000 0100, es decir 4 y la longitud es 0000 1000, es decir 8. Entonces se extraerán 8 bits de a desde la posición 4 y se devolverá 0011 1011.

    4. uint32_t blsi(uint32_t n); (extract lowest set isolated bit) devuelve una máscara para el bit menos pesado que esté encendido. Ejemplo: Para 0101 1100 debe devolver 0000 0100.

    5. uint32_t blsmsk(uint32_t n); (get mask up to lowest set bit) devuelve una máscara que llega hasta el bit menos pesado que esté encendido. Ejemplo: Para 0101 1100 debe devolver 0000 0111.

    6. uint32_t blsr(uint32_t n); (reset lowest set bit) apaga el bit menos pesado que esté encendido. Ejemplo: Para 0101 1100 debe devolver 0101 1000.

    7. int tzcnt(uint32_t n); (trailing zeros count), cuenta la cantidad de ceros que hay a partir del LSB hasta encontrar el primer uno. Ejemplo: Si se recibe 0010 0000 debe devolver el valor 5.

    8. uint32_t pext(uint32_t a, uint32_t m); (parallel bits extract) extrae de a los bits indicados por la máscara m. Ejemplo, si a = 1011 1001 y m = 1001 1100 se deben extraer de a los bits 1--1 10-- por lo que se devolverá 0000 1110.

    9. uint32_t pdep(uint32_t a, uint32_t m); (palallel bits deposit) setea los bits de a indicados en las posiciones de la máscara m. Ejemplo, si a = 0000 1101 y b = 1100 0110 la máscara enmascara 4 bits, que son los más livianos de a, entonces los bits 1101 de a se distribuirían según b así 11-- -01- por lo que se devolverá 1100 0010.

  7. El algoritmo de suma de validación (checksum) de BSD realiza estos pasos operando su suma en 16 bits:

    1. Inicializa el checksum en 0.

    2. Para cada carácter en el texto:

      1. Rota el checksum a la derecha (es decir, desplaza todos los bits en el sentido del bit menos significativo, y el bit que se cae entra en la posición más significativa --15--).

      2. Suma el carácter al checksum.

      3. Verifica que el checksum mantenga 16 bits enmascarándolo con 0xFFFF.

    3. Devuelve el checksum.

    Implementar la función int bsd_checksum(const char *s); que compute el checksum de la cadena s.

  8. Linear-feedback shift register (LFSR) es un método para generar números pseudoaleatorios. En este método se tiene un registro que en cada iteración se desplaza hacia la derecha. El bit aleatorio de la iteración es el LSB, mientras que el MSB se alimenta con una combinación lineal de bits del registro. Eligiendo correctamente los bits que se combinan este método garantiza que para un registro de \(n\) bits, e inicializando el registro en un valor distinto de cero, se puede ciclar por \(2^n-1\) estados diferentes; es decir, se pasará por todos los valores posibles con excepción del cero antes de repetir la secuencia.

    Por ejemplo, para un registro de 4 bits, inicializado en 1001 y tomando como nuevo MSB la combinación de los bits 0 y 1 en el primer paso la salida será un 1 por ser ese el valor del LSB, y se introducirá un 1 como MSB dado que los bits 0 y 1 son 1 y 0 respectivamente y que 1 ^ 0 = 1. Entonces en el segundo paso el registro quedará como 1100, el resultado de desplazar a la derecha e introducir el 1 calculado.

    Si se sigue adelante con la secuencia la misma será:

    Paso    Registro    Salida  Realimentación
    1       1001        1       0 ^ 1 = 1
    2       1100        0       0 ^ 0 = 0
    3       0110        0       1 ^ 0 = 1
    4       1011        1       1 ^ 1 = 0
    5       0101        1       0 ^ 1 = 1
    6       1010        0       1 ^ 0 = 1
    7       1101        1       0 ^ 1 = 1
    8       1110        0       1 ^ 0 = 1
    9       1111        1       1 ^ 1 = 0
    10      0111        1       1 ^ 1 = 0
    11      0011        1       1 ^ 1 = 0
    12      0001        1       0 ^ 1 = 1
    13      1000        0       0 ^ 0 = 0
    14      0100        0       0 ^ 0 = 0
    15      0010        0       1 ^ 0 = 1
    1       1001        ...
    

    donde puede verse que la secuencia resultó ser de 15 valores, es decir \(2^4-1\). Puede además observarse que la salida fueron 8 unos y 7 ceros.

    Implementar:

    1. Una función bool lfsr4(); que implemente el algoritmo de LFSR en 4 bits combinando los bits 0 y 1. Cada llamada deverá devolver un bit de la salida. Para el registro se puede utilizar una variable static dentro de la función inicializada en un valor diferente a cero.

    2. Una función bool lfsr8(); que implemente el algoritmo para 8 bits combinando los bits 0, 2, 3 y 4 (se debe realizar el xor entre los cuatro valores).

    3. Una función bool lfsr16(); que implemente el algoritmo para 16 bits combinando los bits 0, 1, 3 y 12.

    4. Una función bool lfsr24(); que implemente el algoritmo para 24 bits combinando los bits 0, 1, 2 y 7.

    5. Comprobar que las 4 funciones desarrolladas requieran \(2^n-1\) llamadas hasta repetir el valor de inicialización del registro. Para hacer esto se puede agregar un contador static dentro de las funciones y verificar su valor cuando el registro vuelva al valor inicial.

  9. Para detectar errores de transmisión o corrupción en el almacenamiento de datos muchos sistemas utilizan un chequeo de redundancia cíclico (CRC). Un CRC es un grupo de \(n\) bits que son redundantes con el valor que se pretende codificar. Para generar estos \(n\) bits se utilizan los \(n + 1\) coeficientes (binarios) de un polinomio de grado \(n\).

    Por ejemplo para agregarle 3 bits de redundancia a un valor la codificación CRC-3-GSM, utilizada en telefonía celular, usa el polinomio de grado tres: \(x^3 + x + 1\). Es decir sus coeficientes pueden escribirse como \([1, 0, 1, 1]\), o según el binario 1011.

    Teniendo la representación binaria del polinomio y el valor de codificar lo que se hace en primer lugar es agregar \(n\) bits en cero en las posiciones menos pesadas del valor a codificar y luego repetidas veces se alinea el polinomio junto al bit más pesado que esté en uno y se computa el xor entre los bits del polinomio y los del valor, dejando inalterados los demás.

    Por ejemplo, para el valor en 12 bits 100100111011 el mismo se completa con tres ceros a derecha como 100100111011000. La primera operación de xor que se realizará será:

    100100111011 000
    1011
    ----------------
    001000111011 000
    

    Notar que el resultado es la concatenación del xor para los 4 bits más significativos y el valor original para el resto. Como el polinomio tiene un coeficiente 1 en su valor más significativo y como el mismo se alinea siempre con el 1 más pesado del valor entonces siempre la operación va a hacer aparecer al menos un cero adicional al comienzo del resultado. La segunda iteración será:

    001000111011 000
      1011
    ----------------
    000011111011 000
    

    Y las siguientes:

    000011111011 000
        1011
    ----------------
    000001001011 000
         1011
    ----------------
    000000010011 000
           1011
    ----------------
    000000000101 000
             101 1
    ----------------
    000000000000 100
    

    Como ya no quedan unos en los 12 bits del valor original se corta el algoritmo. El CRC-8-GSM para este valor será 100, el resultado que queda en los bits agregados.

    Implementar:

    1. Implementar una función size_t bit_mas_pesado(unsigned int i); que reciba un entero i y devuelva en qué posición está su uno más pesado, por ejemplo, para el entero 1011 el uno más pesado está en la posición 3. Si el entero fuera igual a cero devolver 0.

    2. Implementar una función unsigned int operar(unsigned int valor, unsigned int polimomio); que realice un paso del algoritmo de CRC, se recibe el valor (con los n bits para almacenar la redundancia ya en su lugar) y un polinomio. Se debe computar el resultado de aplicar xor en los \(n + 1\) bits del polinomio dejando inalterados los bits restantes del valor. Por ejemplo para el valor 1000111011000 y el polinomio 1011 debe devolverse 11111011000.

    3. Implementar una función unsigned int calcular_crc(unsigned int valor, unsigned int polinomio); que reciba un valor, un polinomio y devuelva su CRC. Por ejemplo, para el valor 100100111011 y el polinomio 1011 debe devolverse 100.

    4. Implementar una función bool es_crc_valido(unsigned int valor, unsigned int polinomio); que reciba un valor que tiene su CRC puesto en sus n bits más significativos y un polinomio y que devuelva si el CRC se corresponde con el resto del valor, por ejemplo para el valor de 16 bits 1000100100111011, donde el CRC es 100, el valor es 0100100111011 y el polinomio 1011 debe devolverse true.

    Aclaración: Notar que la cantidad de bits del CRC puede obtenerse del resultado de bit_mas_pesado(polinomio).

  10. El formato de codificación de caracteres UTF-8 permite representar símbolos hasta de 21 bits usando entre 1 y 4 bytes para representar cada carácter.

    Un carácter puede tener 1, 2, 3 o 4 bytes representándose de la siguiente forma:

    • 1 byte: 0xxxxxx, donde el valor se conforma por esos 7 bits y se corresponden con la tabla ASCII, es decir los valores entre 0x00 y 0x7F.

    • 2 bytes: 110xxxxx 10xxxxxx, donde los 11 bits codifican al carácter, siendo válida cualquier combinación entre 0x80 y 0x7FF (los valores de entre 0x00 y 0x7F no son válidos porque deberían haber sido representados utilizando sólo un byte).

    • 3 bytes: 1110xxxx 10xxxxxx 10xxxxxx, donde los 16 bits codifican al carácter siendo válidos los símbolos entre 0x800 y 0xFFFF.

    • 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx, donde los 21 bits codifican al carácter siendo válidos los símbolos entre 0x10000 y 0x10FFFF (notar que no se utiliza la totalidad de los 21 bits).

    En la codificación UTF-8 el valor numérico del carácter se denomina code point. A continuación se muestra una tabla de ejemplo de representaciones de distintos caracteres UTF-8 y su representación como bytes y como codepoints:

    ../_images/utf-8.png

    En este trabajo vamos a considerar que un carácter normal está codificado en un char (donde varios char pueden representar un carácter UTF-8), y un codepoint se codifica en un uint32_t. Se deja al alumno investigar sobre el tipo wchar_t del estándar.

    Se pide:

    1. Implementar una función size_t strlen_utf8(const char s[]); que reciba una cadena de caracteres y devuelva su longitud considerando que los caracteres son UTF-8.

    2. Implementar una función bool es_utf8(const char s[]); que reciba una cadena de caracteres y diga si es una cadena UTF-8 válida.

    3. Implementar una función bool char_to_utf8(const char s[], uint32_t codepoints[]); que dada una cadena de caracteres s convierta cada uno de los caracteres de la misma en sus valores UTF-8 y los almacene en codepoints, deberá convertirse hasta el '0' inclusive por lo que el último codepoint será el 0.

    4. Implementar una función bool utf8_to_char(uint32_t codepoints[], char s[]); que convierta el vector codepoints en su representación como bytes conforme a UTF-8 y lo almacene en la cadena s.

  11. Dentro de la tabla de caracteres UTF-8 los codepoints del rango 0x2800 al 0x28FF se utilizan para representar las 256 combinaciones posibles de los caracteres del Braile con cuatro filas y dos columnas de puntos.

    Las mismas están numeradas:

    0 3
    1 4
    2 5
    6 7
    

    Por ejemplo, el codepoint 0x28A7 representa el carácter , es decir, tiene los bits 0, 1, 2, 5 y 7 prendidos: 10100111.

    Si bien no es el objetivo de estos caracteres, los mismos pueden utilizarse para representar 8 pixeles de una imagen en la terminal.

    1. Implementar una función uint32_t imagen4x2_a_codepoint(const bool imagen[4][2]); que dada una matriz de pixeles devuelva el codepoint correspondiente a esa imagen.

    2. Implementar una función void codepoint_braile_a_char(uint32_t codepoint, char caracteres[3]); que convierta un codepoint del rango del Braile a su representación en UTF-8 y la devuelva en caracteres (ver el ejercicio anterior, y notar que los caracteres del Braile están en el caso de 3 bytes).

    3. Haciendo uso de la función del ítem b, reimplementar la función del ítem a para que sea: void imprimir_imagen(const bool **imagen, size_t ancho, size_t alto); que reciba una imagen de tamaño arbitrario (puede asumirse que ancho y alto son múltiplos de 2 y 4 respectivamente) y la imprima en UTF-8 por stdout.

    Si se desea se puede retomar el formato PBM de imágenes descripto en este ejercicio e implementar un programa que lea una imagen en formato PBM y la imprima con caracteres Braile por stdout.

  12. En muchas aplicaciones suele ser conveniente en vez de almacenar números en binario almacenarlos en decimal. Para esto se suele utilizar el formato BCD, en el cual cada 4 bits se representa un dígito decimal entre 0 y 9. Por ejemplo, el número 9511 decimal puede representarse con 16 bits en BCD como 1100 0101 0001 0001 (o, dicho de otra forma, como el 0x9511), donde cada nibble es un dígito.

    1. Implementar la función int extraer_digito(uint64_t bcd, size_t n); que devuelva el dígito n del bcd. Por ejemplo extraer_digito(0x9511, 3) -> 9.

    2. Implementar la función uint64_t sumar_digito(uint64_t bcd, size_t n, int suma); que devuelva el resultado de sumarle el valor suma al dígito n del bcd. Asumir que el dígito más la suma va a ser siempre menor a 16, por lo que nunca el resultado se va a escapar de los 4 bits del dígito. Por ejemplo sumar_digito(0x9511, 2, 5) -> 0x9a11.

      Nota: dado que suma es signado esta función también funcionará para restar si asumimos que el resultado de la resta del dígito sea mayor o igual a cero. No hay que hacer nada adicional.

    3. Implementar la función uint64_t incrementar_bcd(uint64_t bcd); que devuelva el resultado de sumarle uno al bcd dado. Por ejemplo incrementar_bcd(0x399) -> 0x400.

    4. (Difícil) Implementar la función uint64_t sumar_bcd(uint64_t a, uint64_t b); que devuelva el resultado de sumar los dos BCDs a y b. Por ejemplo sumar_bcd(0x9511, 0x7502) -> 0x17013 (notar que no puede utilizarse sumar_digito() dado que no está garantizado que la suma de dos dígitos dé menos de 16).

  13. El algoritmo double dabble permite convertir números binarios en números BCD realizando operaciones de shifts y sumas sobre dígitos únicamente.

    Para convertir un número de n bits hay que reservar una cantidad de dígitos BCD suficientes para realizar la conversión e inicializarlos en 0. Por ejemplo, supongamos que queremos convertir un número de 8 bits, dado que el número más grande que puede representarse es 255 entonces necesitamos 3 dígitos BCD.

    Vamos a convertir el número 243 a BCD. El número 243 se representa como 11110011 en binario. Inicializamos un registro con el siguiente contenido:

    0000 0000 0000 11110011
    

    Con los 3 dígitos y los 8 bits. Con una variable con al menos 20 bits alcanza.

    Partiendo desde esa posición inicial se realizan tantas iteraciones del algoritmo como bits tenga el binario. En este caso serán 8 iteraciones.

    Cada iteración tendrá dos pasos:

    1. A cada dígito del BCD que valga 5 o más se le suma 3.

    2. Se realiza un shift a la izquierda de todo el conjunto.

    Los 8 pasos entonces serán:

    1. 0000 0000 0001 11100110, como todos los BCD valían cero sólo shifteamos.

    2. 0000 0000 0011 11001100, igual al anterior.

    3. 0000 0000 0111 10011000, ídem, notar que ahora el dígito 0 vale 7.

      1. 0000 0000 1010 10011000, sumamos 3 al dígito 0.

      2. 0000 0001 0101 00110000, shift.

      1. 0000 0001 1000 00110000, sumamos 3 al dígito 0 que valía 5.

      2. 0000 0011 0000 01100000, shift.

    4. 0000 0110 0000 11000000, sólo shift, ningún dígito valía más de 5.

      1. 0000 1001 0000 11000000, sumamos 3 al dígito 1 que valía 6.

      2. 0001 0010 0001 10000000, shift.

    5. 0010 0100 0011 00000000, shift.

    Terminados los 8 pasos los dígitos BCD son 243.

    (En el artículo de Wikipedia enlazado al comienzo hay más ejemplos, con más bits.)

    Implementar:

    1. Una función uint64_t convertir_a_bcd(unsigned int n24); que convierta el número n24 de 24 bits en un número BCD según el algoritmo de double dabble.

      Notar que \(2^{24} - 1 = 16\,777\,215\) por lo que utilizando 64 bits para operar hay espacio más que suficiente para los 24 bits del binario y los 8 dígitos BCD.

      Notar además que como 24 es múltiplo de 4 los dígitos BCD están bien alineados por lo que pueden utilizarse todas las funciones desarrolladas en el ejercicio anterior.

    2. El algoritmo de double dabble es muy sencillo de revertir. Se inicializa un registro como el del algoritmo original con los dígitos del BCD y el binario puesto en cero. En cada paso se realiza un shift a la derecha y luego a cada dígito BCD mayor o igual a 8 se le resta 3. Después de tantos pasos como bits en el binario se obtiene el binario. (En el artículo de Wikipedia está el seguimiento para revertir 243.)

      Implementar la función unsigned int convertir_a_binario(uint64_t bcd); que reciba un bcd de no más de 8 dígitos y lo convierta a la representación binaria en 24 bits.