Publi

Cómo extraer la parte alta y la parte baja de un número con varios ejemplos en C

photo-1414509902153-26bed16bc962_red

En ocasiones, en C, sobre todo, una misma variable numérica (un entero, un short, o incluso un char) encierra en sus bits más valores que nos pueden resultar interesantes por separado. Sobre todo, por optimizar la memoria, en lugar de reservar dos variables de tipo entero de 32bits para almacenar valores pequeños, queramos empaquetar en el mismo valor entero los dos valores y luego extraerlos cuando los necesitemos. Esto lo podemos ver en sistemas empotrados en los que estamos muy limitados en memoria.

De la misma forma, si estamos trabajando con grandes bases de datos, con miles de registros (o millones), podremos ahorrar varios megabytes de espacio si reunimos varios valores pequeños en un mismo valor más grande.

En este caso, nos vamos a centrar en una variable de tipo unsigned short (normalmente, 16bits), aunque podremos verificar el tamaño en nuestro sistema con sizeof(). Dicha variable de tipo short, queremos descomponerla en dos variables de tipo char a las que llamaremos parte alta a la parte más significativa de la palabra (es decir, a la que más peso tiene), y parte baja a la que menos peso tiene.

Por ejemplo, el número 43679 podemos representarlo en binario como 10101010 10011111, la parte alta sería 10101010 y la baja 10011111. ¿ Cómo podemos extraer dichos valores en C ?

Nota, hecho en Little Endian

Algunos de estos ejemplos dependerán del sistema que use nuestra plataforma para representar los números de más de un byte. Estos ejemplos están probados en un sistema little endian, con un procesador Intel, puede que si usas otro tipo de CPU, los resultados no sean los mismos, ya que los números grandes de más de un byte, se almacenarán de forma diferente.

Como destino, variables unsigned char

Como el tamaño de cada una de las partes es 8bits, vamos a almacenar los valores resultantes en un unsigned char, de esta forma, nos quitaremos muchos problemas, ya que la parte baja la podemos sacar directamente, y en este caso, la parte alta la calcularemos desplazando los bits del número original a la derecha (operador >>):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
  /* Un número grande, en binario sería 10101010 10011111 */
  /* Como vamos a separar los dos bytes, queremos algo que sea
   fácil de ver. Por un lado sacaremo la parte alta 10101010 y por
   otro la parte baja 10011111*/

  unsigned short numero=43679;
  unsigned char partealta = numero >> 8;
  unsigned char partebaja = numero;

  printf ("Numero: %d\n", numero);
  printf ("Parte alta: %d\n", partealta);
  printf ("Parte baja: %d\n", partebaja);

  return 0;
}

Como destino, variables tipo unsigned char

Pero claro, esto de hacer partebaja = numero, no nos valdrá si el tamaño de la variable resultante es mayor al número de bits que tenemos. Ahora mismo, al tener la variable resultante partebaja el número de bits exactos que queremos extraer se van a copiar tal cual desde número y como partebaja es más pequeña que número sólo se copiará una parte.

En otro caso, si en la variable caben más bits de los necesarios, necesitamos transformar el número, dos operaciones que me vienen a la cabeza son:

  • Desplazar número primero a la derecha y luego a la izquierda: al desplazar número a la derecha, la parte alta se perderá, y al desplazar a la izquierda de nuevo, en la parte alta habrá ceros. Con este método tenemos que tener muy en cuenta los tamaños. Por ejemplo:
    1
      unsigned short partebaja = (unsigned short)(numero << 8) >> 8;
  • Utilizar una máscara para extraer el número, en este caso la máscara sería 00000000 11111111, es decir, 255 en decimal. Con esto queremos que sólo se copien los valores que están a uno, por lo que ignoraríamos la parte alta y sólo nos quedaríamos con la baja:
    1
      unsigned short partebaja = numero & 255;

    Al igual que 255 podemos escribir 0xFF, es decir, su valor hexadecimal. Pero claro, si eso lo consideráis un poco feo al escribir un número a pelo, aunque sea un valor como este, siempre podéis decirle a C que lo calcule. Es decir, lléname un short de unos y luego desplázalo a la derecha, con lo que nos quedaría la máscara deseada:

    1
      unsigned short partebaja = numero & (unsigned short)(~0U) >> 8;

    O incluso podemos llenar directamente un unsigned char de unos y listo, no tenemos que desplazar nada:

    1
      unsigned short partebaja = numero & (unsigned char)~0U;

Un inciso para ver los valores en binario

Está muy bien ver los valores con números enteros, pero podemos ayudarnos de un pequeño código que publiqué hace unos días para visualizar el valor en base2 de un número. El tema quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdlib.h>
#include <stdio.h>

#define typetobin(type) const char* type##_a_binario(type num)  \
  {                             \
  return getBits(sizeof(type), &num);               \
  }


#define extypetobin(mod, type) const char* mod##type##_a_binario(mod type num)  \
  {                             \
  return getBits(sizeof(type), &num);               \
  }


const char* getBits(size_t const size, void* ptr);

typetobin(float);
typetobin(double);
typetobin(char);
typetobin(short);
extypetobin(unsigned, short);
typetobin(int);

