2
votes

Convertir un entier signé de taille binaire variable

J'ai un certain nombre de bits (le nombre de bits peut changer) dans un entier non signé (uint32_t). Par exemple (12 bits dans l'exemple):

union test
{
    signed temp :12;
}; 
union test x;
x.temp = a;
int32_t result = (int32_t) x.temp;

Les bits représentent un entier signé de cette longueur. Dans ce cas, le nombre en décimal doit être -100. Je veux stocker la variable dans une variable signée et obtient sa valeur réelle. Si j'utilise simplement:

int32_t b = (int32_t)a;

ce sera juste la valeur 3996, car il est converti en (0x00000F9C) mais il doit en fait être (0xFFFFFF9C)

Je connais une façon de faire:

uint32_t a = 0xF9C;

maintenant j'obtiens la valeur correcte -100

Mais y a-t-il une meilleure façon de le faire? Ma solution n'est pas très flexible, comme je l'ai mentionné, le nombre de bits peut varier (entre 1 et 64 bits).


5 commentaires

Combien de bits exactement?


Vous avez vraiment besoin de savoir combien de bits, pour pouvoir connaître le signe.


12 bits dans cet exemple. Mais ça peut changer


Eh bien, je sais toujours combien de bits la variable signée a. Im implémentant un protocole de communication. La longueur de chaque variable est définie. Je sais donc que cette variable a 12 bits, mais d'autres variables peuvent avoir une longueur différente. Je peux donc utiliser la solution que j'ai mentionnée, mais j'ai besoin d'un syndicat pour toutes les tailles de 1 à 64


Que diriez-vous de vérifier si le bit signé est défini en fonction de la quantité de bits (décalez votre masque en fonction de la quantité de bits), puis inversez ce masque pour sélectionner la valeur de a. Puis convertissez la valeur en b. et définissez le bit signé de b en fonction de ce que vous avez récupéré précédemment.


5 Réponses :


0
votes

Cela repose sur le comportement défini par l'implémentation de l'extension de signe lors du décalage vers la droite d'entiers négatifs signés. D'abord, vous décalez votre entier non signé complètement à gauche jusqu'à ce que le bit de signe devienne MSB, puis vous le transtypez en entier signé et revenez en arrière:

#include <stdio.h>
#include <stdint.h>

#define NUMBER_OF_BITS 12

int main(void) {
    uint32_t x = 0xF9C;
    int32_t y = (int32_t)(x << (32-NUMBER_OF_BITS)) >> (32-NUMBER_OF_BITS);

    printf("%d\n", y);

    return 0;
}


5 commentaires

implémentation définie signifie-t-elle qu'elle ne fonctionnera pas avec tous les compilateurs?


Oui. L'extension de signe est l'un des choix laissés à l'implémentation du compilateur par le standard C. Mais vous devez également noter que la représentation par nombre négatif est également un tel choix, donc 0xFFFFFF9C ne sera pas -100 avec chaque compilateur.


@EugeneSh. Les types de longueur fixe tels que int32_t sont cependant garantis comme étant le complément à deux. Il n'y a aucune garantie qu'ils existeront, mais s'ils le font, ils seront complémentaires à deux.


@ChristianGibbons Vous avez raison, merci de l'avoir signalé. J'oublie toujours ça.


Vous n'avez pas à vous inquiéter si les types stdint.h sont disponibles, mais écrire du code qui repose sur un comportement défini par l'implémentation est tout simplement mauvais. D'après mon expérience, les changements de droite en particulier ne sont pas portables, environ 50% / 50% entre les changements arithmétiques ou logiques entre les implémentations.



3
votes

Mais y a-t-il une meilleure façon de le faire?

Eh bien, cela dépend de ce que vous entendez par «mieux». L'exemple ci-dessous montre une manière plus flexible de le faire car la taille du champ de bits n'est pas fixe. Si votre cas d'utilisation requiert des tailles de bits différentes, vous pouvez le considérer comme une "meilleure" façon.

-100
1948

Résultat:

unsigned sign_extend(unsigned x, unsigned num_bits)
{
    unsigned f = ~((1 << (num_bits-1)) - 1);
    if (x & f)  x = x | f;
    return x;
}


int main(void)
{
    int x = sign_extend(0xf9c, 12);
    printf("%d\n", x);

    int y = sign_extend(0x79c, 12);
    printf("%d\n", y);
}
blockquote>

1 commentaires

