Comment traiter un struct avec deux courts métrages non signés comme si elle était un entier non signé? (En C)

StackOverflow https://stackoverflow.com/questions/4023212

  •  26-09-2019
  •  | 
  •  

Question

I a créé une structure pour représenter un nombre positif à virgule fixe. Je veux les nombres dans les deux côtés du point décimal se composent de 2 octets.

typedef struct Fixed_t {
    unsigned short floor; //left side of the decimal point
    unsigned short fraction; //right side of the decimal point
} Fixed;

Maintenant, je veux ajouter deux nombres à virgule fixe, Fixed x et Fixed y. Pour ce faire, je les traite comme des entiers et ajouter.

(Fixed) ( (int)x + (int)y );

Mais comme mon visual studio 2010 compilateur dit, je ne peux pas convertir entre Fixed et int.

Quelle est la bonne façon de le faire?

EDIT: Je ne suis pas engagé à la mise en œuvre de {short floor, short fraction} du fixe.

Était-ce utile?

La solution

magie:

typedef union Fixed {
    uint16_t w[2];
    uint32_t d;
} Fixed;
#define Floor w[((Fixed){1}).d==1]
#define Fraction w[((Fixed){1}).d!=1]

Points clés:

  • J'utiliser des types entiers de taille fixe de sorte que vous n'êtes pas en fonction de short étant de 16 bits et int étant 32 bits.
  • Les macros pour Floor et Fraction (majuscule pour éviter de cliquetis avec fonction floor()) l'accès des deux parties d'une manière indépendante de endian, comme foo.Floor et foo.Fraction.

Modifier la demande à l'OP, une explication des macros:

unions sont un moyen de déclarer un objet constitué de plusieurs types différents se chevauchent. Ici, nous avons uint16_t w[2]; uint32_t d; qui se chevauchent, ce qui permet d'accéder à la valeur 2 unités 16 bits ou 1 unité 32 bits.

(Fixed){1} est littéral composé , et pourrait être écrit plus verbeux que (Fixed){{1,0}}. Son premier élément (uint16_t w[2];) s'initialisé avec {1,0}. L'expression ((Fixed){1}).d évalue ensuite l'entier de 32 bits dont la première moitié de 16 bits est de 1 et dont la seconde moitié 16-bit est égal à 0. Sur un petit-boutiste système, cette valeur est 1, alors ((Fixed){1}).d==1 évalue à 1 (vrai) et ((Fixed){1}).d!=1 est évaluée à 0 (faux). Sur un système big-endian, ce sera l'inverse.

Ainsi, sur un système de petit-boutiste, Floor est w[1] et Fraction est w[0]. Sur un système big-endian, Floor est w[0] et Fraction est w[1]. De toute façon, vous finissez par le stockage / l'accès à la moitié correcte de la valeur 32 bits pour la endian-ness de votre plate-forme.

En théorie, un système hypothétique pourrait utiliser une représentation complètement différente pour 16 bits et les valeurs 32 bits (par exemple entrelaçant les bits des deux moitiés), brisant ces macros. Dans la pratique, cela ne va pas se produire. : -)

Autres conseils

Vous pourriez essayer un hack méchant, mais il y a un problème ici avec endian-ness. Quoi que vous fassiez pour convertir, comment est le compilateur censé savoir que vous voulez floor être la plus grande partie du résultat, et fraction la partie moins importante? Toute solution qui repose sur la mémoire de réinterprétant va travailler pour une endian-ness, mais pas un autre.

Vous devez soit:

(1) définissent la conversion explicitement. En supposant que short est de 16 bits:

unsigned int val = (x.floor << 16) + x.fraction;

(2) le changement Fixed de sorte qu'il a un membre de int au lieu de deux courts métrages, puis decompose en cas de besoin, plutôt que composition lorsque cela est nécessaire.

Si vous voulez plus d'être rapide, puis (2) est la chose à faire. Si vous avez un type 64 bits, vous pouvez aussi faire la multiplication sans se décomposer. unsigned int result = (((uint64_t)x) * y) >> 16

Le hack méchant, en passant, serait ceci:

unsigned int val;
assert(sizeof(Fixed) == sizeof(unsigned int))              // could be a static test
assert(2 * sizeof(unsigned short) == sizeof(unsigned int)) // could be a static test
memcpy(&val, &x, sizeof(unsigned int));

qui fonctionnerait sur un système big-endian, où fixe n'a pas de remplissage (et les types entiers ont pas de bits de remplissage). Sur un système little-endian vous auriez besoin les membres de Fixé dans l'autre ordre, ce qui est pourquoi il est méchant. Parfois coulée par memcpy est la bonne chose à faire (dans ce cas, il est un « truc » plutôt que d'un « hack méchant »). Ce n'est pas une de ces fois.

Si vous devez vous pouvez utiliser une union, mais méfiez-vous des questions endian. Vous trouverez peut-être l'arithmétique ne fonctionne pas et certainement pas est portable.

typedef struct Fixed_t {
   union { 
        struct { unsigned short floor; unsigned short fraction }; 
        unsigned int whole;
         };
} Fixed;

qui est plus probable (je pense) au travail big-endian (lequel Windows / Intel est pas).

Ce n'est pas possible portably, comme le compilateur ne garantit pas un Fixed utilisera la même quantité d'espace comme int. La bonne façon de définir une Fixed add(Fixed a, Fixed b) fonction.

Il suffit d'ajouter les morceaux séparément. Vous devez connaître la valeur de la fraction qui signifie « 1 » - ici, je vous appelle que FRAC_MAX:

 // c = a + b
 void fixed_add( Fixed* a, Fixed* b, Fixed* c){
     unsigned short carry = 0;
     if((int)(a->floor) + (int)(b->floor) > FRAC_MAX){
         carry = 1;
         c->fraction = a->floor + b->floor - FRAC_MAX; 
     }
     c->floor = a->floor + b->floor + carry;
 }

Par ailleurs, si vous configurez simplement le point fixe comme étant à 2 octets limite que vous pouvez faire quelque chose comme:

void fixed_add( Fixed* a, Fixed *b, Fixed *c){
    int ia = a->floor << 16 + a->fraction;
    int ib = b->floor << 16 + b->fraction;
    int ic = ia + ib;
    c->floor = ic >> 16;
    c->fraction = ic - c->floor;
}

Essayez ceci:

typedef union {
    struct Fixed_t {
        unsigned short floor; //left side of the decimal point
        unsigned short fraction; //right side of the decimal point
    } Fixed;
    int Fixed_int;
}

Si votre compilateur puts les deux courts de sur 4 octets, vous pouvez utiliser memcpy pour copier votre int dans votre struct, mais comme dit dans une autre réponse, ce n'est pas portable ... et assez laid.

Avez-vous vraiment ajouter séparément chaque champ dans une méthode distincte? Est-ce que vous voulez garder l'entier pour des raisons de performance?

// add two Fixed 
Fixed operator+( Fixed a, Fixed b ) 
{   
...
}

//add Fixed and int
Fixed operator+( Fixed a, int b ) 
{   
...
}

Vous pouvez lancer tout type adressable à un autre en utilisant:

*(newtype *)&var
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top