int main(int argc, char *argv[])
{
  /* Un número grande, en binario sería 10101010 10011111 */
  /* Como vamos a separar los dos bytes, queremos algo que sea
   fácil de ver. Por un lado sacaremo la parte alta 10101010 y por
   otro la parte baja 10011111*/

  unsigned short numero=43679;
  unsigned short partealta = numero >> 8;
  unsigned short partebaja = numero & (unsigned char)~0U;

  printf ("Numero: %d (%s)\n", numero, unsignedshort_a_binario(numero));
  printf ("Parte alta: %d (%s)\n", partealta, unsignedshort_a_binario(partealta));
  printf ("Parte baja: %d (%s)\n", partebaja, unsignedshort_a_binario(partebaja));

  return 0;
}

const char* getBits(size_t const size, void* ptr)
{
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;
    char *resultado = (char*)malloc(100);
    char *res = resultado;

    for (i=size-1;i>=0;i--)
    {
        for (j=7;j>=0;j--)
        {
            byte = b[i] & (1<<j);
            byte >>= j;
        *res='0'+byte;
        res++;
        }
    *res++=' ';
    }

    *res='\0';
    return res-size*9;
}

Usando punteros

Puede que este método sea más fácil si tenemos manejo con punteros. En este caso, tendremos lo siguiente:

1
2
3
  unsigned short numero=43679;
  unsigned char *partealta = (unsigned char*)&numero+1;
  unsigned char *partebaja = (unsigned char*)&numero;

aquí podríamos controlar el caso en el que compilemos en un big-endian cambiando el nombre de las variables *partealta y *partebaja.

Dejo también el ejemplo para copiar y pegar, y ver los valores en binario:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdlib.h>
#include <stdio.h>

#define typetobin(type) const char* type##_a_binario(type num)  \
  {                             \
  return getBits(sizeof(type), &num);               \
  }


#define extypetobin(mod, type) const char* mod##type##_a_binario(mod type num)  \
  {                             \
  return getBits(sizeof(type), &num);               \
  }


const char* getBits(size_t const size, void* ptr);

typetobin(float);
typetobin(double);
typetobin(char);
typetobin(short);
extypetobin(unsigned, short);
typetobin(int);

int main(int argc, char *argv[])
{
  /* Un número grande, en binario sería 10101010 10011111 */
  /* Como vamos a separar los dos bytes, queremos algo que sea
   fácil de ver. Por un lado sacaremo la parte alta 10101010 y por
   otro la parte baja 10011111*/

  unsigned short numero=43679;
  unsigned char *partealta = (unsigned char*)&numero+1;
  unsigned char *partebaja = (unsigned char*)&numero;

  printf ("Numero: %d (%s)\n", numero, unsignedshort_a_binario(numero));
  printf ("Parte alta: %d (%s)\n", *partealta, char_a_binario(*partealta));
  printf ("Parte baja: %d (%s)\n", *partebaja, char_a_binario(*partebaja));

  return 0;
}

const char* getBits(size_t const size, void* ptr)
{
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;
    char *resultado = (char*)malloc(100);
    char *res = resultado;

    for (i=size-1;i>=0;i--)
    {
        for (j=7;j>=0;j--)
        {
            byte = b[i] & (1<<j);
            byte >>= j;
        *res='0'+byte;
        res++;
        }
    *res++=' ';
    }

    *res='\0';
    return res-size*9;
}

¿ Dudas ? ¿ Comentarios ? Dejadlos aquí abajo.

Foto: Jay Wennington (Unsplash)

También podría interesarte....

There are 7 comments left Ir a comentario

  1. Pingback: Cómo extraer la parte alta y la parte baja de un número con varios ejemplos en C | PlanetaLibre /

  2. Ricky Luck /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    Greetings from Florida! I’m bored at work, so I decided to browse your site on my iPhone during lunch break. I love the information you provide here and can’t wait to take a look when I get home. I’m surprised at how fast your blog loaded on my cell phone .. I’m not even using WIFI, just 3G. Anyways, awesome blog! | House Cleaning Regina

  3. Nanasi /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    dino game is a fun and simple game that you can play on your browser when you have no internet connection. It features a cute T-Rex dinosaur that runs across an endless desert and tries to avoid obstacles like cacti and pterodactyls.

  4. Local Citations /
    Usando Google Chrome Google Chrome 119.0.0.0 en Windows Windows NT

    Can anyone tell me how to extract digits from a cell? I already know how to extract the high part and the low part of a number, with several examples in C, but I struggle to extract digits from a cell. I hope anyone will help me.

  5. Ludo Vicfadri /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    Hello I am so excited I found your website, I really found you by error, while I was researching on Google for something else, Regardless I am here now and would just like to say thank you for a tremendous post and a all round exciting blog (I also love the theme/design), I don’t have time to go through it all at the moment but I have book-marked it and also included your RSS feeds, so when I have time I will be back to read much more, Please do keep up the great job.

    Ee29.net

  6. Retro games /
    Usando Google Chrome Google Chrome 123.0.0.0 en Windows Windows NT

    A number of them are more interesting to me, and I hope that you will write more about them in the blogs that i make in the future.

  7. grade calculator /
    Usando Google Chrome Google Chrome 123.0.0.0 en Windows Windows NT

    I should have thought of this from the beginning

Leave a Reply to Anónimo Cancle Reply