Sans saut / test: unsigned sign_extend (unsigned x, unsigned num_bits) {unsigned high = 1 << (num_bits-1); x & = (1 << num_bits) -1; return (x ^ high) - haut;}



0
votes

Voici une solution à votre problème:

int32_t sign_extend(uint32_t x, uint32_t bit_size)
{
    // The expression (0xffffffff << bit_size) will fill the upper bits to sign extend the number.
    // The expression (-(x >> (bit_size-1))) is a mask that will zero the previous expression in case the number was positive (to avoid having an if statemet).
    return (0xffffffff << bit_size) & (-(x >> (bit_size-1))) | x;
}
int main()
{

    printf("%d\n", sign_extend(0xf9c, 12)); // -100
    printf("%d\n", sign_extend(0x7ff, 12)); // 2047

    return 0;
}


0 commentaires

1
votes

Une manière gratuite de signer une extension de champ de bits (Henry S. Warren Jr., CACM v20 n6 juin 1977) est la suivante:

#include <stdio.h>
#include <stdint.h>

int32_t sign_extend (uint32_t x, int32_t len)
{
    int32_t i = (x & ((1u << len) - 1)); // or just x if you know there are no extraneous bits
    int32_t sext = 1 << (len - 1);
    return (i ^ sext) - sext;
}

int main(void)
{
    printf("%d\n", sign_extend(0xF9C, 12));
    return 0;
}

MISE À JOUR basée sur le commentaire de @ Lundin

Voici le code testé (imprime -100):

// value i of bit-length len is a bitfield to sign extend
// i is right aligned and zero-filled to the left
sext = 1 << (len - 1);
i = (i ^ sext) - sext;


10 commentaires

Je n'ai aucune idée de qui est Henry S. Warren Jr mais cela ne fonctionne pas et donne le mauvais résultat ... Quelle est la logique derrière 1 << (len - 1) ? Vouliez-vous dire (1 << len) -1 ?


Henry S. Warren Jr est l'auteur de Hacker's Delight, entre autres. Cela fonctionne bien, je l'ai utilisé plusieurs fois. J'aurais peut-être dû souligner que i et sext doivent avoir des types signés. 1 << (len - 1) est un masque pour le bit de signe.


Oui, ce code est en effet complètement différent de ce que vous avez publié pour la première fois. Mais ce code invoque également un comportement non défini dans le cas où len vaut 31 ou 32, car on pourrait s'attendre à être une entrée valide pour des nombres non signés de 32 bits. De plus, il n'est pas lisible. Il fonctionne également inutilement lentement, comparez-le par exemple au code que je viens de créer à l'improviste: godbolt. org / z / v9-8o5 .


Il semble que vous ayez manqué mon commentaire sur la réponse de @ 4386427 :).


@Lundin les deux routines font des choses différentes, pourquoi les comparer?


@Lundin si le champ «len» est égal ou inférieur à 31, il n'y a pas de comportement indéfini; si len vaut 32, vous devez utiliser int64_t pour un comportement entièrement défini sur du matériel non complémentaire à deux. Les deux dernières lignes de sign_extend sont identiques à ce que j'ai publié initialement, donc je ne reçois pas ce commentaire. Quant à l'efficacité, il se compile en quatre instructions machine (charge 1, décalage, xor, soustraction).


@Nipo, j'ai manqué votre commentaire sur la réponse de @ 4386427 ... Merci d'avoir aidé à promouvoir les véritables hacks!


C11 6.5.7 / 3-4 "Si la valeur de l'opérande droit est négative ou est supérieure ou égale à la largeur de l'opérande gauche promu, le comportement n'est pas défini." / - / "Le résultat de E1 << E2 est E1 décalé à gauche des positions de bit E2; les bits vides sont remplis de zéros." / - / "Si E1 a un type signé et une valeur non négative, et E1 × 2 ^ E2 est représentable dans le type de résultat, alors c'est la valeur résultante; sinon, le comportement n'est pas défini."


@DougCurrie Le décalage vers la gauche msb d'un int32_t 31 étapes est un comportement indéfini, peiod. int32_t ayant 31 bits de données. Quant à l'efficacité elle compile au démontage que je viens de relier, arrêtez de fantasmer. Si vous voulez garder cette réponse, veuillez montrer en quoi la norme C est erronée ou en quoi les 12 instructions du démontage lié gcc -O3 sont en fait 4.


@Lundin, veuillez noter que j'ai dit "si le champ" len "est de 31 ou moins, il n'y a pas de comportement indéfini" et dans ce cas, je ne décale qu'une valeur signée de 1 par 30 bits. L'autre décalage est sur une valeur non signée. Mon commentaire sur les quatre instructions concernait "les deux dernières lignes de sign_extend sont identiques à ce que j'ai publié initialement" comme dans godbolt.org/ z / sjylN8



-1
votes

La manière saine, portable et efficace de faire cela est simplement de masquer la partie données, puis de remplir tout le reste avec 0xFF ... pour obtenir une représentation correcte du complément de 2. Vous devez savoir combien de bits constituent la partie données.

  • Nous pouvons masquer les données avec (1u .
  • Dans ce cas avec data_length = 8 , le masque de données devient 0xFF . Appelons ce data_mask .
  • Ainsi, la partie données du numéro est un & data_mask .
  • Le reste du nombre doit être rempli de zéros. Autrement dit, tout ne fait pas partie du masque de données. Faites simplement ~ data_mask pour y parvenir.
  • Code C: a = (a & data_mask) | ~ masque_données . Maintenant, a est le complément approprié de 32 bits 2.

Example:

FFFFFF9C        -100

Output:

#include <stdio.h>
#include <inttypes.h>

int main(void) 
{
  const uint32_t data_length = 8;
  const uint32_t data_mask = (1u << data_length) - 1;

  uint32_t a = 0xF9C;
  a = (a & data_mask) | ~data_mask;

  printf("%"PRIX32 "\t%"PRIi32, a, (int32_t)a);
}

Cela repose sur int code> étant le complément de 32 bits 2 mais est par ailleurs entièrement portable.


9 commentaires

Ce code ne fait aucune extension de signe, il rend aveuglément l'entrée négative. Et un & data_mask avant oring ~ data_mask est inutile.


@Nipo Pour que toute cette question ait un sens, vous devez déjà savoir que 0xF9C est le complément signé à 2. Vous ne pouvez pas savoir si un nombre aléatoire est signé ou non.


@Nipo Pour vous épeler, vous ne pouvez pas signer étendre un nombre qui n'est pas déjà dans une variable de type signé et défini comme négatif. Vous ne pouvez pas saisir un morceau aléatoire de binaire et le «prolonger par signe». Le décalage des données dans les bits de signe d'un entier signé appelle un comportement indéfini et le programme est libre de planter et de graver.


@Lundin, en plus d'être d'accord avec Nipo, vous pouvez le faire, comme l'atteste ma solution, sans comportement indéfini ni implémentation défini. Votre version ci-dessus appelle le comportement défini par l'implémentation (voir stackoverflow.com/questions/9498190/... )


Vous semblez oublier qu'un nombre signé peut en fait être positif. Si nous signons étendre 0x2a de 7 bits à 32, je m'attends en fait à obtenir 0x0000002a, pas 0xffffffaa. Ce dernier est ce que nous obtiendrons de votre code.


@DougCurrie Dans la pratique, l'implémentation du complément de chaque mainstream 2 effectuera la conversion bien définie, alors quel est votre point.


@Nipo Non. Le terme "extension de signe" fait référence à un nombre négatif, par exemple int8_t avec la valeur binaire 0xFF conservant son signe lors de la promotion vers un type plus grand comme int16_t , 0xFFFF. Rien d'autre. Vous ne pouvez pas «signer l'extension» de nombres positifs. Et encore une fois, vous ne pouvez pas décaler les bits vers la gauche dans le bit de signe d'une variable signée car cela invoque un comportement indéfini.


@Lundin, recherchez "extension de signe" sur Wikipedia. Le deuxième paragraphe est le suivant: Par exemple, si six bits sont utilisés pour représenter le nombre "00 1010" (décimal positif 10) et que l'opération d'extension de signe augmente la longueur du mot à 16 bits, alors la nouvelle représentation est simplement "0000 0000 0000 1010 ". Ainsi, tant la valeur que le fait que la valeur était positive sont maintenues.


@Lundin: Si l'on définit mathématiquement la représentation binaire comme décrivant l'ensemble des puissances entières non négatives de deux qui doivent être additionnées pour donner une valeur qui sera congruente à ce nombre mod chaque base de puissance de deux possible, alors chaque integer commence par une chaîne infinie de uns ou de zéros suivie d'une chaîne finie de uns et de zéros. L'extension de signe est simplement la pratique consistant à dire que tous les chiffres à gauche d'une représentation particulière correspondent au chiffre le plus à gauche affiché, afin d'éviter d'avoir à les stocker.